diff --git a/pro_v3.5.1/app/controller/admin/v1/syj/PromoteController.php b/pro_v3.5.1/app/controller/admin/v1/syj/PromoteController.php new file mode 100644 index 00000000..d5372708 --- /dev/null +++ b/pro_v3.5.1/app/controller/admin/v1/syj/PromoteController.php @@ -0,0 +1,119 @@ +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('重试完成'); + } +} diff --git a/pro_v3.5.1/app/controller/api/v1/syj/PromoteController.php b/pro_v3.5.1/app/controller/api/v1/syj/PromoteController.php new file mode 100644 index 00000000..8816baab --- /dev/null +++ b/pro_v3.5.1/app/controller/api/v1/syj/PromoteController.php @@ -0,0 +1,66 @@ +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))), + ]; + } +} diff --git a/pro_v3.5.1/app/dao/syj/PromoteAmountLogDao.php b/pro_v3.5.1/app/dao/syj/PromoteAmountLogDao.php new file mode 100644 index 00000000..37d8a4bf --- /dev/null +++ b/pro_v3.5.1/app/dao/syj/PromoteAmountLogDao.php @@ -0,0 +1,23 @@ +getModel()->where('uid', $uid); + $count = (clone $model)->count(); + $list = $model->order('add_time', 'desc')->page($page, $limit)->select()->toArray(); + return compact('list', 'count'); + } +} diff --git a/pro_v3.5.1/app/dao/syj/PromoteRewardTriggerDao.php b/pro_v3.5.1/app/dao/syj/PromoteRewardTriggerDao.php new file mode 100644 index 00000000..0da69abb --- /dev/null +++ b/pro_v3.5.1/app/dao/syj/PromoteRewardTriggerDao.php @@ -0,0 +1,15 @@ +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'); + } +} diff --git a/pro_v3.5.1/app/dao/syj/PromoteTaskDao.php b/pro_v3.5.1/app/dao/syj/PromoteTaskDao.php new file mode 100644 index 00000000..c322fc9b --- /dev/null +++ b/pro_v3.5.1/app/dao/syj/PromoteTaskDao.php @@ -0,0 +1,73 @@ +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(); + } +} diff --git a/pro_v3.5.1/app/dao/syj/PromoteTaskRecordDao.php b/pro_v3.5.1/app/dao/syj/PromoteTaskRecordDao.php new file mode 100644 index 00000000..f24ca16a --- /dev/null +++ b/pro_v3.5.1/app/dao/syj/PromoteTaskRecordDao.php @@ -0,0 +1,24 @@ +getModel()->where('task_id', $taskId); + if ($uid > 0) { + $model = $model->where('uid', $uid); + } + return $model->order('step_no', 'asc')->select()->toArray(); + } +} diff --git a/pro_v3.5.1/app/dao/syj/PromoteUserAmountDao.php b/pro_v3.5.1/app/dao/syj/PromoteUserAmountDao.php new file mode 100644 index 00000000..f2555b2c --- /dev/null +++ b/pro_v3.5.1/app/dao/syj/PromoteUserAmountDao.php @@ -0,0 +1,21 @@ +getModel()->where('uid', $uid)->lock(true)->find(); + return $row ? $row->toArray() : null; + } +} diff --git a/pro_v3.5.1/app/listener/order/Pay.php b/pro_v3.5.1/app/listener/order/Pay.php index 69182d12..d517245b 100644 --- a/pro_v3.5.1/app/listener/order/Pay.php +++ b/pro_v3.5.1/app/listener/order/Pay.php @@ -29,6 +29,7 @@ use app\services\order\StoreOrderComputedServices; use app\services\order\StoreOrderCreateServices; use app\services\order\StoreOrderInvoiceServices; use app\services\order\StoreOrderTakeServices; +use app\services\syj\SyjPromoteTaskServices; use app\services\user\channel\ChannelMerchantServices; use app\services\user\UserMoneyServices; 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']) { //拼团 diff --git a/pro_v3.5.1/app/listener/order/Refund.php b/pro_v3.5.1/app/listener/order/Refund.php index 5d33b1d3..542737b1 100644 --- a/pro_v3.5.1/app/listener/order/Refund.php +++ b/pro_v3.5.1/app/listener/order/Refund.php @@ -11,6 +11,7 @@ use app\jobs\supplier\SupplierFinanceJob;use app\jobs\system\CapitalFlowJob; use app\services\order\StoreOrderInvoiceServices; use app\services\order\StoreOrderServices; use app\services\order\StoreOrderStatusServices; +use app\services\syj\SyjPromoteTaskServices; use app\services\user\UserServices; use crmeb\interfaces\ListenerInterface; @@ -48,6 +49,16 @@ class Refund implements ListenerInterface //订单退款消息推送 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']) { $id = (int)$order['pid']; diff --git a/pro_v3.5.1/app/model/syj/PromoteAmountLog.php b/pro_v3.5.1/app/model/syj/PromoteAmountLog.php new file mode 100644 index 00000000..31ef0ad4 --- /dev/null +++ b/pro_v3.5.1/app/model/syj/PromoteAmountLog.php @@ -0,0 +1,23 @@ +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]); return true; diff --git a/pro_v3.5.1/app/services/syj/SyjPromoteConfigServices.php b/pro_v3.5.1/app/services/syj/SyjPromoteConfigServices.php new file mode 100644 index 00000000..9e8673b3 --- /dev/null +++ b/pro_v3.5.1/app/services/syj/SyjPromoteConfigServices.php @@ -0,0 +1,100 @@ + $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, '.', ''); + } +} diff --git a/pro_v3.5.1/app/services/syj/SyjPromoteRewardTriggerServices.php b/pro_v3.5.1/app/services/syj/SyjPromoteRewardTriggerServices.php new file mode 100644 index 00000000..e6edc227 --- /dev/null +++ b/pro_v3.5.1/app/services/syj/SyjPromoteRewardTriggerServices.php @@ -0,0 +1,104 @@ +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); + } +} diff --git a/pro_v3.5.1/app/services/syj/SyjPromoteSettlementServices.php b/pro_v3.5.1/app/services/syj/SyjPromoteSettlementServices.php new file mode 100644 index 00000000..38815521 --- /dev/null +++ b/pro_v3.5.1/app/services/syj/SyjPromoteSettlementServices.php @@ -0,0 +1,168 @@ +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, '.', ''); + } +} diff --git a/pro_v3.5.1/app/services/syj/SyjPromoteTaskServices.php b/pro_v3.5.1/app/services/syj/SyjPromoteTaskServices.php new file mode 100644 index 00000000..54e75ba1 --- /dev/null +++ b/pro_v3.5.1/app/services/syj/SyjPromoteTaskServices.php @@ -0,0 +1,309 @@ +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, '.', ''); + } +} diff --git a/pro_v3.5.1/app/services/system/config/SystemConfigServices.php b/pro_v3.5.1/app/services/system/config/SystemConfigServices.php index 69b5e062..61f6279d 100644 --- a/pro_v3.5.1/app/services/system/config/SystemConfigServices.php +++ b/pro_v3.5.1/app/services/system/config/SystemConfigServices.php @@ -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 diff --git a/pro_v3.5.1/app/services/user/UserBrokerageServices.php b/pro_v3.5.1/app/services/user/UserBrokerageServices.php index 07fc953c..3ee455ea 100644 --- a/pro_v3.5.1/app/services/user/UserBrokerageServices.php +++ b/pro_v3.5.1/app/services/user/UserBrokerageServices.php @@ -95,6 +95,13 @@ class UserBrokerageServices extends BaseServices 'status' => 1, 'pm' => 0 ], + 'syj_promote_settlement' => [ + 'title' => '芍药居推广任务结算', + 'type' => 'syj_promote_settlement', + 'mark' => '{%mark%}', + 'status' => 1, + 'pm' => 1 + ], 'get_staff_brokerage' => [ 'title' => '获得员工推广订单佣金', 'type' => 'staff_brokerage', diff --git a/pro_v3.5.1/database/syj_migration.sql b/pro_v3.5.1/database/syj_migration.sql new file mode 100644 index 00000000..ee032f51 --- /dev/null +++ b/pro_v3.5.1/database/syj_migration.sql @@ -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'; diff --git a/pro_v3.5.1/route/admin.php b/pro_v3.5.1/route/admin.php index 6db577fc..5060cddf 100644 --- a/pro_v3.5.1/route/admin.php +++ b/pro_v3.5.1/route/admin.php @@ -2864,6 +2864,26 @@ Route::group('adminapi', function () { \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 路由 */ diff --git a/pro_v3.5.1/route/api.php b/pro_v3.5.1/route/api.php index 2bef20c2..fbe1383b 100644 --- a/pro_v3.5.1/route/api.php +++ b/pro_v3.5.1/route/api.php @@ -438,6 +438,15 @@ Route::group('api', function () { Route::get('hjf/brokerage/progress', 'v1.hjf.HjfBrokerage/progress')->name('hjfBrokerageProgress');//推荐佣金周期进度 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); /** diff --git a/pro_v3.5.1/view/admin/src/api/syjPromote.js b/pro_v3.5.1/view/admin/src/api/syjPromote.js new file mode 100644 index 00000000..b7dadb96 --- /dev/null +++ b/pro_v3.5.1/view/admin/src/api/syjPromote.js @@ -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' }); +} diff --git a/pro_v3.5.1/view/admin/src/pages/syj/cashout/index.vue b/pro_v3.5.1/view/admin/src/pages/syj/cashout/index.vue new file mode 100644 index 00000000..ec816fef --- /dev/null +++ b/pro_v3.5.1/view/admin/src/pages/syj/cashout/index.vue @@ -0,0 +1,68 @@ + + + + + + + + + 待审核 + 已通过 + 已拒绝 + + + 查询 + + + + + + ¥{{ row.net_amount }} / 应结 ¥{{ row.gross_amount }} + {{ ['待审核','已通过','已拒绝'][row.audit_status] }} + + 通过 + 拒绝 + + + + + + + diff --git a/pro_v3.5.1/view/admin/src/pages/syj/config/index.vue b/pro_v3.5.1/view/admin/src/pages/syj/config/index.vue new file mode 100644 index 00000000..4a86869a --- /dev/null +++ b/pro_v3.5.1/view/admin/src/pages/syj/config/index.vue @@ -0,0 +1,63 @@ + + + 芍药居任务配置 + + + + + + + + + 确认收货 + 支付成功 + + + + 保存 + + + + + diff --git a/pro_v3.5.1/view/admin/src/pages/syj/promoteTask/index.vue b/pro_v3.5.1/view/admin/src/pages/syj/promoteTask/index.vue new file mode 100644 index 00000000..9d1e1c88 --- /dev/null +++ b/pro_v3.5.1/view/admin/src/pages/syj/promoteTask/index.vue @@ -0,0 +1,109 @@ + + + + + + + + + + + 进行中 + 已完成 + 提前兑现 + 异常 + 审核中 + + + + 查询 + 重置 + + + + + + + {{ statusText(row.status) }} + {{ row.progress_count }}/{{ row.target_count }} + ¥{{ Number(row.base_amount).toFixed(2) }} + + 详情 + 重试奖励 + + + { query.page = p; getList(); }" /> + + + {{ detail }} + + + + + + + diff --git a/pro_v3.5.1/view/admin/src/router/modules/syj.js b/pro_v3.5.1/view/admin/src/router/modules/syj.js new file mode 100644 index 00000000..156847d5 --- /dev/null +++ b/pro_v3.5.1/view/admin/src/router/modules/syj.js @@ -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') + } + ] +}; diff --git a/pro_v3.5.1/view/admin/src/router/routes.js b/pro_v3.5.1/view/admin/src/router/routes.js index 9b77fcf4..ee21469d 100644 --- a/pro_v3.5.1/view/admin/src/router/routes.js +++ b/pro_v3.5.1/view/admin/src/router/routes.js @@ -26,6 +26,7 @@ import work from "./modules/work"; import content from "./modules/content"; import inventory from "./modules/inventory"; import hjfQueue from "./modules/hjfCustom.js"; +import syj from "./modules/syj.js"; import { isSupplierPath } from "@/utils/pathUtils"; /** @@ -210,7 +211,8 @@ const frameIn = [ work, content, inventory, - hjfQueue + hjfQueue, + syj ]; /** diff --git a/pro_v3.5.1/view/uniapp/api/syjPromote.js b/pro_v3.5.1/view/uniapp/api/syjPromote.js new file mode 100644 index 00000000..8ab291ce --- /dev/null +++ b/pro_v3.5.1/view/uniapp/api/syjPromote.js @@ -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); +} diff --git a/pro_v3.5.1/view/uniapp/pages.json b/pro_v3.5.1/view/uniapp/pages.json index 12c04cbf..7907c969 100644 --- a/pro_v3.5.1/view/uniapp/pages.json +++ b/pro_v3.5.1/view/uniapp/pages.json @@ -133,6 +133,18 @@ "titleNView": false } } + }, + { + "path": "pages/syj/promote_task/index", + "style": { + "navigationBarTitleText": "我的推广任务" + } + }, + { + "path": "pages/syj/promote_task/detail", + "style": { + "navigationBarTitleText": "任务详情" + } } ], // "plugins": { diff --git a/pro_v3.5.1/view/uniapp/pages/syj/promote_task/detail.vue b/pro_v3.5.1/view/uniapp/pages/syj/promote_task/detail.vue new file mode 100644 index 00000000..ac136bc0 --- /dev/null +++ b/pro_v3.5.1/view/uniapp/pages/syj/promote_task/detail.vue @@ -0,0 +1,80 @@ + + + + {{ detail.task_no }} + {{ statusText(detail.status) }} + ¥{{ detail.base_amount || '0.00' }} + + + {{ detail.progress_count || 0 }}/{{ detail.target_count || 4 }} + + + + 推荐进度 + + 第 {{ item.step_no }} 单 + {{ item.order_no }} + + 暂无推荐订单 + + 申请提前兑现 + + + + + + diff --git a/pro_v3.5.1/view/uniapp/pages/syj/promote_task/index.vue b/pro_v3.5.1/view/uniapp/pages/syj/promote_task/index.vue new file mode 100644 index 00000000..12a45956 --- /dev/null +++ b/pro_v3.5.1/view/uniapp/pages/syj/promote_task/index.vue @@ -0,0 +1,108 @@ + + + + 我的推广任务 + ¥{{ overview.pending_amount || '0.00' }} + 待推广金额 / 满 ¥{{ overview.base_amount || '4333.00' }} 生成 1 个任务 + + {{ overview.active_task_count || 0 }}进行中 + {{ overview.completed_task_count || 0 }}已完成 + {{ overview.cashout_task_count || 0 }}已兑现 + + + + {{ tab.label }} + + + + {{ item.task_no }} + {{ statusText(item.status) }} + + + + {{ item.progress_count }}/{{ item.target_count }} + + 来源订单:{{ item.source_order_no }} + 任务金额:¥{{ item.base_amount }} + + 暂无推广任务 + + + + + +
芍药居任务配置
{{ detail }}