TransferOrderHelper.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. <?php
  2. namespace App\Module\Transfer\AdminControllers\Helper;
  3. use App\Module\Transfer\Models\TransferOrder;
  4. use App\Module\Transfer\Enums\TransferStatus;
  5. use App\Module\Transfer\Enums\TransferType;
  6. use Dcat\Admin\Grid;
  7. use Dcat\Admin\Show;
  8. use Dcat\Admin\Form;
  9. /**
  10. * 划转订单管理辅助类
  11. */
  12. class TransferOrderHelper
  13. {
  14. /**
  15. * 配置Grid表格
  16. */
  17. public static function grid(Grid $grid): void
  18. {
  19. $grid->column('id', 'ID')->sortable();
  20. $grid->column('transfer_app.title', '应用名称')->limit(15);
  21. $grid->column('out_order_id', '外部订单ID')->copyable()->limit(20);
  22. $grid->column('user_id', '用户ID')->link(function ($value) {
  23. return admin_url("users/{$value}");
  24. });
  25. $grid->column('out_user_id', '外部用户ID')->limit(15);
  26. $grid->column('type', '类型')->using([
  27. 1 => '转入',
  28. 2 => '转出',
  29. ])->label([
  30. 1 => 'success',
  31. 2 => 'warning',
  32. ]);
  33. $grid->column('status', '状态')->using([
  34. 1 => '已创建',
  35. 20 => '处理中',
  36. 30 => '已回调',
  37. 100 => '已完成',
  38. -1 => '失败',
  39. ])->label([
  40. 1 => 'info',
  41. 20 => 'warning',
  42. 30 => 'primary',
  43. 100 => 'success',
  44. -1 => 'danger',
  45. ]);
  46. $grid->column('out_amount', '外部金额')->display(function ($value) {
  47. return number_format($value, 4);
  48. });
  49. $grid->column('amount', '内部金额')->display(function ($value) {
  50. return number_format($value, 4);
  51. });
  52. // 手续费信息
  53. $grid->column('fee_info', '手续费信息')->display(function () {
  54. if ($this->fee_amount > 0) {
  55. $feeRate = number_format($this->fee_rate * 100, 2) . '%';
  56. $feeAmount = number_format($this->fee_amount, 4);
  57. $actualAmount = number_format($this->actual_amount, 4);
  58. return "费率: {$feeRate}<br>手续费: {$feeAmount}<br>实际: {$actualAmount}";
  59. }
  60. return '无手续费';
  61. });
  62. $grid->column('exchange_rate', '汇率')->display(function ($value) {
  63. return number_format($value, 4);
  64. });
  65. $grid->column('created_at', '创建时间')->sortable();
  66. $grid->column('completed_at', '完成时间');
  67. // 处理时长
  68. $grid->column('duration', '处理时长')->display(function () {
  69. if ($this->completed_at && $this->created_at) {
  70. $seconds = $this->completed_at->diffInSeconds($this->created_at);
  71. if ($seconds < 60) {
  72. return $seconds . '秒';
  73. } elseif ($seconds < 3600) {
  74. return round($seconds / 60, 1) . '分钟';
  75. } else {
  76. return round($seconds / 3600, 1) . '小时';
  77. }
  78. }
  79. return '-';
  80. });
  81. // 筛选器
  82. $grid->filter(function (Grid\Filter $filter) {
  83. $filter->equal('transfer_app_id', '应用')->select(
  84. \App\Module\Transfer\Models\TransferApp::pluck('title', 'id')
  85. );
  86. $filter->equal('type', '类型')->select([
  87. 1 => '转入',
  88. 2 => '转出',
  89. ]);
  90. $filter->equal('status', '状态')->select([
  91. 1 => '已创建',
  92. 20 => '处理中',
  93. 30 => '已回调',
  94. 100 => '已完成',
  95. -1 => '失败',
  96. ]);
  97. $filter->equal('user_id', '用户ID');
  98. $filter->like('out_order_id', '外部订单ID');
  99. $filter->like('out_user_id', '外部用户ID');
  100. $filter->between('amount', '内部金额');
  101. $filter->between('created_at', '创建时间')->datetime();
  102. });
  103. // 操作按钮
  104. $grid->actions(function (Grid\Displayers\Actions $actions) {
  105. $actions->disableDelete(); // 禁用删除
  106. $actions->disableEdit(); // 禁用编辑
  107. // 重试按钮
  108. if ($this->canRetry()) {
  109. $actions->append('<a href="javascript:void(0)" class="btn btn-xs btn-outline-warning retry-order" data-id="'.$this->id.'">重试</a>');
  110. }
  111. // 手动完成按钮(仅转出订单)
  112. if ($this->isTransferOut() && !$this->isFinalStatus()) {
  113. $actions->append('<a href="javascript:void(0)" class="btn btn-xs btn-outline-success manual-complete" data-id="'.$this->id.'">手动完成</a>');
  114. }
  115. });
  116. // 批量操作
  117. $grid->batchActions(function (Grid\Tools\BatchActions $batch) {
  118. $batch->add(new \App\Module\Transfer\AdminControllers\Tools\RetryOrderTool());
  119. });
  120. // 工具栏
  121. $grid->tools(function (Grid\Tools $tools) {
  122. $tools->append('<a href="javascript:void(0)" class="btn btn-sm btn-primary" onclick="showStats()">统计信息</a>');
  123. $tools->append(new \App\Module\Transfer\AdminControllers\Tools\ExportOrderTool());
  124. });
  125. // 默认排序
  126. $grid->model()->orderBy('created_at', 'desc');
  127. }
  128. /**
  129. * 配置Show详情
  130. */
  131. public static function show(Show $show): void
  132. {
  133. $show->field('id', 'ID');
  134. $show->divider();
  135. $show->field('transfer_app.title', '应用名称');
  136. $show->field('out_order_id', '外部订单ID');
  137. $show->field('out_user_id', '外部用户ID');
  138. $show->field('user_id', '内部用户ID');
  139. $show->field('type', '订单类型')->using([
  140. 1 => '转入',
  141. 2 => '转出',
  142. ]);
  143. $show->field('status', '订单状态')->using([
  144. 1 => '已创建',
  145. 20 => '处理中',
  146. 30 => '已回调',
  147. 100 => '已完成',
  148. -1 => '失败',
  149. ]);
  150. $show->divider();
  151. $show->field('out_amount', '外部金额')->as(function ($value) {
  152. return number_format($value, 10);
  153. });
  154. $show->field('amount', '内部金额')->as(function ($value) {
  155. return number_format($value, 10);
  156. });
  157. $show->field('exchange_rate', '使用汇率')->as(function ($value) {
  158. return number_format($value, 4);
  159. });
  160. // 手续费信息
  161. $show->field('fee_rate', '手续费率')->as(function ($value) {
  162. return number_format($value * 100, 2) . '%';
  163. });
  164. $show->field('fee_amount', '手续费金额')->as(function ($value) {
  165. return number_format($value, 4);
  166. });
  167. $show->field('actual_amount', '实际到账金额')->as(function ($value) {
  168. return number_format($value, 4);
  169. });
  170. $show->field('has_fee', '是否收取手续费')->as(function () {
  171. return $this->fee_amount > 0 ? '是' : '否';
  172. });
  173. $show->divider();
  174. $show->field('currency_id', '货币类型')->using([
  175. 1 => '金币',
  176. 2 => '钻石',
  177. 3 => '人民币',
  178. 4 => '美元',
  179. ]);
  180. $show->field('fund_id', '资金账户类型');
  181. $show->divider();
  182. $show->field('callback_data', '回调数据')->json();
  183. $show->divider();
  184. $show->field('error_message', '错误信息');
  185. $show->field('remark', '备注信息');
  186. $show->divider();
  187. $show->field('created_at', '创建时间');
  188. $show->field('processed_at', '处理时间');
  189. $show->field('callback_at', '回调时间');
  190. $show->field('completed_at', '完成时间');
  191. $show->field('updated_at', '更新时间');
  192. // 处理时长
  193. $show->field('processing_duration', '处理时长')->as(function () {
  194. if ($this->completed_at && $this->created_at) {
  195. return $this->completed_at->diffForHumans($this->created_at, true);
  196. }
  197. return '未完成';
  198. });
  199. }
  200. /**
  201. * 获取状态统计
  202. */
  203. public static function getStatusStats(): array
  204. {
  205. $stats = TransferOrder::selectRaw('status, COUNT(*) as count, SUM(amount) as amount')
  206. ->groupBy('status')
  207. ->get()
  208. ->keyBy('status');
  209. $result = [];
  210. foreach (TransferStatus::cases() as $status) {
  211. $stat = $stats->get($status->value);
  212. $result[] = [
  213. 'status' => $status->getDescription(),
  214. 'count' => $stat ? number_format($stat->count) : 0,
  215. 'amount' => $stat ? number_format($stat->amount, 2) : '0.00',
  216. 'color' => $status->getColor(),
  217. ];
  218. }
  219. return $result;
  220. }
  221. /**
  222. * 获取类型统计
  223. */
  224. public static function getTypeStats(): array
  225. {
  226. $stats = TransferOrder::selectRaw('type, COUNT(*) as count, SUM(amount) as amount')
  227. ->groupBy('type')
  228. ->get()
  229. ->keyBy('type');
  230. $result = [];
  231. foreach (TransferType::cases() as $type) {
  232. $stat = $stats->get($type->value);
  233. $result[] = [
  234. 'type' => $type->getDescription(),
  235. 'count' => $stat ? number_format($stat->count) : 0,
  236. 'amount' => $stat ? number_format($stat->amount, 2) : '0.00',
  237. 'color' => $type->getColor(),
  238. ];
  239. }
  240. return $result;
  241. }
  242. /**
  243. * 获取今日统计
  244. */
  245. public static function getTodayStats(): array
  246. {
  247. $today = now()->startOfDay();
  248. $total = TransferOrder::where('created_at', '>=', $today)->count();
  249. $completed = TransferOrder::where('created_at', '>=', $today)
  250. ->where('status', TransferStatus::COMPLETED)->count();
  251. $failed = TransferOrder::where('created_at', '>=', $today)
  252. ->where('status', TransferStatus::FAILED)->count();
  253. $amount = TransferOrder::where('created_at', '>=', $today)->sum('amount');
  254. $feeAmount = TransferOrder::where('created_at', '>=', $today)->sum('fee_amount');
  255. return [
  256. 'total' => number_format($total),
  257. 'completed' => number_format($completed),
  258. 'failed' => number_format($failed),
  259. 'success_rate' => $total > 0 ? number_format($completed / $total * 100, 1) . '%' : '0%',
  260. 'amount' => number_format($amount, 2),
  261. 'fee_amount' => number_format($feeAmount, 2),
  262. ];
  263. }
  264. /**
  265. * 获取手续费统计
  266. */
  267. public static function getFeeStats(): array
  268. {
  269. $stats = TransferOrder::selectRaw('
  270. COUNT(*) as total_orders,
  271. SUM(fee_amount) as total_fee,
  272. AVG(fee_rate) as avg_fee_rate,
  273. SUM(CASE WHEN type = 1 THEN fee_amount ELSE 0 END) as in_fee,
  274. SUM(CASE WHEN type = 2 THEN fee_amount ELSE 0 END) as out_fee,
  275. COUNT(CASE WHEN type = 1 THEN 1 END) as in_orders,
  276. COUNT(CASE WHEN type = 2 THEN 1 END) as out_orders
  277. ')
  278. ->where('status', TransferStatus::COMPLETED)
  279. ->where('fee_amount', '>', 0)
  280. ->first();
  281. return [
  282. 'total_orders' => number_format($stats->total_orders ?? 0),
  283. 'total_fee' => number_format($stats->total_fee ?? 0, 4),
  284. 'avg_fee_rate' => number_format(($stats->avg_fee_rate ?? 0) * 100, 2) . '%',
  285. 'in_fee' => number_format($stats->in_fee ?? 0, 4),
  286. 'out_fee' => number_format($stats->out_fee ?? 0, 4),
  287. 'in_orders' => number_format($stats->in_orders ?? 0),
  288. 'out_orders' => number_format($stats->out_orders ?? 0),
  289. ];
  290. }
  291. }