文件
yjyf/server/application/admin/controller/Withdraw.php
MiAn fafddceaf2 feat: 新增发票抬头、晒单、钱包日志等模型及后台管理功能
- 新增 invoice_title、showcase、wallet_log 模型及对应语言包
- 新增后台控制器、视图及搜索字段配置
- 修复服务类目删除保护逻辑
- 优化后台登录页面文案及样式
- 新增前端角色切换逻辑及页面拦截
- 新增 SVG 图标及样式变量
- 修复接口权限及下拉框默认值问题
2026-11-11 17:02:35 +08:00

276 行
10 KiB
PHP

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<?php
namespace app\admin\controller;
use app\common\controller\Backend;
use think\Db;
use think\exception\PDOException;
/**
* 提现申请
*
* 提现单仅由师傅端发起,后台只负责财务审核与打款标记:
* 待审核(wait) → 审核通过(pass)/驳回(reject);审核通过(pass) → 打款完成(done)/驳回(reject)
* 驳回自动解冻并退回师傅余额,打款完成自动划出冻结资金
*
* @icon fa fa-rmb
*/
class Withdraw extends Backend
{
/**
* Withdraw模型对象
* @var \app\admin\model\Withdraw
*/
protected $model = null;
protected $relationSearch = true;
protected $searchFields = 'id,account,audit_msg,master.realname,master.mobile';
/**
* 审核状态机:当前状态 => 允许流转到的目标状态
* reject/done 为终态,不允许再变更
* @var array
*/
protected $statusFlow = [
'wait' => ['pass', 'reject'],
'pass' => ['done', 'reject'],
];
public function _initialize()
{
parent::_initialize();
$this->model = new \app\admin\model\Withdraw;
$this->view->assign("channelList", $this->model->getChannelList());
$this->view->assign("statusList", $this->model->getStatusList());
}
/**
* 提现列表:关联师傅信息,展示联系方式与处理时间
*/
public function index()
{
$this->request->filter(['strip_tags', 'trim']);
if (false === $this->request->isAjax()) {
return $this->view->fetch();
}
if ($this->request->request('keyField')) {
return $this->selectpage();
}
[$where, $sort, $order, $offset, $limit] = $this->buildparams();
$list = $this->model
->with(['master'])
->where($where)
->order($sort, $order)
->paginate($limit);
$result = ['total' => $list->total(), 'rows' => $list->items()];
return json($result);
}
/**
* 提现审核与资金来源对账详情
*/
public function detail($ids = null)
{
$row = $this->model->get($ids);
if (!$row) {
$this->error(__('No Results were found'));
}
$masterId = (int)$row['master_id'];
$master = $masterId > 0 ? Db::name('master')->where('id', $masterId)->find() : null;
$recentSettles = $masterId > 0
? Db::name('settle')->alias('s')
->join('__ORDER__ o', 'o.id = s.order_id', 'LEFT')
->where('s.master_id', $masterId)
->field('s.*, o.order_no, o.service_name')
->order('s.id desc')
->limit(8)
->select()
: [];
$walletLogs = $masterId > 0
? Db::name('wallet_log')
->where('user_type', 'master')
->where('user_id', $masterId)
->order('id desc')
->limit(10)
->select()
: [];
$this->view->assign([
'row' => $row,
'master' => $master ?: [],
'recentSettles' => $recentSettles,
'walletLogs' => $walletLogs,
]);
return $this->view->fetch();
}
/**
* 财务审核:申请信息只读,仅允许合法状态流转并做资金联动
* - 驳回:退回师傅余额并解冻,写入钱包流水,通知师傅
* - 通过:仅标记审核通过,等待财务打款
* - 打款完成:划出冻结资金,写入钱包流水,通知师傅
*/
public function edit($ids = null)
{
$row = $this->model->get($ids);
if (!$row) {
$this->error(__('No Results were found'));
}
if (false === $this->request->isPost()) {
// 展示师傅当前钱包情况,辅助财务判断
$master = Db::name('master')->where('id', (int)$row['master_id'])->field('id,realname,mobile,user_id,money,frozen_money')->find();
$this->view->assign('row', $row);
$this->view->assign('master', $master ?: []);
$this->view->assign('flowOptions', $this->statusFlow[$row['status']] ?? []);
return $this->view->fetch();
}
$status = (string)$this->request->post('row.status');
$auditMsg = trim((string)$this->request->post('row.audit_msg'));
// 状态机校验:终态禁止操作,流转目标必须合法
if (!isset($this->statusFlow[$row['status']])) {
$this->error('该提现单已处于终态(已驳回/已打款),不允许再次审核');
}
if (!in_array($status, $this->statusFlow[$row['status']], true)) {
$this->error('状态流转不合法,请刷新后重试');
}
if ($status === 'reject' && $auditMsg === '') {
$this->error('驳回时必须填写驳回原因');
}
$masterId = (int)$row['master_id'];
$amount = round((float)$row['amount'], 2);
Db::startTrans();
try {
// 乐观锁更新状态,防止并发重复审核
$affected = Db::name('withdraw')
->where('id', $row['id'])
->where('status', $row['status'])
->update([
'status' => $status,
'audit_msg' => $auditMsg,
'updatetime' => time(),
]);
if (!$affected) {
throw new \Exception('提现单状态已被其他人变更,请刷新后重试');
}
$master = Db::name('master')->where('id', $masterId)->lock(true)->find();
if (!$master) {
throw new \Exception('提现师傅不存在');
}
if ($status === 'reject') {
// 驳回:解冻并退回余额,写入钱包流水
$newMoney = round((float)$master['money'] + $amount, 2);
Db::name('master')->where('id', $masterId)->update([
'money' => $newMoney,
'frozen_money' => round((float)$master['frozen_money'] - $amount, 2),
'updatetime' => time(),
]);
Db::name('wallet_log')->insert([
'user_type' => 'master',
'user_id' => $masterId,
'type' => 'refund',
'amount' => $amount,
'balance' => $newMoney,
'order_id' => 0,
'remark' => '提现驳回,申请金额退回钱包余额' . ($auditMsg !== '' ? '(原因:' . $auditMsg . ')' : ''),
'createtime' => time(),
]);
$this->sendMasterMessage($masterId, (int)$master['user_id'], '提现申请已驳回', '您的提现申请 ¥' . $amount . ' 未通过财务审核:' . ($auditMsg !== '' ? $auditMsg : '无') . ',申请金额已退回钱包余额');
} elseif ($status === 'pass') {
// 审核通过:仅标记,资金保持冻结,等待财务打款
$this->sendMasterMessage($masterId, (int)$master['user_id'], '提现审核通过', '您的提现申请 ¥' . $amount . ' 已通过审核,财务将尽快安排打款到 ' . ($row['channel'] === 'bank' ? '银行卡' : '微信钱包'));
} elseif ($status === 'done') {
// 打款完成:冻结资金划出
Db::name('master')->where('id', $masterId)->update([
'frozen_money' => round((float)$master['frozen_money'] - $amount, 2),
'updatetime' => time(),
]);
Db::name('wallet_log')->insert([
'user_type' => 'master',
'user_id' => $masterId,
'type' => 'withdraw',
'amount' => 0,
'balance' => (float)$master['money'],
'order_id' => 0,
'remark' => '提现打款完成,冻结资金 ¥' . $amount . ' 划出(' . ($row['channel'] === 'bank' ? '银行储蓄卡' : '微信钱包') . ' ' . $row['account'] . ')',
'createtime' => time(),
]);
$this->sendMasterMessage($masterId, (int)$master['user_id'], '提现打款完成', '您的提现 ¥' . $amount . ' 已打款成功,请留意到账通知');
}
Db::commit();
} catch (PDOException|\Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
$this->success();
}
/**
* 提现单仅由师傅端发起,禁止后台手工添加
*/
public function add()
{
$this->error('提现申请仅支持师傅端发起,后台不可手动添加');
}
/**
* 提现流水记录禁止删除,保障资金对账完整
*/
public function del($ids = null)
{
$this->error('提现申请记录禁止删除');
}
/**
* 禁止批量修改状态,状态变更必须走审核流程以触发资金联动
*/
public function multi($ids = null)
{
$this->error('提现审核不支持批量操作,请逐单审核处理');
}
/**
* 向师傅端工作台及所属用户发送站内消息
* @param int $masterId 师傅ID
* @param int $userId 用户ID
* @param string $title 消息标题
* @param string $content 消息内容
*/
protected function sendMasterMessage($masterId, $userId, $title, $content)
{
$now = time();
$rows = [];
if ($masterId > 0) {
$rows[] = [
'receive_type' => 'master',
'receive_id' => (int)$masterId,
'type' => 'audit',
'title' => mb_substr((string)$title, 0, 100),
'content' => mb_substr((string)$content, 0, 500),
'order_id' => 0,
'is_read' => 0,
'createtime' => $now,
];
}
if ($userId > 0) {
$rows[] = [
'receive_type' => 'user',
'receive_id' => (int)$userId,
'type' => 'system',
'title' => mb_substr((string)$title, 0, 100),
'content' => mb_substr((string)$content, 0, 500),
'order_id' => 0,
'is_read' => 0,
'createtime' => $now,
];
}
if ($rows) {
Db::name('message')->insertAll($rows);
}
}
}