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' => [
],