本文目录导读:

在PHP项目中实现一套完整的报废管理功能,通常需要覆盖申请、审批、执行、归档这几个核心环节,下面是一个通用的设计思路和核心代码实现示例,你可以根据自己的业务逻辑进行调整。
数据库设计(核心表)
至少需要两张表:一张报废申请表,一张报废明细表(如果是单条物品报废,也可以合并)。
-- 1. 报废主表 CREATE TABLE `scrap_orders` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `order_no` varchar(64) NOT NULL COMMENT '报废单号', `dept_id` int(11) NOT NULL COMMENT '申请部门', `applicant_id` int(11) NOT NULL COMMENT '申请人', `total_amount` decimal(10,2) DEFAULT '0.00' COMMENT '报废总价值', `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态:0-待审批, 1-审批通过, 2-已驳回, 3-已完成', `remark` text COMMENT '备注', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `order_no` (`order_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 2. 报废明细表 CREATE TABLE `scrap_order_items` ( `id` int(11) NOT NULL AUTO_INCREMENT, `order_id` int(11) NOT NULL COMMENT '关联主表ID', `asset_id` int(11) NOT NULL COMMENT '资产编号', `asset_name` varchar(255) NOT NULL COMMENT '资产名称', `spec` varchar(255) DEFAULT NULL COMMENT '规格型号', `quantity` int(11) NOT NULL DEFAULT '1' COMMENT '报废数量', `unit_price` decimal(10,2) DEFAULT '0.00' COMMENT '原值', `scrap_reason` varchar(500) DEFAULT NULL COMMENT '报废原因', `disposal_method` tinyint(4) DEFAULT '0' COMMENT '处置方式:0-销毁, 1-变卖, 2-回收', PRIMARY KEY (`id`), KEY `order_id` (`order_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
后端PHP核心逻辑
建议使用 MVC 结构,这里以 ThinkPHP / Laravel 风格为例,显示核心业务逻辑:
报废单申请(创建)
// ScrapService.php
public function createScrapOrder($data, $items)
{
// 开启事务
DB::beginTransaction();
try {
// 生成单号:日期 + 随机数
$orderNo = 'SCRAP-' . date('Ymd') . '-' . mt_rand(1000, 9999);
// 1. 写入主表
$order = new ScrapOrder();
$order->order_no = $orderNo;
$order->dept_id = $data['dept_id'];
$order->applicant_id = auth()->id();
$order->total_amount = array_sum(array_column($items, 'unit_price'));
$order->status = 0; // 待审批
$order->remark = $data['remark'] ?? '';
$order->save();
// 2. 写入明细表
foreach ($items as $item) {
$orderItem = new ScrapOrderItem();
$orderItem->order_id = $order->id;
$orderItem->asset_id = $item['asset_id'];
$orderItem->asset_name = $item['asset_name'];
$orderItem->spec = $item['spec'] ?? '';
$orderItem->quantity = $item['quantity'];
$orderItem->unit_price = $item['unit_price'];
$orderItem->scrap_reason = $item['scrap_reason'];
$orderItem->disposal_method = $item['disposal_method'] ?? 0;
$orderItem->save();
// 可选:锁定对应的资产,防止重复报废
// Asset::where('id', $item['asset_id'])->update(['status' => 'scrap_pending']);
}
DB::commit();
return ['code' => 1, 'msg' => '申请成功', 'order_id' => $order->id];
} catch (\Exception $e) {
DB::rollBack();
return ['code' => 0, 'msg' => '申请失败: ' . $e->getMessage()];
}
}
审批流程(简单版)
// ScrapService.php
public function approveOrder($orderId, $approveResult, $approveRemark = '')
{
$order = ScrapOrder::find($orderId);
if (!$order) {
return ['code' => 0, 'msg' => '单据不存在'];
}
if ($order->status != 0) {
return ['code' => 0, 'msg' => '当前状态不可审批'];
}
DB::beginTransaction();
try {
if ($approveResult == 1) { // 通过
$order->status = 1; // 审批通过
// 处理资产状态:将资产标记为“已报废”
foreach ($order->items as $item) {
Asset::where('id', $item->asset_id)->update(['status' => 'scrapped']);
}
} else { // 驳回
$order->status = 2;
// 解冻资产状态(如果有锁定)
foreach ($order->items as $item) {
Asset::where('id', $item->asset_id)->update(['status' => 'normal']);
}
}
$order->approve_remark = $approveRemark;
$order->approver_id = auth()->id();
$order->approved_at = now();
$order->save();
DB::commit();
return ['code' => 1, 'msg' => '操作成功'];
} catch (\Exception $e) {
DB::rollBack();
return ['code' => 0, 'msg' => '操作失败: ' . $e->getMessage()];
}
}
资产列表查询(供选择报废资产)
// 获取可报废的资产列表(未被报废、未被借出等)
public function getScrappableAssets($params)
{
$query = Asset::where('status', 'normal')
->where('use_status', 'idle'); // 闲置状态
if (!empty($params['keyword'])) {
$query->where(function($q) use ($params) {
$q->where('asset_name', 'like', '%'.$params['keyword'].'%')
->orWhere('asset_no', 'like', '%'.$params['keyword'].'%');
});
}
return $query->paginate($params['per_page'] ?? 20);
}
前端页面(以Vue为例,仅示意)
通常你需要以下页面:
| 页面 | 功能 |
|---|---|
scrap/create.blade.php |
选择资产 -> 填写原因 -> 提交申请 |
scrap/list.blade.php |
我的申请 / 待我审批 / 全部记录 |
scrap/detail.blade.php |
查看详情 + 审批按钮(根据角色) |
核心交互逻辑(Vue + Element UI 示例):
// 1. 申请页面:多选资产提交
async submitScrap() {
if (this.selectedAssets.length === 0) {
this.$message.warning('请选择至少一项资产');
return;
}
const items = this.selectedAssets.map(asset => ({
asset_id: asset.id,
asset_name: asset.name,
spec: asset.spec,
quantity: 1,
unit_price: asset.price,
scrap_reason: this.reason,
disposal_method: this.disposalMethod
}));
const res = await axios.post('/api/scrap/create', {
dept_id: this.deptId,
remark: this.remark,
items: items
});
if (res.data.code == 1) {
this.$message.success('提交成功');
this.$router.push('/scrap/list');
}
}
权限控制建议
- 普通用户:只能查看自己的申请,只能操作“待审批”状态的单据(不能修改已审批的)。
- 审批人:可以查看所有待审批单据,进行“通过”或“驳回”。
- 管理员:可以查看/导出所有记录,进行最终归档操作。
在控制器中检查权限:
// 在审批方法前
$this->authorize('approve_scrap'); // 使用Laravel Policy或自定义中间件
扩展功能(根据需求)
- 报废单打印:使用
dompdf或TCPDF生成PDF。 - 资产残值回收:如果报废后需要变卖,增加“回收金额”、“买家信息”字段。
- 流程审批流:支持多级审批(部门主管 -> 财务 -> 总经理),可以使用
审批流表+状态机实现。 - Excel导出:使用
PhpSpreadsheet导出所有申请明细。 - 通知机制:提交申请后,通过邮件/钉钉/企业微信通知审批人。
安全问题注意
- 报废操作涉及资产状态变更,必须使用事务保证数据一致性。
- 禁止前端直接传
资产ID修改资产状态,所有变更应在后端校验。 - 对于批量操作,注意 SQL注入 和 越权 问题(尤其是 IDOR,即不安全的直接对象引用)。
如果你使用的是 ThinkPHP 5/6/8 或 Laravel 框架,我可以根据你的具体框架提供更贴合框架语法的代码示例,如果你们的业务包含 多级审批 或 工作流引擎,实现会更复杂一些,需要结合 审批节点表 和 审批记录表 来做状态推进。