6
0
Fork 0

线下订单优化

base
Jing Li 2023-11-03 13:36:56 +08:00
parent 1b30f44c6d
commit 827b3f4037
9 changed files with 186 additions and 17 deletions

View File

@ -0,0 +1,57 @@
<?php
namespace App\Admin\Actions\Grid;
use App\Admin\Forms\OrderClose as OrderCloseForm;
use App\Exceptions\BizException;
use App\Models\OfflineOrder;
use App\Services\OfflineOrderService;
use Dcat\Admin\Grid\RowAction;
use Dcat\Admin\Widgets\Modal;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Throwable;
class OfflineOrderRevoke extends RowAction
{
public function title()
{
return '<i class="feather icon-x grid-action-icon"></i>&nbsp;取消订单';
}
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 ['是否取消选中订单?'];
}
}

View File

@ -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();

View File

@ -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');

View File

@ -0,0 +1,72 @@
<?php
namespace App\Console\Commands;
use App\Enums\OfflineOrderStatus;
use App\Models\OfflineOrder;
use App\Services\OfflineOrderService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Throwable;
class OfflineOrderCloseExpiredCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'offline-order:close-expired';
/**
* The console command description.
*
* @var string
*/
protected $description = '关闭过期的线下订单';
/**
* Execute the console command.
*
* @return int
*/
public function handle(OfflineOrderService $offlineOrderService)
{
while (true) {
// 待付款的线下订单的有效期
$expirs = (int) app_settings('app.offline_order_payment_expires_at', 0);
if ($expirs > 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);
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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 "<span class='label' style='background: $background;'>{$this->text()}</span>";
$background = Admin::color()->get($color, $color);
return '<i class="fa fa-circle" style="font-size: 13px;color: '.$background.'"></i>&nbsp;'.$this->text() ?? 'Unknown';
}
public function text()

View File

@ -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',
};
}

View File

@ -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;

View File

@ -21,6 +21,7 @@ return [
'payment_time' => '支付时间',
'out_trade_no' => '外部交易单号',
'status' => '状态',
'revoked_at' => '取消时间',
],
'options' => [
],