diff --git a/app/Admin/Actions/Grid/OfflineOrderRevoke.php b/app/Admin/Actions/Grid/OfflineOrderRevoke.php new file mode 100644 index 00000000..2b9117aa --- /dev/null +++ b/app/Admin/Actions/Grid/OfflineOrderRevoke.php @@ -0,0 +1,57 @@ + 取消订单'; + } + + protected function authorize($user): bool + { + return $user->can('dcat.admin.offline_orders.revoke'); + } + + public function handle(Request $request) + { + try { + DB::beginTransaction(); + + $order = OfflineOrder::lockForUpdate()->findOrFail($this->getKey()); + + if (! $order->isPending()) { + throw new BizException('订单状态已更新'); + } + + (new OfflineOrderService())->revoke($order); + + DB::commit(); + } catch (Throwable $th) { + DB::rollBack(); + report($th); + return $this->response()->error('操作失败,'.$th->getMessage())->refresh(); + } + + return $this->response()->success('操作成功')->refresh(); + } + + /** + * @return string|array|void + */ + public function confirm() + { + return ['是否取消选中订单?']; + } +} diff --git a/app/Admin/Controllers/OfflineOrderController.php b/app/Admin/Controllers/OfflineOrderController.php index d0078a21..a592c812 100644 --- a/app/Admin/Controllers/OfflineOrderController.php +++ b/app/Admin/Controllers/OfflineOrderController.php @@ -2,6 +2,7 @@ namespace App\Admin\Controllers; +use App\Admin\Actions\Grid\OfflineOrderRevoke; use App\Admin\Repositories\OfflineOrder as OfflineOrderRepository; use App\Admin\Widgets\InfoBox; use App\Enums\OfflineOrderStatus; @@ -59,7 +60,7 @@ class OfflineOrderController extends AdminController $grid->column('discount_reduction_amount')->display(fn($v) => bcdiv($v, 100, 2))->prepend('¥'); $grid->column('points_deduction_amount')->display(fn($v) => bcdiv($v, 100, 2))->prepend('¥'); $grid->column('payment_amount')->display(fn($v) => bcdiv($v, 100, 2))->prepend('¥'); - $grid->column('status')->display(fn($v) => $v->label()); + $grid->column('status')->display(fn($v) => $v->dot()); $grid->column('payment_method')->display(fn($v) => $v?->dot()); $grid->column('payment_time'); $grid->column('created_at'); @@ -97,6 +98,10 @@ class OfflineOrderController extends AdminController if ($user->can('dcat.admin.offline_orders.show')) { $actions->disableView(false); } + + if ($user->can('dcat.admin.offline_orders.revoke') && $actions->row->isPending()) { + $actions->append(new OfflineOrderRevoke()); + } }); $grid->header(function ($collection) use ($grid) { @@ -153,7 +158,7 @@ class OfflineOrderController extends AdminController ->prepend('¥'); $show->field('status') ->escape(false) - ->as(fn () => $this->status->label()); + ->as(fn () => $this->status->dot()); $show->field('payment_method') ->escape(false) ->as(fn () => $this->payment_method?->dot()); @@ -161,6 +166,7 @@ class OfflineOrderController extends AdminController $show->field('payment_sn'); $show->field('out_trade_no'); $show->field('created_at'); + $show->field('revoked_at'); }); $show->panel()->tools(function (Show\Tools $tools) use ($show) { $tools->disableEdit(); diff --git a/app/Admin/Forms/Settings/App.php b/app/Admin/Forms/Settings/App.php index fd71141c..53d36deb 100644 --- a/app/Admin/Forms/Settings/App.php +++ b/app/Admin/Forms/Settings/App.php @@ -46,6 +46,7 @@ class App extends Form $this->text('search_hot_keys', '搜索热词(英文半角逗号隔开)')->value($appSettings['search_hot_keys'] ?? ''); $this->text('invite_uri', '分享邀请地址(链接)')->value($appSettings['invite_uri'] ?? ''); $this->divider(); + $this->number('offline_order_payment_expires_at', '线下订单支付过期时间(秒)')->value($appSettings['offline_order_payment_expires_at'] ?? ''); $this->number('order_payment_expires_at', '订单支付过期时间(秒)')->value($appSettings['order_payment_expires_at'] ?? ''); $this->text('order_auto_complete_days', '订单自动完成时间(天)')->value($appSettings['order_auto_complete_days'] ?? '')->rules('required|numeric|min:0'); $this->text('sale_after_expire_days', '售后过期时间(天)')->value($appSettings['sale_after_expire_days'] ?? '')->rules('required|numeric|min:0'); diff --git a/app/Console/Commands/OfflineOrderCloseExpiredCommand.php b/app/Console/Commands/OfflineOrderCloseExpiredCommand.php new file mode 100644 index 00000000..748dcd6f --- /dev/null +++ b/app/Console/Commands/OfflineOrderCloseExpiredCommand.php @@ -0,0 +1,72 @@ + 0) { + $ids = OfflineOrder::where('status', OfflineOrderStatus::Pending) + ->where('created_at', '<=', now()->subSeconds($expirs)) + ->oldest('id') + ->limit(500) + ->pluck('id'); + + foreach ($ids as $id) { + try { + DB::beginTransaction(); + + $order = OfflineOrder::lockForUpdate()->findOrFail($id); + + $offlineOrderService->revoke($order); + + DB::commit(); + } catch (Throwable $e) { + DB::rollBack(); + + report($e); + } + } + + if ($ids->isEmpty()) { + sleep(60); + } else { + sleep(10); + } + } else { + sleep(60); + } + } + } +} diff --git a/app/Console/Commands/OrderCloseExpiredCommand.php b/app/Console/Commands/OrderCloseExpiredCommand.php index 04b15f07..f1333112 100644 --- a/app/Console/Commands/OrderCloseExpiredCommand.php +++ b/app/Console/Commands/OrderCloseExpiredCommand.php @@ -3,7 +3,10 @@ namespace App\Console\Commands; use App\Models\Order; +use App\Services\OrderService; use Illuminate\Console\Command; +use Illuminate\Support\Facades\DB; +use Throwable; class OrderCloseExpiredCommand extends Command { @@ -26,25 +29,31 @@ class OrderCloseExpiredCommand extends Command * * @return int */ - public function handle() + public function handle(OrderService $orderService) { while (true) { - $page = 0; + $ids = Order::expired()->oldest('id')->limit(500)->pluck('id'); - Order::select('id')->expired()->chunkById(1000, function ($orders) use (&$page) { - Order::whereIn('id', $orders->pluck('id')->all())->where('status', Order::STATUS_PENDING)->update([ - 'status' => Order::STATUS_CANCELLED, - ]); + foreach ($ids as $id) { + try { + DB::beginTransaction(); - $page++; - }); + $order = Order::lockForUpdate()->findOrFail($id); - if ($page === 0) { + $orderService->cancel($order); + + DB::commit(); + } catch (Throwable $e) { + DB::rollBack(); + + report($e); + } + } + + if ($ids->isEmpty()) { sleep(60); - } elseif ($page === 1) { - sleep(30); } else { - sleep(15); + sleep(10); } } } diff --git a/app/Enums/OfflineOrderStatus.php b/app/Enums/OfflineOrderStatus.php index eb9d198e..794a3614 100644 --- a/app/Enums/OfflineOrderStatus.php +++ b/app/Enums/OfflineOrderStatus.php @@ -9,11 +9,13 @@ enum OfflineOrderStatus: int { case Paid = 1; case Revoked = 10; - public function label() + public function dot() { - $background = Admin::color()->get($this->color()); + $color = $this->color(); - return "{$this->text()}"; + $background = Admin::color()->get($color, $color); + + return ' '.$this->text() ?? 'Unknown'; } public function text() diff --git a/app/Enums/PayWay.php b/app/Enums/PayWay.php index 51d722dc..6e484b5d 100644 --- a/app/Enums/PayWay.php +++ b/app/Enums/PayWay.php @@ -36,6 +36,7 @@ enum PayWay: string { static::Wallet => '#ff8acc', static::WxpayApp, static::WxpayH5, static::WxpayJsApi, static::WxpayMiniProgram, static::WxpayTransfer, static::WxPayShare => '#21b978', static::AlipayApp => '#3085d6', + static::None => '#b3b9bf', default => '#ea5455', }; } diff --git a/app/Services/OfflineOrderService.php b/app/Services/OfflineOrderService.php index 9d234204..cba51305 100644 --- a/app/Services/OfflineOrderService.php +++ b/app/Services/OfflineOrderService.php @@ -167,6 +167,26 @@ class OfflineOrderService ]; } + public function revoke(OfflineOrder $order) + { + if ($order->isPaid()) { + throw new BizException('订单已付款'); + } + + if ($order->isRevoked()) { + throw new BizException('订单已取消'); + } + + if ($order->points_deduction_amount > 0) { + (new PointService())->change($order->user, $order->points_deduction_amount, PointLogAction::Refund, "线下订单{$order->sn}退还积分", $order); + } + + $order->update([ + 'status' => OfflineOrderStatus::Revoked, + 'revoked_at' => now(), + ]); + } + protected function insertOrderItems(OfflineOrder $order, array $items) { $remainingPointDiscountAmount = $order->points_deduction_amount; diff --git a/resources/lang/zh_CN/offline-order.php b/resources/lang/zh_CN/offline-order.php index 023d8615..19fa6697 100644 --- a/resources/lang/zh_CN/offline-order.php +++ b/resources/lang/zh_CN/offline-order.php @@ -21,6 +21,7 @@ return [ 'payment_time' => '支付时间', 'out_trade_no' => '外部交易单号', 'status' => '状态', + 'revoked_at' => '取消时间', ], 'options' => [ ],