feat: add syj promote workflow
This commit is contained in:
119
pro_v3.5.1/app/controller/admin/v1/syj/PromoteController.php
Normal file
119
pro_v3.5.1/app/controller/admin/v1/syj/PromoteController.php
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\controller\admin\v1\syj;
|
||||||
|
|
||||||
|
use app\controller\admin\AuthController;
|
||||||
|
use app\services\syj\SyjPromoteConfigServices;
|
||||||
|
use app\services\syj\SyjPromoteRewardTriggerServices;
|
||||||
|
use app\services\syj\SyjPromoteSettlementServices;
|
||||||
|
use app\services\syj\SyjPromoteTaskServices;
|
||||||
|
use think\annotation\Inject;
|
||||||
|
|
||||||
|
class PromoteController extends AuthController
|
||||||
|
{
|
||||||
|
#[Inject]
|
||||||
|
protected SyjPromoteTaskServices $services;
|
||||||
|
|
||||||
|
#[Inject]
|
||||||
|
protected SyjPromoteSettlementServices $settlementServices;
|
||||||
|
|
||||||
|
#[Inject]
|
||||||
|
protected SyjPromoteConfigServices $configServices;
|
||||||
|
|
||||||
|
#[Inject]
|
||||||
|
protected SyjPromoteRewardTriggerServices $triggerServices;
|
||||||
|
|
||||||
|
public function taskList(): mixed
|
||||||
|
{
|
||||||
|
$where = $this->request->getMore([
|
||||||
|
['keyword', ''],
|
||||||
|
['status', ''],
|
||||||
|
['reward_trigger_status', ''],
|
||||||
|
['start_time', ''],
|
||||||
|
['end_time', ''],
|
||||||
|
['page', 1],
|
||||||
|
['limit', 20],
|
||||||
|
]);
|
||||||
|
$page = (int)$where['page'];
|
||||||
|
$limit = (int)$where['limit'];
|
||||||
|
unset($where['page'], $where['limit']);
|
||||||
|
return $this->success($this->services->adminTasks($where, $page, $limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function taskDetail(int $id): mixed
|
||||||
|
{
|
||||||
|
return $this->success($this->services->adminTaskDetail($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function taskRecords(int $id): mixed
|
||||||
|
{
|
||||||
|
return $this->success($this->services->records($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cashoutList(): mixed
|
||||||
|
{
|
||||||
|
$where = $this->request->getMore([
|
||||||
|
['keyword', ''],
|
||||||
|
['audit_status', ''],
|
||||||
|
['page', 1],
|
||||||
|
['limit', 20],
|
||||||
|
]);
|
||||||
|
$where['settle_type'] = 'early_cashout';
|
||||||
|
$page = (int)$where['page'];
|
||||||
|
$limit = (int)$where['limit'];
|
||||||
|
unset($where['page'], $where['limit']);
|
||||||
|
return $this->success($this->settlementServices->adminList($where, $page, $limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function settlementList(): mixed
|
||||||
|
{
|
||||||
|
$where = $this->request->getMore([
|
||||||
|
['keyword', ''],
|
||||||
|
['audit_status', ''],
|
||||||
|
['settle_type', ''],
|
||||||
|
['page', 1],
|
||||||
|
['limit', 20],
|
||||||
|
]);
|
||||||
|
$page = (int)$where['page'];
|
||||||
|
$limit = (int)$where['limit'];
|
||||||
|
unset($where['page'], $where['limit']);
|
||||||
|
return $this->success($this->settlementServices->adminList($where, $page, $limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function auditCashout(int $id): mixed
|
||||||
|
{
|
||||||
|
$data = $this->request->postMore([
|
||||||
|
['status', 1],
|
||||||
|
['remark', ''],
|
||||||
|
]);
|
||||||
|
$this->settlementServices->auditCashout($id, (int)$this->adminId, (int)$data['status'], (string)$data['remark']);
|
||||||
|
return $this->success('审核成功');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConfig(): mixed
|
||||||
|
{
|
||||||
|
return $this->success($this->configServices->getConfig());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveConfig(): mixed
|
||||||
|
{
|
||||||
|
$data = $this->request->postMore([
|
||||||
|
['base_amount', 4333],
|
||||||
|
['target_count', 4],
|
||||||
|
['reward_rates', [10, 20, 30, 40]],
|
||||||
|
['early_cashout_fee_rate', 7],
|
||||||
|
['task_generate_timing', 'on_confirm'],
|
||||||
|
['task_order_dedupe', 'order'],
|
||||||
|
['reward_trigger_enable', 1],
|
||||||
|
]);
|
||||||
|
$this->configServices->saveConfig($data);
|
||||||
|
return $this->success('保存成功');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function retryTrigger(int $taskId): mixed
|
||||||
|
{
|
||||||
|
$this->triggerServices->retry($taskId);
|
||||||
|
return $this->success('重试完成');
|
||||||
|
}
|
||||||
|
}
|
||||||
66
pro_v3.5.1/app/controller/api/v1/syj/PromoteController.php
Normal file
66
pro_v3.5.1/app/controller/api/v1/syj/PromoteController.php
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\controller\api\v1\syj;
|
||||||
|
|
||||||
|
use app\Request;
|
||||||
|
use app\services\syj\SyjPromoteSettlementServices;
|
||||||
|
use app\services\syj\SyjPromoteTaskServices;
|
||||||
|
use think\annotation\Inject;
|
||||||
|
|
||||||
|
class PromoteController
|
||||||
|
{
|
||||||
|
#[Inject]
|
||||||
|
protected SyjPromoteTaskServices $services;
|
||||||
|
|
||||||
|
#[Inject]
|
||||||
|
protected SyjPromoteSettlementServices $settlementServices;
|
||||||
|
|
||||||
|
public function overview(Request $request): mixed
|
||||||
|
{
|
||||||
|
return app('json')->success($this->services->overview((int)$request->uid()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function taskList(Request $request): mixed
|
||||||
|
{
|
||||||
|
[$page, $limit] = $this->page($request);
|
||||||
|
$where = ['status' => $request->param('status', '')];
|
||||||
|
return app('json')->success($this->services->userTasks((int)$request->uid(), $where, $page, $limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function taskDetail(Request $request, int $id): mixed
|
||||||
|
{
|
||||||
|
return app('json')->success($this->services->userTaskDetail((int)$request->uid(), $id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function taskRecords(Request $request, int $id): mixed
|
||||||
|
{
|
||||||
|
$task = $this->services->userTaskDetail((int)$request->uid(), $id);
|
||||||
|
return app('json')->success($task['records'] ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cashout(Request $request, int $id): mixed
|
||||||
|
{
|
||||||
|
return app('json')->success($this->settlementServices->applyCashout((int)$request->uid(), $id), '提交成功');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function amountLog(Request $request): mixed
|
||||||
|
{
|
||||||
|
[$page, $limit] = $this->page($request);
|
||||||
|
return app('json')->success($this->services->amountLogs((int)$request->uid(), $page, $limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function settlementList(Request $request): mixed
|
||||||
|
{
|
||||||
|
[$page, $limit] = $this->page($request);
|
||||||
|
return app('json')->success($this->settlementServices->listForUser((int)$request->uid(), $page, $limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function page(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
max(1, (int)$request->param('page', 1)),
|
||||||
|
min(50, max(1, (int)$request->param('limit', 15))),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
23
pro_v3.5.1/app/dao/syj/PromoteAmountLogDao.php
Normal file
23
pro_v3.5.1/app/dao/syj/PromoteAmountLogDao.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\dao\syj;
|
||||||
|
|
||||||
|
use app\dao\BaseDao;
|
||||||
|
use app\model\syj\PromoteAmountLog;
|
||||||
|
|
||||||
|
class PromoteAmountLogDao extends BaseDao
|
||||||
|
{
|
||||||
|
protected function setModel(): string
|
||||||
|
{
|
||||||
|
return PromoteAmountLog::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserList(int $uid, int $page, int $limit): array
|
||||||
|
{
|
||||||
|
$model = $this->getModel()->where('uid', $uid);
|
||||||
|
$count = (clone $model)->count();
|
||||||
|
$list = $model->order('add_time', 'desc')->page($page, $limit)->select()->toArray();
|
||||||
|
return compact('list', 'count');
|
||||||
|
}
|
||||||
|
}
|
||||||
15
pro_v3.5.1/app/dao/syj/PromoteRewardTriggerDao.php
Normal file
15
pro_v3.5.1/app/dao/syj/PromoteRewardTriggerDao.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\dao\syj;
|
||||||
|
|
||||||
|
use app\dao\BaseDao;
|
||||||
|
use app\model\syj\PromoteRewardTrigger;
|
||||||
|
|
||||||
|
class PromoteRewardTriggerDao extends BaseDao
|
||||||
|
{
|
||||||
|
protected function setModel(): string
|
||||||
|
{
|
||||||
|
return PromoteRewardTrigger::class;
|
||||||
|
}
|
||||||
|
}
|
||||||
43
pro_v3.5.1/app/dao/syj/PromoteSettlementDao.php
Normal file
43
pro_v3.5.1/app/dao/syj/PromoteSettlementDao.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\dao\syj;
|
||||||
|
|
||||||
|
use app\dao\BaseDao;
|
||||||
|
use app\model\syj\PromoteSettlement;
|
||||||
|
|
||||||
|
class PromoteSettlementDao extends BaseDao
|
||||||
|
{
|
||||||
|
protected function setModel(): string
|
||||||
|
{
|
||||||
|
return PromoteSettlement::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserList(int $uid, int $page, int $limit): array
|
||||||
|
{
|
||||||
|
$model = $this->getModel()->where('uid', $uid);
|
||||||
|
$count = (clone $model)->count();
|
||||||
|
$list = $model->order('add_time', 'desc')->page($page, $limit)->select()->toArray();
|
||||||
|
return compact('list', 'count');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAdminList(array $where, int $page, int $limit): array
|
||||||
|
{
|
||||||
|
$model = $this->getModel()->alias('s')
|
||||||
|
->leftJoin('user u', 'u.uid = s.uid')
|
||||||
|
->leftJoin('syj_promote_task t', 't.id = s.task_id')
|
||||||
|
->field('s.*,t.task_no,u.nickname,u.phone');
|
||||||
|
if (isset($where['audit_status']) && $where['audit_status'] !== '') {
|
||||||
|
$model = $model->where('s.audit_status', (int)$where['audit_status']);
|
||||||
|
}
|
||||||
|
if (!empty($where['settle_type'])) {
|
||||||
|
$model = $model->where('s.settle_type', $where['settle_type']);
|
||||||
|
}
|
||||||
|
if (!empty($where['keyword'])) {
|
||||||
|
$model = $model->where('t.task_no|u.nickname|u.phone|u.uid', 'like', '%' . $where['keyword'] . '%');
|
||||||
|
}
|
||||||
|
$count = (clone $model)->count();
|
||||||
|
$list = $model->order('s.add_time', 'desc')->page($page, $limit)->select()->toArray();
|
||||||
|
return compact('list', 'count');
|
||||||
|
}
|
||||||
|
}
|
||||||
73
pro_v3.5.1/app/dao/syj/PromoteTaskDao.php
Normal file
73
pro_v3.5.1/app/dao/syj/PromoteTaskDao.php
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\dao\syj;
|
||||||
|
|
||||||
|
use app\dao\BaseDao;
|
||||||
|
use app\model\syj\PromoteTask;
|
||||||
|
|
||||||
|
class PromoteTaskDao extends BaseDao
|
||||||
|
{
|
||||||
|
protected function setModel(): string
|
||||||
|
{
|
||||||
|
return PromoteTask::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserList(int $uid, array $where, int $page, int $limit): array
|
||||||
|
{
|
||||||
|
$model = $this->getModel()->where('uid', $uid);
|
||||||
|
if (isset($where['status']) && $where['status'] !== '') {
|
||||||
|
$model = $model->where('status', (int)$where['status']);
|
||||||
|
}
|
||||||
|
$count = (clone $model)->count();
|
||||||
|
$list = $model->order('add_time', 'desc')->page($page, $limit)->select()->toArray();
|
||||||
|
return compact('list', 'count');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAdminList(array $where, int $page, int $limit): array
|
||||||
|
{
|
||||||
|
$model = $this->getModel()->alias('t')
|
||||||
|
->leftJoin('user u', 'u.uid = t.uid')
|
||||||
|
->field('t.*,u.nickname,u.phone');
|
||||||
|
if (!empty($where['keyword'])) {
|
||||||
|
$keyword = '%' . $where['keyword'] . '%';
|
||||||
|
$model = $model->where('t.task_no|t.source_order_no|u.nickname|u.phone|u.uid', 'like', $keyword);
|
||||||
|
}
|
||||||
|
if (isset($where['status']) && $where['status'] !== '') {
|
||||||
|
$model = $model->where('t.status', (int)$where['status']);
|
||||||
|
}
|
||||||
|
if (isset($where['reward_trigger_status']) && $where['reward_trigger_status'] !== '') {
|
||||||
|
$model = $model->where('t.reward_trigger_status', (int)$where['reward_trigger_status']);
|
||||||
|
}
|
||||||
|
if (!empty($where['start_time'])) {
|
||||||
|
$model = $model->where('t.add_time', '>=', strtotime($where['start_time']));
|
||||||
|
}
|
||||||
|
if (!empty($where['end_time'])) {
|
||||||
|
$model = $model->where('t.add_time', '<=', strtotime($where['end_time']) + 86399);
|
||||||
|
}
|
||||||
|
$count = (clone $model)->count();
|
||||||
|
$list = $model->order('t.add_time', 'desc')->page($page, $limit)->select()->toArray();
|
||||||
|
return compact('list', 'count');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEarliestActiveTask(int $uid): ?array
|
||||||
|
{
|
||||||
|
$row = $this->getModel()
|
||||||
|
->where('uid', $uid)
|
||||||
|
->where('status', 0)
|
||||||
|
->order('add_time', 'asc')
|
||||||
|
->lock(true)
|
||||||
|
->find();
|
||||||
|
return $row ? $row->toArray() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCashoutAvailableTasks(int $uid): array
|
||||||
|
{
|
||||||
|
return $this->getModel()
|
||||||
|
->where('uid', $uid)
|
||||||
|
->where('status', 0)
|
||||||
|
->whereBetween('progress_count', [1, 3])
|
||||||
|
->select()
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
24
pro_v3.5.1/app/dao/syj/PromoteTaskRecordDao.php
Normal file
24
pro_v3.5.1/app/dao/syj/PromoteTaskRecordDao.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\dao\syj;
|
||||||
|
|
||||||
|
use app\dao\BaseDao;
|
||||||
|
use app\model\syj\PromoteTaskRecord;
|
||||||
|
|
||||||
|
class PromoteTaskRecordDao extends BaseDao
|
||||||
|
{
|
||||||
|
protected function setModel(): string
|
||||||
|
{
|
||||||
|
return PromoteTaskRecord::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTaskRecords(int $taskId, int $uid = 0): array
|
||||||
|
{
|
||||||
|
$model = $this->getModel()->where('task_id', $taskId);
|
||||||
|
if ($uid > 0) {
|
||||||
|
$model = $model->where('uid', $uid);
|
||||||
|
}
|
||||||
|
return $model->order('step_no', 'asc')->select()->toArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
21
pro_v3.5.1/app/dao/syj/PromoteUserAmountDao.php
Normal file
21
pro_v3.5.1/app/dao/syj/PromoteUserAmountDao.php
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\dao\syj;
|
||||||
|
|
||||||
|
use app\dao\BaseDao;
|
||||||
|
use app\model\syj\PromoteUserAmount;
|
||||||
|
|
||||||
|
class PromoteUserAmountDao extends BaseDao
|
||||||
|
{
|
||||||
|
protected function setModel(): string
|
||||||
|
{
|
||||||
|
return PromoteUserAmount::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function lockByUid(int $uid): ?array
|
||||||
|
{
|
||||||
|
$row = $this->getModel()->where('uid', $uid)->lock(true)->find();
|
||||||
|
return $row ? $row->toArray() : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ use app\services\order\StoreOrderComputedServices;
|
|||||||
use app\services\order\StoreOrderCreateServices;
|
use app\services\order\StoreOrderCreateServices;
|
||||||
use app\services\order\StoreOrderInvoiceServices;
|
use app\services\order\StoreOrderInvoiceServices;
|
||||||
use app\services\order\StoreOrderTakeServices;
|
use app\services\order\StoreOrderTakeServices;
|
||||||
|
use app\services\syj\SyjPromoteTaskServices;
|
||||||
use app\services\user\channel\ChannelMerchantServices;
|
use app\services\user\channel\ChannelMerchantServices;
|
||||||
use app\services\user\UserMoneyServices;
|
use app\services\user\UserMoneyServices;
|
||||||
use app\services\user\UserServices;
|
use app\services\user\UserServices;
|
||||||
@@ -69,6 +70,17 @@ class Pay implements ListenerInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!empty($orderInfo['is_queue_goods'])) {
|
||||||
|
try {
|
||||||
|
/** @var SyjPromoteTaskServices $syjServices */
|
||||||
|
$syjServices = app()->make(SyjPromoteTaskServices::class);
|
||||||
|
$syjServices->handleOrderEffective(is_array($orderInfo) ? $orderInfo : $orderInfo->toArray(), 'order_pay');
|
||||||
|
$syjServices->handleRecommendedOrder(is_array($orderInfo) ? $orderInfo : $orderInfo->toArray(), 'order_pay');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::error('[SYJ] 支付事件处理失败 order_id=' . ($orderInfo['id'] ?? 0) . ': ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//创建拼团
|
//创建拼团
|
||||||
if ($orderInfo['activity_id'] && !$orderInfo['refund_status']) {
|
if ($orderInfo['activity_id'] && !$orderInfo['refund_status']) {
|
||||||
//拼团
|
//拼团
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use app\jobs\supplier\SupplierFinanceJob;use app\jobs\system\CapitalFlowJob;
|
|||||||
use app\services\order\StoreOrderInvoiceServices;
|
use app\services\order\StoreOrderInvoiceServices;
|
||||||
use app\services\order\StoreOrderServices;
|
use app\services\order\StoreOrderServices;
|
||||||
use app\services\order\StoreOrderStatusServices;
|
use app\services\order\StoreOrderStatusServices;
|
||||||
|
use app\services\syj\SyjPromoteTaskServices;
|
||||||
use app\services\user\UserServices;
|
use app\services\user\UserServices;
|
||||||
use crmeb\interfaces\ListenerInterface;
|
use crmeb\interfaces\ListenerInterface;
|
||||||
|
|
||||||
@@ -48,6 +49,16 @@ class Refund implements ListenerInterface
|
|||||||
//订单退款消息推送
|
//订单退款消息推送
|
||||||
event('notice.notice', [['data' => $data, 'order' => $order], 'order_refund']);
|
event('notice.notice', [['data' => $data, 'order' => $order], 'order_refund']);
|
||||||
|
|
||||||
|
if (!empty($order['is_queue_goods'])) {
|
||||||
|
try {
|
||||||
|
/** @var SyjPromoteTaskServices $syjServices */
|
||||||
|
$syjServices = app()->make(SyjPromoteTaskServices::class);
|
||||||
|
$syjServices->handleRefund($order, $data);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
\think\facade\Log::error('[SYJ] 退款事件处理失败 order_id=' . ($order['id'] ?? 0) . ': ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//检测主订单 是否全部退款
|
//检测主订单 是否全部退款
|
||||||
if ($order['pid']) {
|
if ($order['pid']) {
|
||||||
$id = (int)$order['pid'];
|
$id = (int)$order['pid'];
|
||||||
|
|||||||
23
pro_v3.5.1/app/model/syj/PromoteAmountLog.php
Normal file
23
pro_v3.5.1/app/model/syj/PromoteAmountLog.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\model\syj;
|
||||||
|
|
||||||
|
use crmeb\basic\BaseModel;
|
||||||
|
use crmeb\traits\ModelTrait;
|
||||||
|
|
||||||
|
class PromoteAmountLog extends BaseModel
|
||||||
|
{
|
||||||
|
use ModelTrait;
|
||||||
|
|
||||||
|
protected $pk = 'id';
|
||||||
|
|
||||||
|
protected $name = 'syj_promote_amount_log';
|
||||||
|
|
||||||
|
protected $autoWriteTimestamp = false;
|
||||||
|
|
||||||
|
public function setAddTimeAttr(): int
|
||||||
|
{
|
||||||
|
return time();
|
||||||
|
}
|
||||||
|
}
|
||||||
22
pro_v3.5.1/app/model/syj/PromoteRewardTrigger.php
Normal file
22
pro_v3.5.1/app/model/syj/PromoteRewardTrigger.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\model\syj;
|
||||||
|
|
||||||
|
use crmeb\basic\BaseModel;
|
||||||
|
use crmeb\traits\ModelTrait;
|
||||||
|
|
||||||
|
class PromoteRewardTrigger extends BaseModel
|
||||||
|
{
|
||||||
|
use ModelTrait;
|
||||||
|
|
||||||
|
protected $pk = 'id';
|
||||||
|
|
||||||
|
protected $name = 'syj_promote_reward_trigger';
|
||||||
|
|
||||||
|
protected $autoWriteTimestamp = 'int';
|
||||||
|
|
||||||
|
protected $createTime = 'add_time';
|
||||||
|
|
||||||
|
protected $updateTime = 'update_time';
|
||||||
|
}
|
||||||
22
pro_v3.5.1/app/model/syj/PromoteSettlement.php
Normal file
22
pro_v3.5.1/app/model/syj/PromoteSettlement.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\model\syj;
|
||||||
|
|
||||||
|
use crmeb\basic\BaseModel;
|
||||||
|
use crmeb\traits\ModelTrait;
|
||||||
|
|
||||||
|
class PromoteSettlement extends BaseModel
|
||||||
|
{
|
||||||
|
use ModelTrait;
|
||||||
|
|
||||||
|
protected $pk = 'id';
|
||||||
|
|
||||||
|
protected $name = 'syj_promote_settlement';
|
||||||
|
|
||||||
|
protected $autoWriteTimestamp = 'int';
|
||||||
|
|
||||||
|
protected $createTime = 'add_time';
|
||||||
|
|
||||||
|
protected $updateTime = 'update_time';
|
||||||
|
}
|
||||||
22
pro_v3.5.1/app/model/syj/PromoteTask.php
Normal file
22
pro_v3.5.1/app/model/syj/PromoteTask.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\model\syj;
|
||||||
|
|
||||||
|
use crmeb\basic\BaseModel;
|
||||||
|
use crmeb\traits\ModelTrait;
|
||||||
|
|
||||||
|
class PromoteTask extends BaseModel
|
||||||
|
{
|
||||||
|
use ModelTrait;
|
||||||
|
|
||||||
|
protected $pk = 'id';
|
||||||
|
|
||||||
|
protected $name = 'syj_promote_task';
|
||||||
|
|
||||||
|
protected $autoWriteTimestamp = 'int';
|
||||||
|
|
||||||
|
protected $createTime = 'add_time';
|
||||||
|
|
||||||
|
protected $updateTime = 'update_time';
|
||||||
|
}
|
||||||
22
pro_v3.5.1/app/model/syj/PromoteTaskRecord.php
Normal file
22
pro_v3.5.1/app/model/syj/PromoteTaskRecord.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\model\syj;
|
||||||
|
|
||||||
|
use crmeb\basic\BaseModel;
|
||||||
|
use crmeb\traits\ModelTrait;
|
||||||
|
|
||||||
|
class PromoteTaskRecord extends BaseModel
|
||||||
|
{
|
||||||
|
use ModelTrait;
|
||||||
|
|
||||||
|
protected $pk = 'id';
|
||||||
|
|
||||||
|
protected $name = 'syj_promote_task_record';
|
||||||
|
|
||||||
|
protected $autoWriteTimestamp = 'int';
|
||||||
|
|
||||||
|
protected $createTime = 'add_time';
|
||||||
|
|
||||||
|
protected $updateTime = 'update_time';
|
||||||
|
}
|
||||||
22
pro_v3.5.1/app/model/syj/PromoteUserAmount.php
Normal file
22
pro_v3.5.1/app/model/syj/PromoteUserAmount.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\model\syj;
|
||||||
|
|
||||||
|
use crmeb\basic\BaseModel;
|
||||||
|
use crmeb\traits\ModelTrait;
|
||||||
|
|
||||||
|
class PromoteUserAmount extends BaseModel
|
||||||
|
{
|
||||||
|
use ModelTrait;
|
||||||
|
|
||||||
|
protected $pk = 'id';
|
||||||
|
|
||||||
|
protected $name = 'syj_promote_user_amount';
|
||||||
|
|
||||||
|
protected $autoWriteTimestamp = 'int';
|
||||||
|
|
||||||
|
protected $createTime = 'add_time';
|
||||||
|
|
||||||
|
protected $updateTime = 'update_time';
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ use app\services\hjf\PointsRewardServices;
|
|||||||
use app\services\user\UserBillServices;
|
use app\services\user\UserBillServices;
|
||||||
use app\services\user\UserBrokerageServices;
|
use app\services\user\UserBrokerageServices;
|
||||||
use app\services\user\UserServices;
|
use app\services\user\UserServices;
|
||||||
|
use app\services\syj\SyjPromoteTaskServices;
|
||||||
use think\annotation\Inject;
|
use think\annotation\Inject;
|
||||||
use think\exception\ValidateException;
|
use think\exception\ValidateException;
|
||||||
use think\facade\Log;
|
use think\facade\Log;
|
||||||
@@ -148,6 +149,17 @@ class StoreOrderTakeServices extends BaseServices
|
|||||||
}, $isTran);
|
}, $isTran);
|
||||||
}
|
}
|
||||||
if ($res) {
|
if ($res) {
|
||||||
|
if (!empty($order['is_queue_goods'])) {
|
||||||
|
try {
|
||||||
|
/** @var SyjPromoteTaskServices $syjServices */
|
||||||
|
$syjServices = app()->make(SyjPromoteTaskServices::class);
|
||||||
|
$orderData = is_array($order) ? $order : $order->toArray();
|
||||||
|
$syjServices->handleOrderEffective($orderData, 'order_confirm');
|
||||||
|
$syjServices->handleRecommendedOrder($orderData, 'order_confirm');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
\think\facade\Log::error('[SYJ] 确认收货处理失败 order_id=' . ($order['id'] ?? 0) . ': ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
//订单收货事件
|
//订单收货事件
|
||||||
event('order.take', [$order, $storeTitle, $isRecord]);
|
event('order.take', [$order, $storeTitle, $isRecord]);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
100
pro_v3.5.1/app/services/syj/SyjPromoteConfigServices.php
Normal file
100
pro_v3.5.1/app/services/syj/SyjPromoteConfigServices.php
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\services\syj;
|
||||||
|
|
||||||
|
use app\services\BaseServices;
|
||||||
|
use app\services\system\config\SystemConfigServices;
|
||||||
|
use crmeb\services\SystemConfigService;
|
||||||
|
use think\exception\ValidateException;
|
||||||
|
|
||||||
|
class SyjPromoteConfigServices extends BaseServices
|
||||||
|
{
|
||||||
|
public function getConfig(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'base_amount' => $this->money('syj_task_base_amount', '4333'),
|
||||||
|
'target_count' => (int)SystemConfigService::get('syj_task_target_count', 4),
|
||||||
|
'reward_rates' => $this->rewardRates(),
|
||||||
|
'early_cashout_fee_rate' => $this->money('syj_early_cashout_fee_rate', '7'),
|
||||||
|
'task_generate_timing' => (string)SystemConfigService::get('syj_task_generate_timing', 'on_confirm'),
|
||||||
|
'task_order_dedupe' => (string)SystemConfigService::get('syj_task_order_dedupe', 'order'),
|
||||||
|
'reward_trigger_enable' => (int)SystemConfigService::get('syj_reward_trigger_enable', 1),
|
||||||
|
'brokerage_timing' => (string)SystemConfigService::get('brokerage_timing', 'on_confirm'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveConfig(array $data): void
|
||||||
|
{
|
||||||
|
$baseAmount = (float)($data['base_amount'] ?? 0);
|
||||||
|
$targetCount = (int)($data['target_count'] ?? 0);
|
||||||
|
$rates = $data['reward_rates'] ?? [];
|
||||||
|
if (is_string($rates)) {
|
||||||
|
$rates = json_decode($rates, true) ?: [];
|
||||||
|
}
|
||||||
|
$feeRate = (float)($data['early_cashout_fee_rate'] ?? -1);
|
||||||
|
$generateTiming = (string)($data['task_generate_timing'] ?? 'on_confirm');
|
||||||
|
|
||||||
|
if ($baseAmount <= 0) {
|
||||||
|
throw new ValidateException('任务基准金额必须大于0');
|
||||||
|
}
|
||||||
|
if ($targetCount <= 0) {
|
||||||
|
throw new ValidateException('目标单数必须大于0');
|
||||||
|
}
|
||||||
|
if (count($rates) !== $targetCount) {
|
||||||
|
throw new ValidateException('奖励比例数量必须与目标单数一致');
|
||||||
|
}
|
||||||
|
foreach ($rates as $rate) {
|
||||||
|
if ((float)$rate < 0 || (float)$rate > 100) {
|
||||||
|
throw new ValidateException('奖励比例必须在0到100之间');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($feeRate < 0 || $feeRate > 100) {
|
||||||
|
throw new ValidateException('扣费比例必须在0到100之间');
|
||||||
|
}
|
||||||
|
if (!in_array($generateTiming, ['on_confirm', 'on_pay'], true)) {
|
||||||
|
throw new ValidateException('任务生成节点不正确');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var SystemConfigServices $configServices */
|
||||||
|
$configServices = app()->make(SystemConfigServices::class);
|
||||||
|
$map = [
|
||||||
|
'syj_task_base_amount' => $this->formatMoney($baseAmount),
|
||||||
|
'syj_task_target_count' => (string)$targetCount,
|
||||||
|
'syj_task_reward_rates' => json_encode(array_values(array_map('floatval', $rates)), JSON_UNESCAPED_UNICODE),
|
||||||
|
'syj_early_cashout_fee_rate' => $this->formatMoney($feeRate),
|
||||||
|
'syj_task_generate_timing' => $generateTiming,
|
||||||
|
'syj_task_order_dedupe' => (string)($data['task_order_dedupe'] ?? 'order'),
|
||||||
|
'syj_reward_trigger_enable' => (string)(int)($data['reward_trigger_enable'] ?? 1),
|
||||||
|
];
|
||||||
|
foreach ($map as $key => $value) {
|
||||||
|
$configServices->setConfig($key, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rewardRates(): array
|
||||||
|
{
|
||||||
|
$value = SystemConfigService::get('syj_task_reward_rates', '[10,20,30,40]');
|
||||||
|
if (is_array($value)) {
|
||||||
|
return array_values(array_map('floatval', $value));
|
||||||
|
}
|
||||||
|
$decoded = json_decode((string)$value, true);
|
||||||
|
return is_array($decoded) ? array_values(array_map('floatval', $decoded)) : [10, 20, 30, 40];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rateForStep(int $step): float
|
||||||
|
{
|
||||||
|
$rates = $this->rewardRates();
|
||||||
|
return (float)($rates[$step - 1] ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function money(string $key, string $default): string
|
||||||
|
{
|
||||||
|
return $this->formatMoney((float)SystemConfigService::get($key, $default));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function formatMoney(float|string $number): string
|
||||||
|
{
|
||||||
|
return number_format((float)$number, 2, '.', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
104
pro_v3.5.1/app/services/syj/SyjPromoteRewardTriggerServices.php
Normal file
104
pro_v3.5.1/app/services/syj/SyjPromoteRewardTriggerServices.php
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\services\syj;
|
||||||
|
|
||||||
|
use app\dao\syj\PromoteRewardTriggerDao;
|
||||||
|
use app\services\BaseServices;
|
||||||
|
use app\services\agent\AgentLevelServices;
|
||||||
|
use app\services\hjf\PointsRewardServices;
|
||||||
|
use app\services\user\UserServices;
|
||||||
|
use think\annotation\Inject;
|
||||||
|
use think\facade\Log;
|
||||||
|
|
||||||
|
class SyjPromoteRewardTriggerServices extends BaseServices
|
||||||
|
{
|
||||||
|
#[Inject]
|
||||||
|
protected PromoteRewardTriggerDao $dao;
|
||||||
|
|
||||||
|
public function triggerForTask(array $task): void
|
||||||
|
{
|
||||||
|
if ((int)sys_config('syj_reward_trigger_enable', 1) !== 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$taskId = (int)$task['id'];
|
||||||
|
$triggerNo = $task['reward_trigger_no'] ?: 'SYJ-TASK-' . $taskId;
|
||||||
|
if (!$this->dao->be(['task_id' => $taskId])) {
|
||||||
|
$this->dao->save([
|
||||||
|
'task_id' => $taskId,
|
||||||
|
'uid' => (int)$task['uid'],
|
||||||
|
'trigger_no' => $triggerNo,
|
||||||
|
'trigger_amount' => $task['base_amount'],
|
||||||
|
'brokerage_status' => 0,
|
||||||
|
'points_status' => 0,
|
||||||
|
'level_task_status' => 0,
|
||||||
|
'error_msg' => '',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$status = [
|
||||||
|
'points_status' => 1,
|
||||||
|
'level_task_status' => 1,
|
||||||
|
'brokerage_status' => 1,
|
||||||
|
'error_msg' => '',
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
/** @var UserServices $userServices */
|
||||||
|
$userServices = app()->make(UserServices::class);
|
||||||
|
$uids = [(int)$task['uid']];
|
||||||
|
$user = $userServices->get((int)$task['uid'], ['uid', 'spread_uid', 'agent_level']);
|
||||||
|
$spreadUid = $user ? (int)($user['spread_uid'] ?? 0) : 0;
|
||||||
|
if ($spreadUid > 0) {
|
||||||
|
$uids[] = $spreadUid;
|
||||||
|
$spreadUser = $userServices->get($spreadUid, ['uid', 'spread_uid', 'agent_level']);
|
||||||
|
$twoUid = $spreadUser ? (int)($spreadUser['spread_uid'] ?? 0) : 0;
|
||||||
|
if ($twoUid > 0) {
|
||||||
|
$uids[] = $twoUid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$preUpgradeLevels = [];
|
||||||
|
foreach (array_unique($uids) as $uid) {
|
||||||
|
if ($uid <= 0) continue;
|
||||||
|
$row = $userServices->get((int)$uid, ['uid', 'agent_level']);
|
||||||
|
$preUpgradeLevels[(int)$uid] = $row ? (int)($row['agent_level'] ?? 0) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var AgentLevelServices $agentLevelServices */
|
||||||
|
$agentLevelServices = app()->make(AgentLevelServices::class);
|
||||||
|
$agentLevelServices->checkUserLevelFinish((int)$task['uid'], array_keys($preUpgradeLevels));
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$status['level_task_status'] = 2;
|
||||||
|
$status['error_msg'] = '等级任务触发失败:' . $e->getMessage();
|
||||||
|
Log::error('[SYJ] 等级任务触发失败 task=' . $taskId . ' ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
/** @var PointsRewardServices $pointsRewardServices */
|
||||||
|
$pointsRewardServices = app()->make(PointsRewardServices::class);
|
||||||
|
$pointsRewardServices->reward((int)$task['uid'], $triggerNo, (int)$task['source_order_id'], $preUpgradeLevels ?? [], 1);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$status['points_status'] = 2;
|
||||||
|
$status['error_msg'] = trim($status['error_msg'] . ' 积分触发失败:' . $e->getMessage());
|
||||||
|
Log::error('[SYJ] 积分触发失败 task=' . $taskId . ' ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->dao->update(['task_id' => $taskId], $status + ['retry_count' => $this->dao->value(['task_id' => $taskId], 'retry_count')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function retry(int $taskId): void
|
||||||
|
{
|
||||||
|
/** @var SyjPromoteTaskServices $taskServices */
|
||||||
|
$taskServices = app()->make(SyjPromoteTaskServices::class);
|
||||||
|
$task = $taskServices->getTask($taskId);
|
||||||
|
if (!$task) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$row = $this->dao->getOne(['task_id' => $taskId]);
|
||||||
|
if ($row) {
|
||||||
|
$this->dao->update(['task_id' => $taskId], ['retry_count' => (int)$row['retry_count'] + 1]);
|
||||||
|
}
|
||||||
|
$this->triggerForTask($task);
|
||||||
|
$taskServices->refreshRewardStatus($taskId);
|
||||||
|
}
|
||||||
|
}
|
||||||
168
pro_v3.5.1/app/services/syj/SyjPromoteSettlementServices.php
Normal file
168
pro_v3.5.1/app/services/syj/SyjPromoteSettlementServices.php
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\services\syj;
|
||||||
|
|
||||||
|
use app\dao\syj\PromoteSettlementDao;
|
||||||
|
use app\dao\syj\PromoteTaskDao;
|
||||||
|
use app\services\BaseServices;
|
||||||
|
use app\services\user\UserBrokerageServices;
|
||||||
|
use app\services\user\UserServices;
|
||||||
|
use think\annotation\Inject;
|
||||||
|
use think\exception\ValidateException;
|
||||||
|
use think\facade\Db;
|
||||||
|
|
||||||
|
class SyjPromoteSettlementServices extends BaseServices
|
||||||
|
{
|
||||||
|
#[Inject]
|
||||||
|
protected PromoteSettlementDao $dao;
|
||||||
|
|
||||||
|
#[Inject]
|
||||||
|
protected PromoteTaskDao $taskDao;
|
||||||
|
|
||||||
|
public function complete(array $task): array
|
||||||
|
{
|
||||||
|
return Db::transaction(function () use ($task) {
|
||||||
|
$taskId = (int)$task['id'];
|
||||||
|
if ($this->dao->be(['task_id' => $taskId, 'settle_type' => 'complete'])) {
|
||||||
|
return $this->dao->getOne(['task_id' => $taskId, 'settle_type' => 'complete'])->toArray();
|
||||||
|
}
|
||||||
|
$gross = $this->money($task['base_amount']);
|
||||||
|
$settlement = $this->dao->save([
|
||||||
|
'task_id' => $taskId,
|
||||||
|
'uid' => (int)$task['uid'],
|
||||||
|
'settle_type' => 'complete',
|
||||||
|
'gross_amount' => $gross,
|
||||||
|
'fee_rate' => '0.00',
|
||||||
|
'fee_amount' => '0.00',
|
||||||
|
'net_amount' => $gross,
|
||||||
|
'audit_status' => 1,
|
||||||
|
'audit_time' => time(),
|
||||||
|
])->toArray();
|
||||||
|
$brokerageId = $this->grantBrokerage((int)$task['uid'], $gross, $taskId, '推四免一任务完成结算,任务号:' . $task['task_no']);
|
||||||
|
$this->dao->update((int)$settlement['id'], ['brokerage_id' => $brokerageId]);
|
||||||
|
$this->taskDao->update($taskId, ['status' => 1, 'finish_time' => time()]);
|
||||||
|
return $settlement;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyCashout(int $uid, int $taskId): array
|
||||||
|
{
|
||||||
|
return Db::transaction(function () use ($uid, $taskId) {
|
||||||
|
$task = $this->taskDao->get($taskId);
|
||||||
|
if (!$task || (int)$task['uid'] !== $uid) {
|
||||||
|
throw new ValidateException('推广任务不存在');
|
||||||
|
}
|
||||||
|
if ((int)$task['status'] !== 0) {
|
||||||
|
throw new ValidateException('当前任务状态不可提前兑现');
|
||||||
|
}
|
||||||
|
$progress = (int)$task['progress_count'];
|
||||||
|
if ($progress < 1 || $progress >= (int)$task['target_count']) {
|
||||||
|
throw new ValidateException('当前进度不可提前兑现');
|
||||||
|
}
|
||||||
|
if ($this->dao->be(['task_id' => $taskId, 'settle_type' => 'early_cashout'])) {
|
||||||
|
throw new ValidateException('已提交提前兑现申请');
|
||||||
|
}
|
||||||
|
[$gross, $fee, $net] = $this->calcEarlyCashout($task->toArray());
|
||||||
|
$settlement = $this->dao->save([
|
||||||
|
'task_id' => $taskId,
|
||||||
|
'uid' => $uid,
|
||||||
|
'settle_type' => 'early_cashout',
|
||||||
|
'gross_amount' => $gross,
|
||||||
|
'fee_rate' => $this->money($task['fee_rate']),
|
||||||
|
'fee_amount' => $fee,
|
||||||
|
'net_amount' => $net,
|
||||||
|
'audit_status' => 0,
|
||||||
|
])->toArray();
|
||||||
|
$this->taskDao->update($taskId, ['status' => 5]);
|
||||||
|
return $settlement;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function auditCashout(int $settlementId, int $auditUid, int $status, string $remark = ''): void
|
||||||
|
{
|
||||||
|
Db::transaction(function () use ($settlementId, $auditUid, $status, $remark) {
|
||||||
|
$settlement = $this->dao->get($settlementId);
|
||||||
|
if (!$settlement || $settlement['settle_type'] !== 'early_cashout') {
|
||||||
|
throw new ValidateException('提前兑现申请不存在');
|
||||||
|
}
|
||||||
|
if ((int)$settlement['audit_status'] !== 0) {
|
||||||
|
throw new ValidateException('申请已审核');
|
||||||
|
}
|
||||||
|
$task = $this->taskDao->get((int)$settlement['task_id']);
|
||||||
|
if (!$task || (int)$task['status'] !== 5) {
|
||||||
|
throw new ValidateException('任务状态不可审核');
|
||||||
|
}
|
||||||
|
if ($status === 1) {
|
||||||
|
$brokerageId = $this->grantBrokerage((int)$settlement['uid'], (string)$settlement['net_amount'], (int)$task['id'], '推四免一提前兑现到账,任务号:' . $task['task_no']);
|
||||||
|
$this->dao->update($settlementId, [
|
||||||
|
'audit_status' => 1,
|
||||||
|
'audit_uid' => $auditUid,
|
||||||
|
'audit_remark' => $remark,
|
||||||
|
'audit_time' => time(),
|
||||||
|
'brokerage_id' => $brokerageId,
|
||||||
|
]);
|
||||||
|
$this->taskDao->update((int)$task['id'], ['status' => 2, 'cashout_time' => time()]);
|
||||||
|
} elseif ($status === 2) {
|
||||||
|
$this->dao->update($settlementId, [
|
||||||
|
'audit_status' => 2,
|
||||||
|
'audit_uid' => $auditUid,
|
||||||
|
'audit_remark' => $remark,
|
||||||
|
'audit_time' => time(),
|
||||||
|
]);
|
||||||
|
$this->taskDao->update((int)$task['id'], ['status' => 0]);
|
||||||
|
} else {
|
||||||
|
throw new ValidateException('审核状态不正确');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function listForUser(int $uid, int $page, int $limit): array
|
||||||
|
{
|
||||||
|
return $this->dao->getUserList($uid, $page, $limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function adminList(array $where, int $page, int $limit): array
|
||||||
|
{
|
||||||
|
return $this->dao->getAdminList($where, $page, $limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function calcEarlyCashout(array $task): array
|
||||||
|
{
|
||||||
|
$rates = json_decode((string)$task['reward_rates'], true);
|
||||||
|
if (!is_array($rates)) {
|
||||||
|
$rates = [10, 20, 30, 40];
|
||||||
|
}
|
||||||
|
$step = max(1, (int)$task['progress_count']);
|
||||||
|
$rate = (float)($rates[$step - 1] ?? 0);
|
||||||
|
$gross = bcmul((string)$task['base_amount'], bcdiv((string)$rate, '100', 4), 2);
|
||||||
|
$fee = bcmul($gross, bcdiv((string)$task['fee_rate'], '100', 4), 2);
|
||||||
|
$net = bcsub($gross, $fee, 2);
|
||||||
|
return [$gross, $fee, $net];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function grantBrokerage(int $uid, string $amount, int $taskId, string $mark): int
|
||||||
|
{
|
||||||
|
/** @var UserServices $userServices */
|
||||||
|
$userServices = app()->make(UserServices::class);
|
||||||
|
$current = (string)($userServices->value(['uid' => $uid], 'brokerage_price') ?: '0');
|
||||||
|
$balance = bcadd($current, $amount, 2);
|
||||||
|
$userServices->bcInc($uid, 'brokerage_price', $amount, 'uid');
|
||||||
|
|
||||||
|
/** @var UserBrokerageServices $brokerageServices */
|
||||||
|
$brokerageServices = app()->make(UserBrokerageServices::class);
|
||||||
|
$row = $brokerageServices->income('syj_promote_settlement', $uid, [
|
||||||
|
'number' => $amount,
|
||||||
|
'mark' => $mark,
|
||||||
|
], $balance, $taskId);
|
||||||
|
if ($row && method_exists($row, 'getData')) {
|
||||||
|
return (int)$row->getData('id');
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function money(float|string $amount): string
|
||||||
|
{
|
||||||
|
return number_format((float)$amount, 2, '.', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
309
pro_v3.5.1/app/services/syj/SyjPromoteTaskServices.php
Normal file
309
pro_v3.5.1/app/services/syj/SyjPromoteTaskServices.php
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\services\syj;
|
||||||
|
|
||||||
|
use app\dao\syj\PromoteAmountLogDao;
|
||||||
|
use app\dao\syj\PromoteRewardTriggerDao;
|
||||||
|
use app\dao\syj\PromoteSettlementDao;
|
||||||
|
use app\dao\syj\PromoteTaskDao;
|
||||||
|
use app\dao\syj\PromoteTaskRecordDao;
|
||||||
|
use app\dao\syj\PromoteUserAmountDao;
|
||||||
|
use app\services\BaseServices;
|
||||||
|
use think\annotation\Inject;
|
||||||
|
use think\exception\ValidateException;
|
||||||
|
use think\facade\Db;
|
||||||
|
use think\facade\Log;
|
||||||
|
|
||||||
|
class SyjPromoteTaskServices extends BaseServices
|
||||||
|
{
|
||||||
|
#[Inject]
|
||||||
|
protected PromoteTaskDao $taskDao;
|
||||||
|
|
||||||
|
#[Inject]
|
||||||
|
protected PromoteUserAmountDao $userAmountDao;
|
||||||
|
|
||||||
|
#[Inject]
|
||||||
|
protected PromoteAmountLogDao $amountLogDao;
|
||||||
|
|
||||||
|
#[Inject]
|
||||||
|
protected PromoteTaskRecordDao $recordDao;
|
||||||
|
|
||||||
|
#[Inject]
|
||||||
|
protected PromoteSettlementDao $settlementDao;
|
||||||
|
|
||||||
|
#[Inject]
|
||||||
|
protected PromoteRewardTriggerDao $triggerDao;
|
||||||
|
|
||||||
|
public function handleOrderEffective(array $order, string $sourceType = 'order_confirm'): void
|
||||||
|
{
|
||||||
|
if (!$this->isQueueOrder($order) || empty($order['uid'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$generateTiming = (string)sys_config('syj_task_generate_timing', 'on_confirm');
|
||||||
|
if (($generateTiming === 'on_confirm' && $sourceType !== 'order_confirm') || ($generateTiming === 'on_pay' && $sourceType !== 'order_pay')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Db::transaction(function () use ($order) {
|
||||||
|
$orderId = (int)$order['id'];
|
||||||
|
if ($this->amountLogDao->be(['order_id' => $orderId, 'type' => 'income'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$uid = (int)$order['uid'];
|
||||||
|
$amount = $this->validAmount($order);
|
||||||
|
if (bccomp($amount, '0', 2) <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$summary = $this->lockUserAmount($uid);
|
||||||
|
$before = (string)$summary['pending_amount'];
|
||||||
|
$afterIncome = bcadd($before, $amount, 2);
|
||||||
|
$config = app()->make(SyjPromoteConfigServices::class)->getConfig();
|
||||||
|
$base = $config['base_amount'];
|
||||||
|
$taskCount = (int)floor((float)bcdiv($afterIncome, $base, 4));
|
||||||
|
$remaining = bcsub($afterIncome, bcmul((string)$taskCount, $base, 2), 2);
|
||||||
|
|
||||||
|
$this->amountLogDao->save([
|
||||||
|
'uid' => $uid,
|
||||||
|
'order_id' => $orderId,
|
||||||
|
'order_no' => (string)$order['order_id'],
|
||||||
|
'amount' => $amount,
|
||||||
|
'before_amount' => $before,
|
||||||
|
'after_amount' => $afterIncome,
|
||||||
|
'type' => 'income',
|
||||||
|
'mark' => '有效消费计入待推广金额',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->userAmountDao->update((int)$summary['id'], ['pending_amount' => $remaining]);
|
||||||
|
|
||||||
|
for ($i = 1; $i <= $taskCount; $i++) {
|
||||||
|
$task = $this->createTask($uid, $order, $i, $config);
|
||||||
|
$this->amountLogDao->save([
|
||||||
|
'uid' => $uid,
|
||||||
|
'order_id' => $orderId,
|
||||||
|
'order_no' => (string)$order['order_id'],
|
||||||
|
'amount' => '-' . $base,
|
||||||
|
'before_amount' => $afterIncome,
|
||||||
|
'after_amount' => $remaining,
|
||||||
|
'type' => 'consume_task_' . $i,
|
||||||
|
'link_id' => (int)$task['id'],
|
||||||
|
'mark' => '生成推四免一推广任务',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleRecommendedOrder(array $order, string $sourceType): void
|
||||||
|
{
|
||||||
|
if (!$this->isQueueOrder($order) || empty($order['spread_uid'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$timing = (string)sys_config('brokerage_timing', 'on_confirm');
|
||||||
|
if (($timing === 'on_pay' && $sourceType !== 'order_pay') || ($timing === 'on_confirm' && $sourceType !== 'order_confirm')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Db::transaction(function () use ($order, $sourceType, $timing) {
|
||||||
|
$orderId = (int)$order['id'];
|
||||||
|
if ($this->recordDao->be(['order_id' => $orderId])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$task = $this->taskDao->getEarliestActiveTask((int)$order['spread_uid']);
|
||||||
|
if (!$task) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$step = (int)$task['progress_count'] + 1;
|
||||||
|
$rates = json_decode((string)$task['reward_rates'], true);
|
||||||
|
$rate = (float)($rates[$step - 1] ?? 0);
|
||||||
|
$this->recordDao->save([
|
||||||
|
'task_id' => (int)$task['id'],
|
||||||
|
'uid' => (int)$task['uid'],
|
||||||
|
'order_uid' => (int)$order['uid'],
|
||||||
|
'order_id' => $orderId,
|
||||||
|
'order_no' => (string)$order['order_id'],
|
||||||
|
'source_type' => $sourceType,
|
||||||
|
'trigger_timing' => $timing,
|
||||||
|
'step_no' => $step,
|
||||||
|
'reward_rate' => $rate,
|
||||||
|
'status' => 1,
|
||||||
|
]);
|
||||||
|
$this->taskDao->update((int)$task['id'], ['progress_count' => $step]);
|
||||||
|
$task['progress_count'] = $step;
|
||||||
|
if ($step >= (int)$task['target_count']) {
|
||||||
|
app()->make(SyjPromoteSettlementServices::class)->complete($task);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleRefund(array $order, array $refundData = []): void
|
||||||
|
{
|
||||||
|
Db::transaction(function () use ($order, $refundData) {
|
||||||
|
$orderId = (int)$order['id'];
|
||||||
|
$sourceTasks = $this->taskDao->getColumn(['source_order_id' => $orderId], 'id,status', 'id');
|
||||||
|
if ($sourceTasks) {
|
||||||
|
foreach ($sourceTasks as $taskId => $status) {
|
||||||
|
if ((int)$status === 0) {
|
||||||
|
$this->taskDao->update((int)$taskId, ['status' => 4, 'exception_reason' => '来源订单退款']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$record = $this->recordDao->getOne(['order_id' => $orderId, 'status' => 1]);
|
||||||
|
if ($record) {
|
||||||
|
$task = $this->taskDao->get((int)$record['task_id']);
|
||||||
|
$this->recordDao->update((int)$record['id'], ['status' => 2]);
|
||||||
|
if ($task && (int)$task['status'] === 0) {
|
||||||
|
$this->taskDao->update((int)$task['id'], ['progress_count' => max(0, (int)$task['progress_count'] - 1)]);
|
||||||
|
} elseif ($task) {
|
||||||
|
$this->taskDao->update((int)$task['id'], ['status' => 4, 'exception_reason' => '推荐订单退款,需人工复核']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function overview(int $uid): array
|
||||||
|
{
|
||||||
|
$summary = $this->userAmountDao->getOne(['uid' => $uid]);
|
||||||
|
$pending = $summary ? (string)$summary['pending_amount'] : '0.00';
|
||||||
|
$base = (string)sys_config('syj_task_base_amount', '4333');
|
||||||
|
return [
|
||||||
|
'pending_amount' => $this->money($pending),
|
||||||
|
'base_amount' => $this->money($base),
|
||||||
|
'active_task_count' => $this->taskDao->count(['uid' => $uid, 'status' => 0]),
|
||||||
|
'completed_task_count' => $this->taskDao->count(['uid' => $uid, 'status' => 1]),
|
||||||
|
'cashout_task_count' => $this->taskDao->count(['uid' => $uid, 'status' => 2]),
|
||||||
|
'available_cashout_amount' => $this->availableCashoutAmount($uid),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function userTasks(int $uid, array $where, int $page, int $limit): array
|
||||||
|
{
|
||||||
|
return $this->taskDao->getUserList($uid, $where, $page, $limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function adminTasks(array $where, int $page, int $limit): array
|
||||||
|
{
|
||||||
|
return $this->taskDao->getAdminList($where, $page, $limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTask(int $taskId): ?array
|
||||||
|
{
|
||||||
|
$row = $this->taskDao->get($taskId);
|
||||||
|
return $row ? $row->toArray() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function userTaskDetail(int $uid, int $taskId): array
|
||||||
|
{
|
||||||
|
$task = $this->getTask($taskId);
|
||||||
|
if (!$task || (int)$task['uid'] !== $uid) {
|
||||||
|
throw new ValidateException('推广任务不存在');
|
||||||
|
}
|
||||||
|
return $this->decorateTask($task);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function adminTaskDetail(int $taskId): array
|
||||||
|
{
|
||||||
|
$task = $this->getTask($taskId);
|
||||||
|
if (!$task) {
|
||||||
|
throw new ValidateException('推广任务不存在');
|
||||||
|
}
|
||||||
|
return $this->decorateTask($task);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function records(int $taskId, int $uid = 0): array
|
||||||
|
{
|
||||||
|
return $this->recordDao->getTaskRecords($taskId, $uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function amountLogs(int $uid, int $page, int $limit): array
|
||||||
|
{
|
||||||
|
return $this->amountLogDao->getUserList($uid, $page, $limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refreshRewardStatus(int $taskId): void
|
||||||
|
{
|
||||||
|
$trigger = $this->triggerDao->getOne(['task_id' => $taskId]);
|
||||||
|
if (!$trigger) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$failed = in_array(2, [(int)$trigger['points_status'], (int)$trigger['level_task_status'], (int)$trigger['brokerage_status']], true);
|
||||||
|
$success = (int)$trigger['points_status'] === 1 && (int)$trigger['level_task_status'] === 1 && (int)$trigger['brokerage_status'] === 1;
|
||||||
|
$this->taskDao->update($taskId, ['reward_trigger_status' => $success ? 1 : ($failed ? 2 : 3)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createTask(int $uid, array $order, int $splitIndex, array $config): array
|
||||||
|
{
|
||||||
|
$taskNo = 'SYJ' . date('YmdHis') . $uid . str_pad((string)$splitIndex, 2, '0', STR_PAD_LEFT) . mt_rand(100, 999);
|
||||||
|
$row = $this->taskDao->save([
|
||||||
|
'uid' => $uid,
|
||||||
|
'task_no' => $taskNo,
|
||||||
|
'source_order_id' => (int)$order['id'],
|
||||||
|
'source_order_no' => (string)$order['order_id'],
|
||||||
|
'source_split_index' => $splitIndex,
|
||||||
|
'base_amount' => $config['base_amount'],
|
||||||
|
'reward_rates' => json_encode($config['reward_rates'], JSON_UNESCAPED_UNICODE),
|
||||||
|
'fee_rate' => $config['early_cashout_fee_rate'],
|
||||||
|
'status' => 0,
|
||||||
|
'progress_count' => 0,
|
||||||
|
'target_count' => $config['target_count'],
|
||||||
|
'reward_trigger_status' => 3,
|
||||||
|
'reward_trigger_no' => $taskNo,
|
||||||
|
])->toArray();
|
||||||
|
try {
|
||||||
|
app()->make(SyjPromoteRewardTriggerServices::class)->triggerForTask($row);
|
||||||
|
$this->refreshRewardStatus((int)$row['id']);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::error('[SYJ] 任务奖励触发异常 task=' . $row['id'] . ' ' . $e->getMessage());
|
||||||
|
$this->taskDao->update((int)$row['id'], ['reward_trigger_status' => 2, 'exception_reason' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
return $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function lockUserAmount(int $uid): array
|
||||||
|
{
|
||||||
|
$row = $this->userAmountDao->lockByUid($uid);
|
||||||
|
if (!$row) {
|
||||||
|
$this->userAmountDao->save(['uid' => $uid, 'pending_amount' => '0.00']);
|
||||||
|
$row = $this->userAmountDao->lockByUid($uid);
|
||||||
|
}
|
||||||
|
return $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isQueueOrder(array $order): bool
|
||||||
|
{
|
||||||
|
return !empty($order['is_queue_goods']) && empty($order['is_del']) && empty($order['is_system_del']) && (int)($order['paid'] ?? 1) === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validAmount(array $order): string
|
||||||
|
{
|
||||||
|
$pay = (string)($order['pay_price'] ?? '0');
|
||||||
|
$refund = (string)($order['refund_price'] ?? '0');
|
||||||
|
$amount = bcsub($pay, $refund, 2);
|
||||||
|
return bccomp($amount, '0', 2) > 0 ? $amount : '0.00';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function decorateTask(array $task): array
|
||||||
|
{
|
||||||
|
$task['records'] = $this->recordDao->getTaskRecords((int)$task['id']);
|
||||||
|
$task['settlement'] = ($this->settlementDao->getOne(['task_id' => (int)$task['id']]) ?: null)?->toArray();
|
||||||
|
$task['reward_trigger'] = ($this->triggerDao->getOne(['task_id' => (int)$task['id']]) ?: null)?->toArray();
|
||||||
|
return $task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function availableCashoutAmount(int $uid): string
|
||||||
|
{
|
||||||
|
$tasks = $this->taskDao->getCashoutAvailableTasks($uid);
|
||||||
|
$total = '0.00';
|
||||||
|
foreach ($tasks as $task) {
|
||||||
|
$rates = json_decode((string)$task['reward_rates'], true) ?: [10, 20, 30, 40];
|
||||||
|
$rate = (float)($rates[((int)$task['progress_count']) - 1] ?? 0);
|
||||||
|
$gross = bcmul((string)$task['base_amount'], bcdiv((string)$rate, '100', 4), 2);
|
||||||
|
$fee = bcmul($gross, bcdiv((string)$task['fee_rate'], '100', 4), 2);
|
||||||
|
$total = bcadd($total, bcsub($gross, $fee, 2), 2);
|
||||||
|
}
|
||||||
|
return $total;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function money(float|string $amount): string
|
||||||
|
{
|
||||||
|
return number_format((float)$amount, 2, '.', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -324,6 +324,25 @@ class SystemConfigServices extends BaseServices implements ServeConfigInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新单个系统配置值
|
||||||
|
* @param string $configName
|
||||||
|
* @param mixed $value
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function setConfig(string $configName, $value): bool
|
||||||
|
{
|
||||||
|
$config = $this->dao->getOne(['menu_name' => $configName]);
|
||||||
|
if (!$config) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$config['value'] = $value;
|
||||||
|
$this->valiDateValue($config);
|
||||||
|
$this->dao->update($configName, ['value' => json_encode($value)], 'menu_name');
|
||||||
|
\crmeb\services\SystemConfigService::clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取配置并分页
|
* 获取配置并分页
|
||||||
* @param array $where
|
* @param array $where
|
||||||
|
|||||||
@@ -95,6 +95,13 @@ class UserBrokerageServices extends BaseServices
|
|||||||
'status' => 1,
|
'status' => 1,
|
||||||
'pm' => 0
|
'pm' => 0
|
||||||
],
|
],
|
||||||
|
'syj_promote_settlement' => [
|
||||||
|
'title' => '芍药居推广任务结算',
|
||||||
|
'type' => 'syj_promote_settlement',
|
||||||
|
'mark' => '{%mark%}',
|
||||||
|
'status' => 1,
|
||||||
|
'pm' => 1
|
||||||
|
],
|
||||||
'get_staff_brokerage' => [
|
'get_staff_brokerage' => [
|
||||||
'title' => '获得员工推广订单佣金',
|
'title' => '获得员工推广订单佣金',
|
||||||
'type' => 'staff_brokerage',
|
'type' => 'staff_brokerage',
|
||||||
|
|||||||
145
pro_v3.5.1/database/syj_migration.sql
Normal file
145
pro_v3.5.1/database/syj_migration.sql
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
-- 芍药居推四免一任务迁移
|
||||||
|
-- 执行前请确认当前库前缀为 eb_,并已备份生产数据。
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `eb_syj_promote_user_amount` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`uid` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID',
|
||||||
|
`pending_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '当前待推广金额',
|
||||||
|
`add_time` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`update_time` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uniq_uid` (`uid`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='芍药居用户待推广金额汇总';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `eb_syj_promote_task` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`uid` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`task_no` varchar(64) NOT NULL DEFAULT '',
|
||||||
|
`source_order_id` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`source_order_no` varchar(64) NOT NULL DEFAULT '',
|
||||||
|
`source_split_index` int(10) unsigned NOT NULL DEFAULT '1',
|
||||||
|
`base_amount` decimal(10,2) NOT NULL DEFAULT '4333.00',
|
||||||
|
`reward_rates` varchar(255) NOT NULL DEFAULT '',
|
||||||
|
`fee_rate` decimal(5,2) NOT NULL DEFAULT '7.00',
|
||||||
|
`status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '0进行中 1已完成 2提前兑现 3已关闭 4异常 5审核中',
|
||||||
|
`progress_count` tinyint(3) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`target_count` tinyint(3) unsigned NOT NULL DEFAULT '4',
|
||||||
|
`reward_trigger_status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '0未触发 1成功 2失败 3处理中',
|
||||||
|
`reward_trigger_no` varchar(64) NOT NULL DEFAULT '',
|
||||||
|
`exception_reason` varchar(500) NOT NULL DEFAULT '',
|
||||||
|
`finish_time` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`cashout_time` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`add_time` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`update_time` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uniq_task_no` (`task_no`),
|
||||||
|
UNIQUE KEY `uniq_source_split` (`source_order_id`,`source_split_index`),
|
||||||
|
UNIQUE KEY `uniq_reward_trigger_no` (`reward_trigger_no`),
|
||||||
|
KEY `idx_uid_status` (`uid`,`status`),
|
||||||
|
KEY `idx_source_order` (`source_order_id`),
|
||||||
|
KEY `idx_add_time` (`add_time`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='芍药居推四免一推广任务';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `eb_syj_promote_amount_log` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`uid` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`order_id` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`order_no` varchar(64) NOT NULL DEFAULT '',
|
||||||
|
`amount` decimal(10,2) NOT NULL DEFAULT '0.00',
|
||||||
|
`before_amount` decimal(10,2) NOT NULL DEFAULT '0.00',
|
||||||
|
`after_amount` decimal(10,2) NOT NULL DEFAULT '0.00',
|
||||||
|
`type` varchar(32) NOT NULL DEFAULT '',
|
||||||
|
`link_id` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`mark` varchar(255) NOT NULL DEFAULT '',
|
||||||
|
`add_time` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uniq_order_type` (`order_id`,`type`),
|
||||||
|
KEY `idx_uid_type` (`uid`,`type`),
|
||||||
|
KEY `idx_add_time` (`add_time`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='芍药居待推广金额流水';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `eb_syj_promote_task_record` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`task_id` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`uid` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '推荐人',
|
||||||
|
`order_uid` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '下单用户',
|
||||||
|
`order_id` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`order_no` varchar(64) NOT NULL DEFAULT '',
|
||||||
|
`source_type` varchar(32) NOT NULL DEFAULT '',
|
||||||
|
`trigger_timing` varchar(32) NOT NULL DEFAULT '',
|
||||||
|
`step_no` tinyint(3) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`reward_rate` decimal(5,2) NOT NULL DEFAULT '0.00',
|
||||||
|
`status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '1有效 2退款失效',
|
||||||
|
`add_time` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`update_time` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uniq_order` (`order_id`),
|
||||||
|
KEY `idx_task_status` (`task_id`,`status`),
|
||||||
|
KEY `idx_uid` (`uid`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='芍药居推广任务推荐订单进度';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `eb_syj_promote_settlement` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`task_id` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`uid` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`settle_type` varchar(32) NOT NULL DEFAULT '',
|
||||||
|
`gross_amount` decimal(10,2) NOT NULL DEFAULT '0.00',
|
||||||
|
`fee_rate` decimal(5,2) NOT NULL DEFAULT '0.00',
|
||||||
|
`fee_amount` decimal(10,2) NOT NULL DEFAULT '0.00',
|
||||||
|
`net_amount` decimal(10,2) NOT NULL DEFAULT '0.00',
|
||||||
|
`bill_id` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`brokerage_id` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`audit_status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '0待审核 1通过 2拒绝',
|
||||||
|
`audit_uid` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`audit_remark` varchar(255) NOT NULL DEFAULT '',
|
||||||
|
`audit_time` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`add_time` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`update_time` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uniq_task_type` (`task_id`,`settle_type`),
|
||||||
|
KEY `idx_uid` (`uid`),
|
||||||
|
KEY `idx_audit` (`audit_status`,`add_time`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='芍药居推广任务结算';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `eb_syj_promote_reward_trigger` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`task_id` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`uid` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`trigger_no` varchar(64) NOT NULL DEFAULT '',
|
||||||
|
`trigger_amount` decimal(10,2) NOT NULL DEFAULT '4333.00',
|
||||||
|
`brokerage_status` tinyint(3) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`points_status` tinyint(3) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`level_task_status` tinyint(3) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`brokerage_link_id` varchar(255) NOT NULL DEFAULT '',
|
||||||
|
`points_link_id` varchar(255) NOT NULL DEFAULT '',
|
||||||
|
`level_task_link_id` varchar(255) NOT NULL DEFAULT '',
|
||||||
|
`retry_count` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`error_msg` varchar(500) NOT NULL DEFAULT '',
|
||||||
|
`add_time` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`update_time` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uniq_task` (`task_id`),
|
||||||
|
UNIQUE KEY `uniq_trigger_no` (`trigger_no`),
|
||||||
|
KEY `idx_status` (`points_status`,`level_task_status`,`brokerage_status`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='芍药居推广任务等价奖励触发';
|
||||||
|
|
||||||
|
DELETE FROM `eb_system_config` WHERE `menu_name` IN (
|
||||||
|
'syj_task_base_amount',
|
||||||
|
'syj_task_target_count',
|
||||||
|
'syj_task_reward_rates',
|
||||||
|
'syj_early_cashout_fee_rate',
|
||||||
|
'syj_task_generate_timing',
|
||||||
|
'syj_task_order_dedupe',
|
||||||
|
'syj_reward_trigger_enable'
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `eb_system_config` (`menu_name`, `type`, `input_type`, `config_tab_id`, `parameter`, `upload_type`, `required`, `width`, `high`, `value`, `info`, `desc`, `sort`, `status`) VALUES
|
||||||
|
('syj_task_base_amount', 'text', 'input', 0, '', 0, '', 0, 0, '4333', '芍药居任务基准金额', '每满多少有效消费生成1个推广任务', 0, 1),
|
||||||
|
('syj_task_target_count', 'text', 'input', 0, '', 0, '', 0, 0, '4', '芍药居任务目标单数', '推四免一目标推荐单数', 0, 1),
|
||||||
|
('syj_task_reward_rates', 'text', 'input', 0, '', 0, '', 0, 0, '[10,20,30,40]', '芍药居任务奖励比例', 'JSON数组,按进度档位配置', 0, 1),
|
||||||
|
('syj_early_cashout_fee_rate', 'text', 'input', 0, '', 0, '', 0, 0, '7', '芍药居提前兑现扣费比例', '1-3单提前兑现扣费百分比', 0, 1),
|
||||||
|
('syj_task_generate_timing', 'text', 'input', 0, '', 0, '', 0, 0, 'on_confirm', '芍药居任务生成节点', '首版默认确认收货', 0, 1),
|
||||||
|
('syj_task_order_dedupe', 'text', 'input', 0, '', 0, '', 0, 0, 'order', '芍药居推荐订单去重方式', '首版按订单去重', 0, 1),
|
||||||
|
('syj_reward_trigger_enable', 'text', 'input', 0, '', 0, '', 0, 0, '1', '芍药居奖励触发开关', '是否启用等价奖励触发', 0, 1);
|
||||||
|
|
||||||
|
UPDATE `eb_system_config` SET `value` = '0' WHERE `menu_name` = 'hjf_queue_pool_enable';
|
||||||
@@ -2864,6 +2864,26 @@ Route::group('adminapi', function () {
|
|||||||
\app\http\middleware\admin\AdminLogMiddleware::class
|
\app\http\middleware\admin\AdminLogMiddleware::class
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 芍药居推四免一推广任务
|
||||||
|
*/
|
||||||
|
Route::group('syj/promote', function () {
|
||||||
|
Route::get('task/list', 'v1.syj.PromoteController/taskList')->option(['real_name' => '芍药居推广任务列表']);
|
||||||
|
Route::get('task/:id', 'v1.syj.PromoteController/taskDetail')->option(['real_name' => '芍药居推广任务详情']);
|
||||||
|
Route::get('task/:id/records', 'v1.syj.PromoteController/taskRecords')->option(['real_name' => '芍药居推广任务进度']);
|
||||||
|
Route::get('cashout/list', 'v1.syj.PromoteController/cashoutList')->option(['real_name' => '芍药居提前兑现审核列表']);
|
||||||
|
Route::post('cashout/:id/audit', 'v1.syj.PromoteController/auditCashout')->option(['real_name' => '芍药居提前兑现审核']);
|
||||||
|
Route::get('settlement/list', 'v1.syj.PromoteController/settlementList')->option(['real_name' => '芍药居结算流水']);
|
||||||
|
Route::get('config', 'v1.syj.PromoteController/getConfig')->option(['real_name' => '芍药居任务配置']);
|
||||||
|
Route::post('config', 'v1.syj.PromoteController/saveConfig')->option(['real_name' => '保存芍药居任务配置']);
|
||||||
|
Route::post('retry-trigger/:taskId', 'v1.syj.PromoteController/retryTrigger')->option(['real_name' => '重试芍药居奖励触发']);
|
||||||
|
})->middleware([
|
||||||
|
\app\http\middleware\AllowOriginMiddleware::class,
|
||||||
|
\app\http\middleware\admin\AdminAuthTokenMiddleware::class,
|
||||||
|
\app\http\middleware\admin\AdminCkeckRoleMiddleware::class,
|
||||||
|
\app\http\middleware\admin\AdminLogMiddleware::class
|
||||||
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* miss 路由
|
* miss 路由
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -438,6 +438,15 @@ Route::group('api', function () {
|
|||||||
Route::get('hjf/brokerage/progress', 'v1.hjf.HjfBrokerage/progress')->name('hjfBrokerageProgress');//推荐佣金周期进度
|
Route::get('hjf/brokerage/progress', 'v1.hjf.HjfBrokerage/progress')->name('hjfBrokerageProgress');//推荐佣金周期进度
|
||||||
Route::get('hjf/assets/overview', 'v1.hjf.HjfAssets/overview')->name('hjfAssetsOverview');//资产总览
|
Route::get('hjf/assets/overview', 'v1.hjf.HjfAssets/overview')->name('hjfAssetsOverview');//资产总览
|
||||||
|
|
||||||
|
// 芍药居推四免一推广任务
|
||||||
|
Route::get('syj/promote/overview', 'v1.syj.PromoteController/overview')->name('syjPromoteOverview');
|
||||||
|
Route::get('syj/promote/task/list', 'v1.syj.PromoteController/taskList')->name('syjPromoteTaskList');
|
||||||
|
Route::get('syj/promote/task/:id', 'v1.syj.PromoteController/taskDetail')->name('syjPromoteTaskDetail');
|
||||||
|
Route::get('syj/promote/task/:id/records', 'v1.syj.PromoteController/taskRecords')->name('syjPromoteTaskRecords');
|
||||||
|
Route::post('syj/promote/task/:id/cashout', 'v1.syj.PromoteController/cashout')->name('syjPromoteCashout');
|
||||||
|
Route::get('syj/promote/amount/log', 'v1.syj.PromoteController/amountLog')->name('syjPromoteAmountLog');
|
||||||
|
Route::get('syj/promote/settlement/list', 'v1.syj.PromoteController/settlementList')->name('syjPromoteSettlementList');
|
||||||
|
|
||||||
})->middleware(StationOpenMiddleware::class)->middleware(AuthTokenMiddleware::class, true);
|
})->middleware(StationOpenMiddleware::class)->middleware(AuthTokenMiddleware::class, true);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
37
pro_v3.5.1/view/admin/src/api/syjPromote.js
Normal file
37
pro_v3.5.1/view/admin/src/api/syjPromote.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import request from '@/plugins/request';
|
||||||
|
|
||||||
|
export function taskListApi(params) {
|
||||||
|
return request({ url: 'syj/promote/task/list', method: 'get', params });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function taskDetailApi(id) {
|
||||||
|
return request({ url: `syj/promote/task/${id}`, method: 'get' });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function taskRecordsApi(id) {
|
||||||
|
return request({ url: `syj/promote/task/${id}/records`, method: 'get' });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cashoutListApi(params) {
|
||||||
|
return request({ url: 'syj/promote/cashout/list', method: 'get', params });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function auditCashoutApi(id, data) {
|
||||||
|
return request({ url: `syj/promote/cashout/${id}/audit`, method: 'post', data });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function settlementListApi(params) {
|
||||||
|
return request({ url: 'syj/promote/settlement/list', method: 'get', params });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function configGetApi() {
|
||||||
|
return request({ url: 'syj/promote/config', method: 'get' });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function configSaveApi(data) {
|
||||||
|
return request({ url: 'syj/promote/config', method: 'post', data });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function retryTriggerApi(taskId) {
|
||||||
|
return request({ url: `syj/promote/retry-trigger/${taskId}`, method: 'post' });
|
||||||
|
}
|
||||||
68
pro_v3.5.1/view/admin/src/pages/syj/cashout/index.vue
Normal file
68
pro_v3.5.1/view/admin/src/pages/syj/cashout/index.vue
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Card :bordered="false" dis-hover class="ivu-mt" :padding="0">
|
||||||
|
<div class="new_card_pd">
|
||||||
|
<Form inline @submit.native.prevent>
|
||||||
|
<FormItem label="关键词:"><Input v-model="query.keyword" placeholder="任务号/用户" class="input-add" clearable /></FormItem>
|
||||||
|
<FormItem label="状态:">
|
||||||
|
<Select v-model="query.audit_status" class="input-add" clearable>
|
||||||
|
<Option :value="0">待审核</Option>
|
||||||
|
<Option :value="1">已通过</Option>
|
||||||
|
<Option :value="2">已拒绝</Option>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem><Button type="primary" @click="getList">查询</Button></FormItem>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
<Card :bordered="false" dis-hover class="ivu-mt">
|
||||||
|
<Table :columns="columns" :data="list" :loading="loading">
|
||||||
|
<template slot-scope="{ row }" slot="amount">¥{{ row.net_amount }} / 应结 ¥{{ row.gross_amount }}</template>
|
||||||
|
<template slot-scope="{ row }" slot="status">{{ ['待审核','已通过','已拒绝'][row.audit_status] }}</template>
|
||||||
|
<template slot-scope="{ row }" slot="action">
|
||||||
|
<Button v-if="row.audit_status == 0" size="small" type="primary" @click="audit(row, 1)">通过</Button>
|
||||||
|
<Button v-if="row.audit_status == 0" size="small" class="ivu-ml-8" @click="audit(row, 2)">拒绝</Button>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { cashoutListApi, auditCashoutApi } from '@/api/syjPromote.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SyjCashout',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
list: [],
|
||||||
|
query: { keyword: '', audit_status: 0, page: 1, limit: 20 },
|
||||||
|
columns: [
|
||||||
|
{ title: '任务号', key: 'task_no', minWidth: 180 },
|
||||||
|
{ title: '用户', key: 'nickname', minWidth: 140 },
|
||||||
|
{ title: '到账/应结', slot: 'amount', minWidth: 160 },
|
||||||
|
{ title: '扣费', key: 'fee_amount', width: 100 },
|
||||||
|
{ title: '状态', slot: 'status', width: 100 },
|
||||||
|
{ title: '操作', slot: 'action', width: 160 }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() { this.getList(); },
|
||||||
|
methods: {
|
||||||
|
getList() {
|
||||||
|
this.loading = true;
|
||||||
|
cashoutListApi(this.query).then(res => {
|
||||||
|
const data = res.data || res;
|
||||||
|
this.list = data.list || [];
|
||||||
|
}).finally(() => { this.loading = false; });
|
||||||
|
},
|
||||||
|
audit(row, status) {
|
||||||
|
auditCashoutApi(row.id, { status, remark: status === 1 ? '审核通过' : '审核拒绝' }).then(() => {
|
||||||
|
this.$Message.success('审核成功');
|
||||||
|
this.getList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
63
pro_v3.5.1/view/admin/src/pages/syj/config/index.vue
Normal file
63
pro_v3.5.1/view/admin/src/pages/syj/config/index.vue
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<template>
|
||||||
|
<Card :bordered="false" dis-hover class="ivu-mt">
|
||||||
|
<p slot="title">芍药居任务配置</p>
|
||||||
|
<Spin v-if="loading" fix />
|
||||||
|
<Form v-else :model="form" :label-width="180" @submit.native.prevent>
|
||||||
|
<FormItem label="任务基准金额"><InputNumber v-model="form.base_amount" :min="1" style="width:180px" /></FormItem>
|
||||||
|
<FormItem label="目标单数"><InputNumber v-model="form.target_count" :min="1" :precision="0" style="width:180px" /></FormItem>
|
||||||
|
<FormItem label="奖励比例"><Input v-model="ratesText" placeholder="10,20,30,40" style="width:260px" /></FormItem>
|
||||||
|
<FormItem label="提前兑现扣费比例"><InputNumber v-model="form.early_cashout_fee_rate" :min="0" :max="100" style="width:180px" /></FormItem>
|
||||||
|
<FormItem label="任务生成节点">
|
||||||
|
<RadioGroup v-model="form.task_generate_timing">
|
||||||
|
<Radio label="on_confirm">确认收货</Radio>
|
||||||
|
<Radio label="on_pay">支付成功</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="奖励触发"><i-switch v-model="form.reward_trigger_enable" :true-value="1" :false-value="0" /></FormItem>
|
||||||
|
<FormItem><Button type="primary" :loading="saving" @click="save">保存</Button></FormItem>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { configGetApi, configSaveApi } from '@/api/syjPromote.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SyjConfig',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
saving: false,
|
||||||
|
ratesText: '10,20,30,40',
|
||||||
|
form: {
|
||||||
|
base_amount: 4333,
|
||||||
|
target_count: 4,
|
||||||
|
reward_rates: [10, 20, 30, 40],
|
||||||
|
early_cashout_fee_rate: 7,
|
||||||
|
task_generate_timing: 'on_confirm',
|
||||||
|
task_order_dedupe: 'order',
|
||||||
|
reward_trigger_enable: 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() { this.load(); },
|
||||||
|
methods: {
|
||||||
|
load() {
|
||||||
|
this.loading = true;
|
||||||
|
configGetApi().then(res => {
|
||||||
|
this.form = Object.assign(this.form, res.data || res);
|
||||||
|
this.ratesText = (this.form.reward_rates || []).join(',');
|
||||||
|
}).finally(() => { this.loading = false; });
|
||||||
|
},
|
||||||
|
save() {
|
||||||
|
this.saving = true;
|
||||||
|
const data = Object.assign({}, this.form, {
|
||||||
|
reward_rates: this.ratesText.split(',').map(item => Number(item.trim())).filter(item => !Number.isNaN(item))
|
||||||
|
});
|
||||||
|
configSaveApi(data).then(() => {
|
||||||
|
this.$Message.success('保存成功');
|
||||||
|
}).finally(() => { this.saving = false; });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
109
pro_v3.5.1/view/admin/src/pages/syj/promoteTask/index.vue
Normal file
109
pro_v3.5.1/view/admin/src/pages/syj/promoteTask/index.vue
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Card :bordered="false" dis-hover class="ivu-mt" :padding="0">
|
||||||
|
<div class="new_card_pd">
|
||||||
|
<Form inline @submit.native.prevent>
|
||||||
|
<FormItem label="关键词:">
|
||||||
|
<Input v-model="query.keyword" placeholder="任务号/订单号/用户" class="input-add" clearable />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="状态:">
|
||||||
|
<Select v-model="query.status" class="input-add" clearable>
|
||||||
|
<Option :value="0">进行中</Option>
|
||||||
|
<Option :value="1">已完成</Option>
|
||||||
|
<Option :value="2">提前兑现</Option>
|
||||||
|
<Option :value="4">异常</Option>
|
||||||
|
<Option :value="5">审核中</Option>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem>
|
||||||
|
<Button type="primary" class="mr14" @click="getList">查询</Button>
|
||||||
|
<Button @click="reset">重置</Button>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
<Card :bordered="false" dis-hover class="ivu-mt">
|
||||||
|
<Table :columns="columns" :data="list" :loading="loading">
|
||||||
|
<template slot-scope="{ row }" slot="status">{{ statusText(row.status) }}</template>
|
||||||
|
<template slot-scope="{ row }" slot="progress">{{ row.progress_count }}/{{ row.target_count }}</template>
|
||||||
|
<template slot-scope="{ row }" slot="amount">¥{{ Number(row.base_amount).toFixed(2) }}</template>
|
||||||
|
<template slot-scope="{ row }" slot="action">
|
||||||
|
<Button size="small" type="primary" @click="showDetail(row)">详情</Button>
|
||||||
|
<Button v-if="row.reward_trigger_status == 2" size="small" class="ivu-ml-8" @click="retry(row)">重试奖励</Button>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
<Page class="mt20" :total="total" :current="query.page" :page-size="query.limit" show-total @on-change="p => { query.page = p; getList(); }" />
|
||||||
|
</Card>
|
||||||
|
<Modal v-model="detailVisible" width="720" title="推广任务详情">
|
||||||
|
<pre class="detail-json">{{ detail }}</pre>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { taskListApi, taskDetailApi, retryTriggerApi } from '@/api/syjPromote.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SyjPromoteTask',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
detailVisible: false,
|
||||||
|
detail: '',
|
||||||
|
total: 0,
|
||||||
|
list: [],
|
||||||
|
query: { keyword: '', status: '', page: 1, limit: 20 },
|
||||||
|
columns: [
|
||||||
|
{ title: '任务号', key: 'task_no', minWidth: 180 },
|
||||||
|
{ title: '用户', key: 'nickname', minWidth: 140 },
|
||||||
|
{ title: '来源订单', key: 'source_order_no', minWidth: 160 },
|
||||||
|
{ title: '金额', slot: 'amount', width: 110 },
|
||||||
|
{ title: '进度', slot: 'progress', width: 90 },
|
||||||
|
{ title: '状态', slot: 'status', width: 100 },
|
||||||
|
{ title: '创建时间', key: 'add_time', width: 140 },
|
||||||
|
{ title: '操作', slot: 'action', width: 180, fixed: 'right' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getList() {
|
||||||
|
this.loading = true;
|
||||||
|
taskListApi(this.query).then(res => {
|
||||||
|
const data = res.data || res;
|
||||||
|
this.list = data.list || [];
|
||||||
|
this.total = data.count || 0;
|
||||||
|
}).finally(() => { this.loading = false; });
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
this.query = { keyword: '', status: '', page: 1, limit: 20 };
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
showDetail(row) {
|
||||||
|
taskDetailApi(row.id).then(res => {
|
||||||
|
this.detail = JSON.stringify(res.data || res, null, 2);
|
||||||
|
this.detailVisible = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
retry(row) {
|
||||||
|
retryTriggerApi(row.id).then(() => {
|
||||||
|
this.$Message.success('重试完成');
|
||||||
|
this.getList();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
statusText(status) {
|
||||||
|
return ['进行中', '已完成', '提前兑现', '已关闭', '异常', '审核中'][Number(status)] || '未知';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.detail-json {
|
||||||
|
max-height: 520px;
|
||||||
|
overflow: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
34
pro_v3.5.1/view/admin/src/router/modules/syj.js
Normal file
34
pro_v3.5.1/view/admin/src/router/modules/syj.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import BasicLayout from '@/layouts/basic-layout';
|
||||||
|
|
||||||
|
const pre = 'syj_';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
path: '/admin/syj',
|
||||||
|
name: 'syj',
|
||||||
|
header: 'syj',
|
||||||
|
meta: {
|
||||||
|
auth: ['admin-syj'],
|
||||||
|
title: '芍药居'
|
||||||
|
},
|
||||||
|
component: BasicLayout,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'promote/task',
|
||||||
|
name: `${pre}promoteTask`,
|
||||||
|
meta: { auth: ['syj-promote-task'], title: '推广任务' },
|
||||||
|
component: () => import('@/pages/syj/promoteTask/index')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'promote/cashout',
|
||||||
|
name: `${pre}cashout`,
|
||||||
|
meta: { auth: ['syj-promote-cashout'], title: '提前兑现审核' },
|
||||||
|
component: () => import('@/pages/syj/cashout/index')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'promote/config',
|
||||||
|
name: `${pre}config`,
|
||||||
|
meta: { auth: ['syj-promote-config'], title: '任务配置' },
|
||||||
|
component: () => import('@/pages/syj/config/index')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
@@ -26,6 +26,7 @@ import work from "./modules/work";
|
|||||||
import content from "./modules/content";
|
import content from "./modules/content";
|
||||||
import inventory from "./modules/inventory";
|
import inventory from "./modules/inventory";
|
||||||
import hjfQueue from "./modules/hjfCustom.js";
|
import hjfQueue from "./modules/hjfCustom.js";
|
||||||
|
import syj from "./modules/syj.js";
|
||||||
import { isSupplierPath } from "@/utils/pathUtils";
|
import { isSupplierPath } from "@/utils/pathUtils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -210,7 +211,8 @@ const frameIn = [
|
|||||||
work,
|
work,
|
||||||
content,
|
content,
|
||||||
inventory,
|
inventory,
|
||||||
hjfQueue
|
hjfQueue,
|
||||||
|
syj
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
25
pro_v3.5.1/view/uniapp/api/syjPromote.js
Normal file
25
pro_v3.5.1/view/uniapp/api/syjPromote.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import request from '@/utils/request.js';
|
||||||
|
|
||||||
|
export function getSyjOverview() {
|
||||||
|
return request.get('syj/promote/overview');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSyjTaskList(params) {
|
||||||
|
return request.get('syj/promote/task/list', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSyjTaskDetail(id) {
|
||||||
|
return request.get(`syj/promote/task/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applySyjCashout(id) {
|
||||||
|
return request.post(`syj/promote/task/${id}/cashout`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSyjAmountLog(params) {
|
||||||
|
return request.get('syj/promote/amount/log', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSyjSettlementList(params) {
|
||||||
|
return request.get('syj/promote/settlement/list', params);
|
||||||
|
}
|
||||||
@@ -133,6 +133,18 @@
|
|||||||
"titleNView": false
|
"titleNView": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/syj/promote_task/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的推广任务"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/syj/promote_task/detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "任务详情"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
// "plugins": {
|
// "plugins": {
|
||||||
|
|||||||
80
pro_v3.5.1/view/uniapp/pages/syj/promote_task/detail.vue
Normal file
80
pro_v3.5.1/view/uniapp/pages/syj/promote_task/detail.vue
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page">
|
||||||
|
<view class="card">
|
||||||
|
<view class="title">{{ detail.task_no }}</view>
|
||||||
|
<view class="status">{{ statusText(detail.status) }}</view>
|
||||||
|
<view class="amount">¥{{ detail.base_amount || '0.00' }}</view>
|
||||||
|
<view class="progress">
|
||||||
|
<view class="bar"><view :style="{width: progressWidth}"></view></view>
|
||||||
|
<text>{{ detail.progress_count || 0 }}/{{ detail.target_count || 4 }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="card">
|
||||||
|
<view class="section-title">推荐进度</view>
|
||||||
|
<view class="record" v-for="item in records" :key="item.id">
|
||||||
|
<text>第 {{ item.step_no }} 单</text>
|
||||||
|
<text>{{ item.order_no }}</text>
|
||||||
|
</view>
|
||||||
|
<view v-if="!records.length" class="empty">暂无推荐订单</view>
|
||||||
|
</view>
|
||||||
|
<button v-if="canCashout" class="cashout" @tap="cashout">申请提前兑现</button>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getSyjTaskDetail, applySyjCashout } from '@/api/syjPromote.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return { id: 0, detail: {} };
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
records() {
|
||||||
|
return this.detail.records || [];
|
||||||
|
},
|
||||||
|
canCashout() {
|
||||||
|
const progress = Number(this.detail.progress_count || 0);
|
||||||
|
return Number(this.detail.status) === 0 && progress >= 1 && progress <= 3;
|
||||||
|
},
|
||||||
|
progressWidth() {
|
||||||
|
const total = Number(this.detail.target_count || 4);
|
||||||
|
return `${Math.min(100, Number(this.detail.progress_count || 0) / total * 100)}%`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad(options) {
|
||||||
|
this.id = Number(options.id || 0);
|
||||||
|
this.load();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
load() {
|
||||||
|
getSyjTaskDetail(this.id).then(res => {
|
||||||
|
this.detail = res.data || res;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
cashout() {
|
||||||
|
applySyjCashout(this.id).then(() => {
|
||||||
|
uni.showToast({ title: '已提交审核' });
|
||||||
|
this.load();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
statusText(status) {
|
||||||
|
return ['进行中', '已完成', '提前兑现', '已关闭', '异常', '审核中'][Number(status)] || '未知';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.page{min-height:100vh;background:#f6f7f9;padding:24rpx;}
|
||||||
|
.card{background:#fff;border-radius:16rpx;padding:30rpx;margin-bottom:20rpx;}
|
||||||
|
.title{font-size:30rpx;color:#222;font-weight:600;}
|
||||||
|
.status{font-size:26rpx;color:#0f8f6f;margin-top:12rpx;}
|
||||||
|
.amount{font-size:52rpx;color:#111;font-weight:600;margin:28rpx 0;}
|
||||||
|
.progress{display:flex;align-items:center;gap:20rpx;}
|
||||||
|
.bar{flex:1;height:12rpx;background:#edf0f2;border-radius:12rpx;overflow:hidden;}
|
||||||
|
.bar view{height:100%;background:#0f8f6f;}
|
||||||
|
.section-title{font-size:30rpx;font-weight:600;margin-bottom:20rpx;}
|
||||||
|
.record{display:flex;justify-content:space-between;font-size:26rpx;color:#555;padding:20rpx 0;border-bottom:1px solid #f0f0f0;}
|
||||||
|
.empty{text-align:center;color:#999;font-size:26rpx;padding:40rpx 0;}
|
||||||
|
.cashout{height:88rpx;line-height:88rpx;border-radius:44rpx;background:#0f8f6f;color:#fff;font-size:30rpx;}
|
||||||
|
</style>
|
||||||
108
pro_v3.5.1/view/uniapp/pages/syj/promote_task/index.vue
Normal file
108
pro_v3.5.1/view/uniapp/pages/syj/promote_task/index.vue
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page">
|
||||||
|
<view class="summary">
|
||||||
|
<view class="summary-title">我的推广任务</view>
|
||||||
|
<view class="summary-amount">¥{{ overview.pending_amount || '0.00' }}</view>
|
||||||
|
<view class="summary-sub">待推广金额 / 满 ¥{{ overview.base_amount || '4333.00' }} 生成 1 个任务</view>
|
||||||
|
<view class="summary-grid">
|
||||||
|
<view><text>{{ overview.active_task_count || 0 }}</text><text>进行中</text></view>
|
||||||
|
<view><text>{{ overview.completed_task_count || 0 }}</text><text>已完成</text></view>
|
||||||
|
<view><text>{{ overview.cashout_task_count || 0 }}</text><text>已兑现</text></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tabs">
|
||||||
|
<view v-for="tab in tabs" :key="tab.value" :class="{active: status === tab.value}" @tap="switchTab(tab.value)">{{ tab.label }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="task" v-for="item in list" :key="item.id" @tap="goDetail(item.id)">
|
||||||
|
<view class="task-head">
|
||||||
|
<text>{{ item.task_no }}</text>
|
||||||
|
<text>{{ statusText(item.status) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="progress">
|
||||||
|
<view class="bar"><view :style="{width: progressWidth(item)}"></view></view>
|
||||||
|
<text>{{ item.progress_count }}/{{ item.target_count }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="task-meta">来源订单:{{ item.source_order_no }}</view>
|
||||||
|
<view class="task-meta">任务金额:¥{{ item.base_amount }}</view>
|
||||||
|
</view>
|
||||||
|
<view v-if="!list.length && !loading" class="empty">暂无推广任务</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getSyjOverview, getSyjTaskList } from '@/api/syjPromote.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
overview: {},
|
||||||
|
list: [],
|
||||||
|
loading: false,
|
||||||
|
status: '',
|
||||||
|
page: 1,
|
||||||
|
tabs: [
|
||||||
|
{ label: '全部', value: '' },
|
||||||
|
{ label: '进行中', value: 0 },
|
||||||
|
{ label: '已完成', value: 1 },
|
||||||
|
{ label: '已兑现', value: 2 },
|
||||||
|
{ label: '异常', value: 4 }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
onShow() {
|
||||||
|
this.load();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
load() {
|
||||||
|
this.loading = true;
|
||||||
|
Promise.all([
|
||||||
|
getSyjOverview(),
|
||||||
|
getSyjTaskList({ status: this.status, page: this.page, limit: 20 })
|
||||||
|
]).then(([overview, tasks]) => {
|
||||||
|
this.overview = overview.data || overview;
|
||||||
|
const data = tasks.data || tasks;
|
||||||
|
this.list = data.list || [];
|
||||||
|
}).finally(() => {
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
switchTab(value) {
|
||||||
|
this.status = value;
|
||||||
|
this.page = 1;
|
||||||
|
this.load();
|
||||||
|
},
|
||||||
|
goDetail(id) {
|
||||||
|
uni.navigateTo({ url: `/pages/syj/promote_task/detail?id=${id}` });
|
||||||
|
},
|
||||||
|
progressWidth(item) {
|
||||||
|
const total = Number(item.target_count || 4);
|
||||||
|
return `${Math.min(100, Number(item.progress_count || 0) / total * 100)}%`;
|
||||||
|
},
|
||||||
|
statusText(status) {
|
||||||
|
return ['进行中', '已完成', '提前兑现', '已关闭', '异常', '审核中'][Number(status)] || '未知';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.page{min-height:100vh;background:#f6f7f9;padding:24rpx;}
|
||||||
|
.summary{background:#fff;border-radius:16rpx;padding:32rpx;}
|
||||||
|
.summary-title{font-size:30rpx;color:#333;}
|
||||||
|
.summary-amount{font-size:56rpx;font-weight:600;color:#0f8f6f;margin-top:20rpx;}
|
||||||
|
.summary-sub{font-size:24rpx;color:#777;margin-top:8rpx;}
|
||||||
|
.summary-grid{display:flex;margin-top:28rpx;border-top:1px solid #eee;padding-top:24rpx;}
|
||||||
|
.summary-grid view{flex:1;display:flex;flex-direction:column;gap:8rpx;font-size:24rpx;color:#777;}
|
||||||
|
.summary-grid text:first-child{font-size:34rpx;color:#222;font-weight:600;}
|
||||||
|
.tabs{display:flex;gap:16rpx;margin:24rpx 0;overflow-x:auto;}
|
||||||
|
.tabs view{white-space:nowrap;background:#fff;border-radius:999rpx;padding:14rpx 24rpx;font-size:26rpx;color:#555;}
|
||||||
|
.tabs .active{background:#0f8f6f;color:#fff;}
|
||||||
|
.task{background:#fff;border-radius:16rpx;padding:28rpx;margin-bottom:20rpx;}
|
||||||
|
.task-head{display:flex;justify-content:space-between;font-size:28rpx;color:#222;}
|
||||||
|
.task-head text:last-child{color:#0f8f6f;}
|
||||||
|
.progress{display:flex;align-items:center;gap:20rpx;margin:24rpx 0;}
|
||||||
|
.bar{flex:1;height:12rpx;background:#edf0f2;border-radius:12rpx;overflow:hidden;}
|
||||||
|
.bar view{height:100%;background:#0f8f6f;}
|
||||||
|
.task-meta{font-size:24rpx;color:#777;line-height:38rpx;}
|
||||||
|
.empty{text-align:center;color:#999;font-size:28rpx;padding:80rpx 0;}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user