505 行
21 KiB
PHP
505 行
21 KiB
PHP
<?php
|
|
|
|
namespace app\admin\controller\shopro;
|
|
|
|
use app\admin\model\shopro\order\Order;
|
|
use app\admin\model\shopro\order\OrderItem;
|
|
use app\admin\model\shopro\goods\Goods;
|
|
use app\admin\model\shopro\user\User;
|
|
use app\admin\model\shopro\Share;
|
|
use app\admin\model\shopro\commission\Agent;
|
|
use app\admin\model\shopro\partner\Reward;
|
|
use app\admin\model\shopro\partner\DailyReport;
|
|
use app\admin\model\shopro\partner\RateLog;
|
|
use app\admin\model\shopro\partner\VoucherPlan;
|
|
use addons\shopro\service\SearchHistory;
|
|
use addons\shopro\service\partner\RateService;
|
|
use addons\shopro\service\partner\IdentityService;
|
|
use addons\shopro\service\partner\PartnerConfig;
|
|
|
|
class Dashboard extends Common
|
|
{
|
|
|
|
public function index()
|
|
{
|
|
$cardList = [
|
|
[
|
|
'name' => 'total',
|
|
'card' => 'shopro/dashboard/total',
|
|
'status' => $this->auth->check('shopro/dashboard/total')
|
|
],
|
|
[
|
|
'name' => 'chart',
|
|
'card' => 'shopro/dashboard/chart',
|
|
'status' => $this->auth->check('shopro/dashboard/chart')
|
|
],
|
|
[
|
|
'name' => 'ranking',
|
|
'card' => 'shopro/dashboard/ranking',
|
|
'status' => $this->auth->check('shopro/dashboard/ranking')
|
|
],
|
|
];
|
|
$this->assignconfig('cardList', $cardList);
|
|
return $this->view->fetch();
|
|
}
|
|
|
|
|
|
public function total()
|
|
{
|
|
// 1. 基础用户数据 (保持向下兼容)
|
|
$userData = [
|
|
'total' => User::count(),
|
|
'today' => User::whereTime('createtime', 'today')->count(),
|
|
'week' => User::whereTime('createtime', 'week')->count(),
|
|
'array' => collection(User::field('id,createtime')->whereTime('createtime', 'today')->select())->each(function ($user) {
|
|
$user->counter = 1;
|
|
$user->createtime_unix = $user->getData('createtime') * 1000;
|
|
})
|
|
];
|
|
|
|
// 2. 分销商/合伙人基础数据 (保持向下兼容)
|
|
$agentData = [
|
|
'total' => Agent::count(),
|
|
'today' => Agent::whereTime('createtime', 'today')->count(),
|
|
'week' => Agent::whereTime('createtime', 'week')->count(),
|
|
'array' => collection(Agent::field('user_id,createtime')->whereTime('createtime', 'today')->select())->each(function ($agent) {
|
|
$agent->counter = 1;
|
|
$agent->createtime_unix = $agent->getData('createtime') * 1000;
|
|
})
|
|
];
|
|
|
|
// 3. 分享数据 (保持向下兼容)
|
|
$shareData = [
|
|
'total' => Share::count(),
|
|
'today' => Share::whereTime('createtime', 'today')->count(),
|
|
'week' => Share::whereTime('createtime', 'week')->count(),
|
|
'array' => collection(Share::field('id,createtime')->whereTime('createtime', 'today')->select())->each(function ($share) {
|
|
$share->counter = 1;
|
|
$share->createtime_unix = $share->getData('createtime') * 1000;
|
|
})
|
|
];
|
|
|
|
// 4. 全局财富大盘与资金流 (专业财富指标)
|
|
$paidStatuses = [Order::STATUS_PAID, Order::STATUS_COMPLETED];
|
|
$totalGmv = (float) Order::whereIn('status', $paidStatuses)->sum('pay_fee');
|
|
$todayGmv = (float) Order::whereIn('status', $paidStatuses)->whereTime('createtime', 'today')->sum('pay_fee');
|
|
$paidOrderCount = (int) Order::whereIn('status', $paidStatuses)->count();
|
|
$todayPaidOrderCount = (int) Order::whereIn('status', $paidStatuses)->whereTime('createtime', 'today')->count();
|
|
$avgOrderAmount = $paidOrderCount > 0 ? round($totalGmv / $paidOrderCount, 2) : 0.00;
|
|
|
|
// 现金分成总支出 (status = 1 真实入账)
|
|
$totalPayout = (float) Reward::where('status', 1)->sum('amount');
|
|
$todayPayout = (float) Reward::where('status', 1)->whereTime('createtime', 'today')->sum('amount');
|
|
$netRetention = round($totalGmv - $totalPayout, 2);
|
|
$payoutRate = $totalGmv > 0 ? round(($totalPayout / $totalGmv) * 100, 2) : 0.00;
|
|
|
|
$wealthData = [
|
|
'total_gmv' => number_format($totalGmv, 2, '.', ''),
|
|
'today_gmv' => number_format($todayGmv, 2, '.', ''),
|
|
'total_payout' => number_format($totalPayout, 2, '.', ''),
|
|
'today_payout' => number_format($todayPayout, 2, '.', ''),
|
|
'net_retention' => number_format($netRetention, 2, '.', ''),
|
|
'payout_rate' => number_format($payoutRate, 2, '.', ''),
|
|
'paid_order_count' => $paidOrderCount,
|
|
'today_paid_order_count' => $todayPaidOrderCount,
|
|
'avg_order_amount' => number_format($avgOrderAmount, 2, '.', ''),
|
|
];
|
|
|
|
// 5. 马豆生态资产蓄水池
|
|
$totalMadow = (float) Agent::sum('madow');
|
|
$rateService = new RateService();
|
|
$currentRate = $rateService->currentRate();
|
|
$totalMadowCny = round($totalMadow * $currentRate, 2);
|
|
$historyOrderN = $rateService->countN();
|
|
|
|
$latestReport = DailyReport::order('settle_date', 'desc')->find();
|
|
$yesterdayMadowPool = $latestReport ? (float) $latestReport->madow_pool : 0.00;
|
|
|
|
$madowData = [
|
|
'total_madow' => IdentityService::formatMadow($totalMadow),
|
|
'current_rate' => number_format($currentRate, 10, '.', ''),
|
|
'rate_per_wan' => number_format($currentRate * 10000, 4, '.', ''), // 每万豆价值折算
|
|
'total_madow_cny' => number_format($totalMadowCny, 2, '.', ''),
|
|
'history_order_n' => $historyOrderN,
|
|
'yesterday_madow_pool' => IdentityService::formatMadow($yesterdayMadowPool),
|
|
];
|
|
|
|
// 6. 消费兑换券杠杆池
|
|
$totalVoucher = (float) VoucherPlan::sum('total_amount');
|
|
$releasedVoucher = (float) VoucherPlan::sum('released_amount');
|
|
$usedVoucher = (float) Agent::sum('voucher_used');
|
|
$pendingVoucher = max(0, round($totalVoucher - $releasedVoucher, 2));
|
|
$availableVoucher = max(0, round($releasedVoucher - $usedVoucher, 2));
|
|
|
|
$voucherData = [
|
|
'total_voucher' => number_format($totalVoucher, 2, '.', ''),
|
|
'released_voucher' => number_format($releasedVoucher, 2, '.', ''),
|
|
'used_voucher' => number_format($usedVoucher, 2, '.', ''),
|
|
'pending_voucher' => number_format($pendingVoucher, 2, '.', ''),
|
|
'available_voucher' => number_format($availableVoucher, 2, '.', ''),
|
|
];
|
|
|
|
// 7. 合伙股东网络与资本活力
|
|
$totalAgents = (int) Agent::count();
|
|
$tasterCount = (int) Agent::where('level', 1)->count();
|
|
$makerCount = (int) Agent::where('level', '>=', 2)->count();
|
|
$makerLevels = [];
|
|
for ($lv = 2; $lv <= 7; $lv++) {
|
|
$makerLevels['V' . ($lv - 1)] = (int) Agent::where('level', $lv)->count();
|
|
}
|
|
$venueCount = (int) Agent::where('is_venue', 1)->count();
|
|
$countyCount = (int) Agent::where('is_county', 1)->count();
|
|
|
|
$activeWindow = time() - (int) PartnerConfig::get('partner_active_days', 30) * 86400;
|
|
$activeCount = (int) Agent::where('last_self_order_time', '>=', $activeWindow)->count();
|
|
$inactiveCount = max(0, $totalAgents - $activeCount);
|
|
$activeRate = $totalAgents > 0 ? round(($activeCount / $totalAgents) * 100, 1) : 0.0;
|
|
|
|
$partnerNetwork = [
|
|
'total_agents' => $totalAgents,
|
|
'taster_count' => $tasterCount,
|
|
'maker_count' => $makerCount,
|
|
'maker_levels' => $makerLevels,
|
|
'venue_count' => $venueCount,
|
|
'county_count' => $countyCount,
|
|
'active_count' => $activeCount,
|
|
'inactive_count' => $inactiveCount,
|
|
'active_rate' => $activeRate,
|
|
];
|
|
|
|
$this->success('获取成功', null, [
|
|
'user_data' => $userData,
|
|
'agent_data' => $agentData ?? null,
|
|
'share_data' => $shareData,
|
|
'wealth_data' => $wealthData,
|
|
'madow_data' => $madowData,
|
|
'voucher_data' => $voucherData,
|
|
'partner_network' => $partnerNetwork,
|
|
]);
|
|
}
|
|
|
|
|
|
|
|
public function chart()
|
|
{
|
|
$date = $this->request->param('date', '');
|
|
$date = array_values(array_filter(explode(' - ', $date)));
|
|
if (empty($date) || count($date) < 2) {
|
|
$date = [date('Y-m-d 00:00:00'), date('Y-m-d 23:59:59')];
|
|
}
|
|
|
|
$orders = Order::with(['items'])->whereTime('createtime', 'between', $date)
|
|
->order('id', 'asc')->select();
|
|
|
|
// 订单数
|
|
$data['orderNum'] = count($orders);
|
|
$data['orderArr'] = [];
|
|
|
|
// 支付订单(包含退款的订单, 不包含货到付款还未收货(未支付)订单)
|
|
$data['payOrderNum'] = 0;
|
|
$data['payOrderArr'] = [];
|
|
// 支付金额(包含退款的订单, 不包含货到付款还未收货(未支付)订单)
|
|
$data['payAmountNum'] = 0;
|
|
$data['payAmountArr'] = [];
|
|
|
|
// 支付用户
|
|
$userIds = [];
|
|
$data['payUserNum'] = 0;
|
|
$data['payUserArr'] = [];
|
|
|
|
// 待发货(包含货到付款)
|
|
$data['noSendNum'] = 0;
|
|
$data['noSendArr'] = [];
|
|
// 售后维权
|
|
$data['aftersaleNum'] = 0;
|
|
$data['aftersaleArr'] = [];
|
|
// 退款订单
|
|
$data['refundNum'] = 0;
|
|
$data['refundArr'] = [];
|
|
|
|
foreach ($orders as $key => $order) {
|
|
$data['orderArr'][] = [
|
|
'counter' => 1,
|
|
'createtime' => $order->getData('createtime') * 1000,
|
|
'user_id' => $order->user_id
|
|
];
|
|
|
|
// 已支付的,不包含货到付款未支付的
|
|
if (in_array($order->status, [Order::STATUS_PAID, Order::STATUS_COMPLETED])) {
|
|
$data['payOrderNum']++;
|
|
|
|
$data['payOrderArr'][] = [
|
|
'counter' => 1,
|
|
'createtime' => $order->getData('createtime') * 1000,
|
|
'user_id' => $order->user_id
|
|
];
|
|
|
|
// 支付金额
|
|
$data['payAmountNum'] = bcadd((string)$data['payAmountNum'], $order->pay_fee, 2);
|
|
|
|
$data['payAmountArr'][] = [
|
|
'counter' => (float) $order->pay_fee,
|
|
'createtime' => $order->getData('createtime') * 1000,
|
|
];
|
|
|
|
// 下单用户
|
|
if (!in_array($order->user_id, $userIds)) {
|
|
$data['payUserNum']++;
|
|
$data['payUserArr'][] = [
|
|
'counter' => 1,
|
|
'createtime' => $order->getData('createtime') * 1000,
|
|
'user_id' => $order->user_id
|
|
];
|
|
}
|
|
}
|
|
|
|
// 已支付的和货到付款未支付的
|
|
if (in_array($order->status, [Order::STATUS_PAID, Order::STATUS_COMPLETED]) || $order->isOffline($order)) {
|
|
$flagnoSend = false;
|
|
$flagaftersale = false;
|
|
$flagrefund = false;
|
|
$aftersaleIng = false;
|
|
foreach ($order->items as $k => $item) {
|
|
if (
|
|
!$flagnoSend
|
|
&& $item->dispatch_status == OrderItem::DISPATCH_STATUS_NOSEND
|
|
&& $item->refund_status == OrderItem::REFUND_STATUS_NOREFUND
|
|
&& in_array($order->apply_refund_status, [
|
|
Order::APPLY_REFUND_STATUS_NOAPPLY,
|
|
Order::APPLY_REFUND_STATUS_REFUSE
|
|
])
|
|
) {
|
|
$flagnoSend = true;
|
|
}
|
|
|
|
if (
|
|
$item->aftersale_status == OrderItem::AFTERSALE_STATUS_ING
|
|
&& $item->dispatch_status == OrderItem::DISPATCH_STATUS_NOSEND
|
|
&& $item->refund_status == OrderItem::REFUND_STATUS_NOREFUND
|
|
) {
|
|
$aftersaleIng = true;
|
|
}
|
|
|
|
if (!$flagaftersale && $item->aftersale_status != OrderItem::AFTERSALE_STATUS_NOAFTER) {
|
|
$data['aftersaleNum']++;
|
|
$flagaftersale = true;
|
|
}
|
|
|
|
if (!$flagrefund && $item->refund_status > OrderItem::REFUND_STATUS_NOREFUND) {
|
|
$data['refundNum']++;
|
|
$flagrefund = true;
|
|
}
|
|
}
|
|
|
|
if (!$aftersaleIng && $flagnoSend) {
|
|
$data['noSendNum']++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ================= 财富与收益分成时段穿透 (新增) =================
|
|
$rewards = Reward::where('status', 1)
|
|
->whereTime('createtime', 'between', $date)
|
|
->order('id', 'asc')
|
|
->select();
|
|
|
|
$payoutTotal = 0.00;
|
|
$payoutArr = [];
|
|
$periodMadow = 0.00000000;
|
|
|
|
// 四大分成板块结构
|
|
$structureMap = [
|
|
'push3' => [
|
|
'key' => 'push3',
|
|
'name' => '直推裂变(推三返一)',
|
|
'types' => ['push3'],
|
|
'amount' => 0.00,
|
|
'count' => 0,
|
|
'color' => '#806AF6',
|
|
],
|
|
'maker' => [
|
|
'key' => 'maker',
|
|
'name' => '创客团队(极差/平级/超越)',
|
|
'types' => ['maker_diff', 'maker_peer', 'maker_overtaken'],
|
|
'amount' => 0.00,
|
|
'count' => 0,
|
|
'color' => '#13c2c2',
|
|
],
|
|
'channel' => [
|
|
'key' => 'channel',
|
|
'name' => '实体渠道(馆县拆分/平级)',
|
|
'types' => ['team_venue', 'team_county', 'venue_peer', 'county_peer'],
|
|
'amount' => 0.00,
|
|
'count' => 0,
|
|
'color' => '#fa8c16',
|
|
],
|
|
'share' => [
|
|
'key' => 'share',
|
|
'name' => '全网共享(平台日结均分)',
|
|
'types' => ['venue_share', 'county_share'],
|
|
'amount' => 0.00,
|
|
'count' => 0,
|
|
'color' => '#eb2f96',
|
|
],
|
|
];
|
|
|
|
// 详细奖项映射
|
|
$detailList = [];
|
|
foreach (PartnerConfig::rewardTypeMap() as $typeKey => $typeName) {
|
|
if (in_array($typeKey, ['madow_share', 'voucher_grant', 'voucher_release'])) {
|
|
continue;
|
|
}
|
|
$detailList[$typeKey] = [
|
|
'type' => $typeKey,
|
|
'name' => $typeName,
|
|
'amount' => 0.00,
|
|
'count' => 0,
|
|
];
|
|
}
|
|
|
|
foreach ($rewards as $reward) {
|
|
$amt = (float) $reward->amount;
|
|
$rwType = $reward->reward_type;
|
|
|
|
if ($rwType === 'madow_share') {
|
|
$periodMadow = function_exists('bcadd')
|
|
? (float) bcadd((string)$periodMadow, (string)($reward->madow ?? 0), 8)
|
|
: ($periodMadow + (float)($reward->madow ?? 0));
|
|
continue;
|
|
}
|
|
|
|
if ($amt > 0) {
|
|
$payoutTotal = round($payoutTotal + $amt, 2);
|
|
$payoutArr[] = [
|
|
'counter' => $amt,
|
|
'createtime' => $reward->getData('createtime') * 1000,
|
|
'type' => $rwType,
|
|
];
|
|
|
|
// 归类到板块
|
|
foreach ($structureMap as $catKey => &$cat) {
|
|
if (in_array($rwType, $cat['types'])) {
|
|
$cat['amount'] = round($cat['amount'] + $amt, 2);
|
|
$cat['count']++;
|
|
break;
|
|
}
|
|
}
|
|
unset($cat);
|
|
|
|
if (isset($detailList[$rwType])) {
|
|
$detailList[$rwType]['amount'] = round($detailList[$rwType]['amount'] + $amt, 2);
|
|
$detailList[$rwType]['count']++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 格式化分成板块占比
|
|
$splitStructure = [];
|
|
foreach ($structureMap as $catKey => $cat) {
|
|
$catAmount = (float) $cat['amount'];
|
|
$ratio = $payoutTotal > 0 ? round(($catAmount / $payoutTotal) * 100, 1) : 0.0;
|
|
$splitStructure[] = [
|
|
'key' => $catKey,
|
|
'name' => $cat['name'],
|
|
'amount' => number_format($catAmount, 2, '.', ''),
|
|
'count' => $cat['count'],
|
|
'ratio' => $ratio,
|
|
'color' => $cat['color'],
|
|
];
|
|
}
|
|
|
|
$payAmountNum = (float) $data['payAmountNum'];
|
|
$data['payoutNum'] = number_format($payoutTotal, 2, '.', '');
|
|
$data['payoutArr'] = $payoutArr;
|
|
$data['retentionNum'] = number_format(max(0, round($payAmountNum - $payoutTotal, 2)), 2, '.', '');
|
|
$data['payoutRate'] = $payAmountNum > 0 ? round(($payoutTotal / $payAmountNum) * 100, 2) : 0.00;
|
|
$data['splitStructure'] = $splitStructure;
|
|
$data['rewardDetails'] = array_values($detailList);
|
|
$data['periodMadow'] = IdentityService::formatMadow($periodMadow);
|
|
|
|
$this->success('获取成功', null, $data);
|
|
}
|
|
|
|
|
|
|
|
public function ranking()
|
|
{
|
|
// 1. 商品销量排行 (原有)
|
|
$goods = Goods::limit(5)->order('sales', 'desc')->select();
|
|
foreach ($goods as $key => $gd) {
|
|
$gd->append(['real_sales']);
|
|
$result = OrderItem::field('sum(goods_num * goods_price) as sale_total_money')->where('goods_id', $gd['id'])
|
|
->whereExists(function ($query) use ($gd) {
|
|
$order_table_name = (new Order())->getQuery()->getTable();
|
|
$table_name = (new OrderItem())->getQuery()->getTable();
|
|
|
|
$query->table($order_table_name)->where($table_name . '.order_id=' . $order_table_name . '.id')
|
|
->whereIn('status', [Order::STATUS_PAID, Order::STATUS_COMPLETED]); // 已支付的订单
|
|
})->find();
|
|
|
|
$gd['sale_total_money'] = $result['sale_total_money'] ?: 0;
|
|
}
|
|
|
|
// 2. 热搜排行 (原有)
|
|
$searchHistory = new SearchHistory();
|
|
$hotSearch = $searchHistory->hotSearch();
|
|
|
|
// 3. 合伙股东收益排行 TOP 5 (新增)
|
|
$partnerRanking = [];
|
|
$rankingRows = Reward::field('user_id, sum(amount) as total_income, count(*) as reward_count')
|
|
->where('status', 1)
|
|
->where('user_id', '>', 0)
|
|
->group('user_id')
|
|
->order('total_income', 'desc')
|
|
->limit(5)
|
|
->select();
|
|
|
|
foreach ($rankingRows as $row) {
|
|
$user = User::field('id, nickname, avatar, mobile')->where('id', $row->user_id)->find();
|
|
$agent = Agent::where('user_id', $row->user_id)->find();
|
|
$identity = IdentityService::build($agent);
|
|
|
|
$partnerRanking[] = [
|
|
'user_id' => $row->user_id,
|
|
'nickname' => $user ? ($user->nickname ?: '用户#' . $row->user_id) : '用户#' . $row->user_id,
|
|
'avatar' => $user ? $user->avatar : '',
|
|
'mobile' => $user ? $user->mobile : '',
|
|
'total_income' => number_format((float)$row->total_income, 2, '.', ''),
|
|
'reward_count' => (int) $row->reward_count,
|
|
'identity_text' => $identity['identity_text'] ?: '普通会员',
|
|
'is_partner' => $identity['is_partner'],
|
|
'direct_order_seq' => $agent ? (int) $agent->direct_order_seq : 0,
|
|
];
|
|
}
|
|
|
|
// 4. 最新日结概览 (新增)
|
|
$dailyReport = DailyReport::order('settle_date', 'desc')->find();
|
|
$recentReport = null;
|
|
if ($dailyReport) {
|
|
$recentReport = [
|
|
'settle_date' => $dailyReport->settle_date,
|
|
'gmv' => number_format((float)$dailyReport->gmv, 2, '.', ''),
|
|
'order_count' => (int)$dailyReport->order_count,
|
|
'order_count_n' => (int)$dailyReport->order_count_n,
|
|
'rate' => number_format((float)$dailyReport->rate, 10, '.', ''),
|
|
'madow_pool' => IdentityService::formatMadow($dailyReport->madow_pool),
|
|
'share_user_count' => (int)$dailyReport->share_user_count,
|
|
'venue_pool' => number_format((float)$dailyReport->venue_pool, 2, '.', ''),
|
|
'venue_user_count' => (int)$dailyReport->venue_user_count,
|
|
'county_pool' => number_format((float)$dailyReport->county_pool, 2, '.', ''),
|
|
'county_user_count' => (int)$dailyReport->county_user_count,
|
|
'voucher_release_total' => number_format((float)$dailyReport->voucher_release_total, 2, '.', ''),
|
|
'status' => (int)$dailyReport->status,
|
|
'status_text' => $dailyReport->status_text,
|
|
];
|
|
}
|
|
|
|
$this->success('获取成功', null, [
|
|
'goods' => $goods,
|
|
'hot_search' => $hotSearch,
|
|
'partner_ranking' => $partnerRanking,
|
|
'daily_report_recent' => $recentReport,
|
|
]);
|
|
}
|
|
}
|