webman_ad/app/service/AdsInsightService.php
2025-01-10 21:50:48 +08:00

1030 lines
51 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\service;
use app\model\Ad;
use app\model\BpsAdCreativeInsight;
use app\model\BpsAdInsight;
use app\model\DayData;
use app\model\Campaign;
use app\model\BpsAdCampaign;
use app\model\BpsAdSet;
use app\model\BpsAdAd;
use app\model\ThirdUser;
use app\model\Asset;
use app\model\AssetRelation;
use DateTime;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use think\db\exception\DbException;
use think\facade\Db as ThinkDb;
class AdsInsightService
{
// 状态映射数组
private static $statusMapping = [
2 => 'ENABLE', // 2 代表广告已启用
3 => 'PAUSED', // 3 代表广告待审核等状态
// 其他状态可以继续添加
];
/**
* 获取广告系列列表
*/
public static function getCampaignList($platformType, $customerIds, $page, $pageSize, $keyword, $startDate = null, $endDate = null, $status = 0)
{
// 检查 customerIds 是否为空,直接返回空结构
if (empty($customerIds)) {
return [
'pagination' => [
'startIndex' => 0,
'maxResults' => $pageSize,
'count' => 0,
'pageNo' => $page,
'pageSize' => $pageSize,
'pages' => 0,
],
'statistics' => [
],
'data' => [],
];
}
// 动态构建日期条件
$dateCondition = '';
if ($startDate && $endDate) {
$dateCondition = "d.date BETWEEN '{$startDate}' AND '{$endDate}'";
} else {
}
// 基础查询:广告活动和日数据表联接
$query = BpsAdCampaign::alias('c')
->cache(false) // 强制不使用缓存
->leftJoin('bps.bps_ads_insights d', "c.campaign_id = d.ad_campaign_id AND c.platform_type = d.platform AND {$dateCondition}")
->field('c.campaign_id, c.status as status, c.name, c.account_id,c.platform_type,
COALESCE(SUM(d.assisted_purchases), 0) as assisted_purchases,
COALESCE(SUM(d.last_clicked_purchases), 0) as last_clicked_purchases,
COALESCE(SUM(d.clicks), 0) as clicks,
COALESCE(SUM(d.spend) / 1000000, 0) as spend,
COALESCE(SUM(d.impressions), 0) as impressions,
COALESCE(SUM(d.adds_to_cart), 0) as adds_to_cart,
COALESCE(SUM(d.cost_per_atc), 0) as cost_per_atc,
COALESCE(SUM(d.purchases), 0) as purchases,
COALESCE(SUM(d.purchases_value), 0) as purchases_value,
COALESCE(SUM(d.revenue), 0) as revenue,
COALESCE(SUM(d.total_cost), 0) as total_cost,
-1 as conversion_rate, -1 as roas, -1 as ctr,-1 as net_profit,-1 as net_profit_margin,-1 as net_profit_on_ad_spend')
->group('c.campaign_id, c.status, c.account_id, c.name,c.platform_type')
->where('c.account_id', 'in', $customerIds); // 添加 customerIds 条件
if ($status !== 0) {
$query->where('c.status', '=', $status);
}
// 添加关键字过滤条件
$query->where(function ($query) use ($keyword, $platformType) {
if ($keyword) {
$query->where('c.campaign_name', 'like', '%' . $keyword . '%');
}
if ($platformType) {
$platformType = (int)$platformType;
$query->where('c.platform_type', '=', $platformType);
}
});
// 获取所有符合条件的数据(不分页)
$allCampaigns = $query->select()->toArray();
$total_spend = array_sum(array_column($allCampaigns, 'spend'));
$total_cost = array_sum(array_column($allCampaigns, 'total_cost'));
$total_impressions = array_sum(array_column($allCampaigns, 'impressions'));
$total_clicks = array_sum(array_column($allCampaigns, 'clicks'));
$total_purchases_value = array_sum(array_column($allCampaigns, 'purchases_value'));
$total_revenue = array_sum(array_column($allCampaigns, 'revenue'));
$total_purchases = array_sum(array_column($allCampaigns, 'purchases'));
$cost_per_purchase = $total_purchases == 0 ? 0 : round($total_spend / $total_purchases, 2);
// 汇总统计数据
$statistics = [
'assisted_purchases' => array_sum(array_column($allCampaigns, 'assisted_purchases')),
'last_clicked_purchases' => array_sum(array_column($allCampaigns, 'last_clicked_purchases')),
'roas' => $total_spend == 0 ? '-' : round($total_revenue / $total_spend, 2) . 'X',
'amount_spend' => '$' . number_format($total_spend, 2) ?: '$0.00', // 格式化支出
'clicks' => $total_clicks,
'impressions' => $total_impressions,
'adds_to_cart' => array_sum(array_column($allCampaigns, 'adds_to_cart')),
'cost_per_atc' => array_sum(array_column($allCampaigns, 'cost_per_atc')),
'purchases' => array_sum(array_column($allCampaigns, 'purchases')),
'purchases_value' => array_sum(array_column($allCampaigns, 'purchases_value')),
'cost_per_purchase' => '$' . number_format($cost_per_purchase, 2) ?: '$0.00',
'revenue' => '$' . number_format(array_sum(array_column($allCampaigns, 'revenue')), 2) ?: '$0.00', // 格式化收入
'total_cost' => '$' . number_format($total_cost, 2) ?: '$0.00', // 格式化总成本
'conversion_rate' => $total_clicks == 0 ? '-' : round(($total_purchases / $total_clicks) * 100, 2) . '%', // 转换率
'net_profit' => ($total_revenue - $total_cost) >= 0 ? '+$' . number_format($total_revenue - $total_cost, 2) : '-$' . number_format($total_revenue - $total_cost, 2), // 净利润
'net_profit_margin' => $total_revenue == 0 ? '-' : round(($total_revenue - $total_cost) / $total_revenue, 2) * 100 . '%', // 净利润率
'net_profit_on_ad_spend' => $total_spend == 0 ? '-' : round(($total_revenue - $total_cost) / $total_spend, 2), // 广告支出净利润
// 计算总的 CTR
'ctr' => ($total_impressions > 0) ? number_format(($total_clicks / $total_impressions) * 100, 2) . '%' : '-', // 格式化为百分比
];
// 获取分页数据
$campaigns = $query->paginate($pageSize, false, ['page' => $page]);
// 确保数据格式统一
$result = array_map(function ($item) {
// CTR 的计算:点击率 = 点击数 / 展示数
$ctr = $item['impressions'] > 0 ? number_format(($item['clicks'] / $item['impressions']) * 100, 2) . '%' : '-';
// Conversion Rate 的计算:转换率 = 购买数 / 点击数
$conversion_rate = $item['clicks'] > 0 ? round(($item['purchases'] / $item['clicks']) * 100, 2) . '%' : '-';
// Net Profit 的计算:净利润 = 收入 - 总成本
$net_profit = ($item['revenue'] - $item['total_cost']) >= 0 ? '+$' . number_format($item['revenue'] - $item['total_cost'], 2) : '-$' . number_format($item['revenue'] - $item['total_cost'], 2);
// Net Profit Margin 的计算:净利润率 = 净利润 / 收入
$net_profit_margin = $item['revenue'] > 0 ? round(($item['revenue'] - $item['total_cost']) / $item['revenue'], 2) * 100 . '%' : '-';
// Net Profit on Ad Spend 的计算:广告支出净利润 = (收入 - 总成本) / 广告支出
$net_profit_on_ad_spend = $item['spend'] > 0 ? round(($item['revenue'] - $item['total_cost']) / $item['spend'], 2) : '-';
return [
'id' => $item['campaign_id'],
'platform_type' => $item['platform_type'],
'account_id' => $item['account_id'], // 映射为 customer_id
'name' => $item['name'] ?: '-', // 若 name 为空则显示 '-'
'status' => $item['status'],
'assisted_purchases' => $item['assisted_purchases'],
'last_clicked_purchases' => $item['last_clicked_purchases'],
'roas' => $item['spend'] == 0 ? '-' : round($item['revenue'] / $item['spend'], 2) . 'X',
'spend' => '$' . number_format($item['spend'], 2), // 格式化支出
'impressions' => $item['impressions'],
'clicks' => $item['clicks'],
'ctr' => $ctr, // CTR 字段
'adds_to_cart' => $item['adds_to_cart'],
'cost_per_atc' => $item['cost_per_atc'],
'purchases' => $item['purchases'],
'purchases_value' => '$' . number_format($item['purchases_value'], 2), // 格式化购买金额
'cost_per_purchase' => $item['purchases'] > 0 ? '$' . number_format(($item['spend'] / $item['purchases']), 2) : '$0.00',
'revenue' => '$' . number_format($item['revenue'], 2), // 格式化收入
'total_cost' => '$' . number_format($item['total_cost'], 2), // 格式化总成本
'conversion_rate' => $conversion_rate,
'net_profit' => $net_profit,
'net_profit_margin' => $net_profit_margin,
'net_profit_on_ad_spend' => $net_profit_on_ad_spend,
];
}, $campaigns->items());
// Pagination 数据
$pagination = [
'startIndex' => ($page - 1) * $pageSize,
'maxResults' => $pageSize,
'count' => $campaigns->total(),
'pageNo' => $campaigns->currentPage(),
'pageSize' => $pageSize,
'pages' => $campaigns->lastPage(),
];
return [
'pagination' => $pagination,
'statistics' => $statistics,
'data' => $result,
];
}
/**
* 获取广告组列表
*/
public static function getAdsetList($platformType, $customerIds, $page, $pageSize, $keyword, $startDate = null, $endDate = null, $status = 0)
{
// 检查 customerIds 是否为空,直接返回空结构
if (empty($customerIds)) {
return [
'pagination' => [
'startIndex' => 0,
'maxResults' => $pageSize,
'count' => 0,
'pageNo' => $page,
'pageSize' => $pageSize,
'pages' => 0,
],
'statistics' => [
],
'data' => [],
];
}
// 动态构建日期条件
$dateCondition = '';
if ($startDate && $endDate) {
$dateCondition = "d.date BETWEEN '{$startDate}' AND '{$endDate}'";
}
// 基础查询:广告组和日数据表联接
$query = BpsAdSet::alias('s')
->cache(false) // 强制不使用缓存
->leftJoin('bps.bps_ads_insights d', "s.ad_set_id = d.ad_set_id AND s.platform_type = d.platform AND {$dateCondition}")
->leftJoin('bps.bps_ads_campaign c', 's.campaign_id = c.campaign_id') // 联接广告系列表
->field('s.ad_set_id, s.status as status, s.name, s.account_id,s.platform_type,c.name as campaign_name,
COALESCE(SUM(d.assisted_purchases), 0) as assisted_purchases,
COALESCE(SUM(d.last_clicked_purchases), 0) as last_clicked_purchases,
COALESCE(SUM(d.clicks), 0) as clicks,
COALESCE(SUM(d.spend) / 1000000, 0) as spend,
COALESCE(SUM(d.impressions), 0) as impressions,
COALESCE(SUM(d.adds_to_cart), 0) as adds_to_cart,
COALESCE(SUM(d.cost_per_atc), 0) as cost_per_atc,
COALESCE(SUM(d.purchases), 0) as purchases,
COALESCE(SUM(d.purchases_value), 0) as purchases_value,
COALESCE(SUM(d.revenue), 0) as revenue,
COALESCE(SUM(d.total_cost), 0) as total_cost,
-1 as conversion_rate, -1 as roas, -1 as ctr,-1 as net_profit,-1 as net_profit_margin,-1 as net_profit_on_ad_spend')
->group('s.ad_set_id, s.status, s.account_id, s.name,s.platform_type,c.name')
->where('s.account_id', 'in', $customerIds); // 添加 customerIds 条件
if ($status !== 0) {
$query->where('s.status', '=', $status);
}
// 添加关键字过滤条件
$query->where(function ($query) use ($keyword, $platformType) {
if ($keyword) {
$query->where('s.name', 'like', '%' . $keyword . '%');
}
if ($platformType) {
$platformType = (int)$platformType;
$query->where('s.platform_type', '=', $platformType);
}
});
// 获取所有符合条件的数据(不分页)
$allAdsets = $query->select()->toArray();
$total_spend = array_sum(array_column($allAdsets, 'spend'));
$total_cost = array_sum(array_column($allAdsets, 'total_cost'));
$total_impressions = array_sum(array_column($allAdsets, 'impressions'));
$total_clicks = array_sum(array_column($allAdsets, 'clicks'));
$total_purchases_value = array_sum(array_column($allAdsets, 'purchases_value'));
$total_revenue = array_sum(array_column($allAdsets, 'revenue'));
$total_purchases = array_sum(array_column($allAdsets, 'purchases'));
$cost_per_purchase = $total_purchases == 0 ? 0 : round($total_spend / $total_purchases, 2);
// 汇总统计数据
$statistics = [
'assisted_purchases' => array_sum(array_column($allAdsets, 'assisted_purchases')),
'last_clicked_purchases' => array_sum(array_column($allAdsets, 'last_clicked_purchases')),
'roas' => $total_spend == 0 ? '-' : round($total_revenue / $total_spend, 2) . 'X',
'amount_spend' => '$' . number_format($total_spend, 2) ?: '$0.00', // 格式化支出
'clicks' => $total_clicks,
'impressions' => array_sum(array_column($allAdsets, 'impressions')),
'adds_to_cart' => array_sum(array_column($allAdsets, 'adds_to_cart')),
'cost_per_atc' => array_sum(array_column($allAdsets, 'cost_per_atc')),
'purchases' => array_sum(array_column($allAdsets, 'purchases')),
'purchases_value' => array_sum(array_column($allAdsets, 'purchases_value')),
'cost_per_purchase' => '$' . number_format($cost_per_purchase, 2) ?: '$0.00',
'revenue' => '$' . number_format(array_sum(array_column($allAdsets, 'revenue')), 2) ?: '$0.00', // 格式化收入
'total_cost' => '$' . number_format($total_cost, 2) ?: '$0.00', // 格式化总成本
'conversion_rate' => $total_clicks == 0 ? '-' : round(($total_purchases / $total_clicks) * 100, 2) . '%', // 转换率
'net_profit' => ($total_revenue - $total_cost) >= 0 ? '+$' . number_format($total_revenue - $total_cost, 2) : '-$' . number_format($total_revenue - $total_cost, 2), // 净利润
'net_profit_margin' => $total_revenue == 0 ? '-' : round(($total_revenue - $total_cost) / $total_revenue, 2) * 100 . '%', // 净利润率
'net_profit_on_ad_spend' => $total_spend == 0 ? '-' : round(($total_revenue - $total_cost) / $total_spend, 2), // 广告支出净利润
// 计算总的 CTR
'ctr' => ($total_impressions > 0) ? number_format(($total_clicks / $total_impressions) * 100, 2) . '%' : '-', // 格式化为百分比
];
// 获取分页数据
$adsets = $query->paginate($pageSize, false, ['page' => $page]);
// 确保数据格式统一
$result = array_map(function ($item) {
// CTR 的计算:点击率 = 点击数 / 展示数
$ctr = $item['impressions'] > 0 ? number_format(($item['clicks'] / $item['impressions']) * 100, 2) . '%' : '-';
// Conversion Rate 的计算:转换率 = 购买数 / 点击数
$conversion_rate = $item['clicks'] > 0 ? round(($item['purchases'] / $item['clicks']) * 100, 2) . '%' : '-';
// Net Profit 的计算:净利润 = 收入 - 总成本
$net_profit = ($item['revenue'] - $item['total_cost']) >= 0 ? '+$' . number_format($item['revenue'] - $item['total_cost'], 2) : '-$' . number_format($item['revenue'] - $item['total_cost'], 2);
// Net Profit Margin 的计算:净利润率 = 净利润 / 收入
$net_profit_margin = $item['revenue'] > 0 ? round(($item['revenue'] - $item['total_cost']) / $item['revenue'], 2) * 100 . '%' : '-';
// Net Profit on Ad Spend 的计算:广告支出净利润 = (收入 - 总成本) / 广告支出
$net_profit_on_ad_spend = $item['spend'] > 0 ? round(($item['revenue'] - $item['total_cost']) / $item['spend'], 2) : '-';
return [
'id' => $item['ad_set_id'],
'platform_type' => $item['platform_type'],
'account_id' => $item['account_id'], // 映射为 customer_id
'name' => $item['name'] ?: '-', // 若 name 为空则显示 '-'
'campaign_name' => $item['campaign_name'] ?: '-', // 若 name 为空则显示 '-'
'status' => $item['status'],
'assisted_purchases' => $item['assisted_purchases'],
'last_clicked_purchases' => $item['last_clicked_purchases'],
'roas' => $item['spend'] == 0 ? '-' : round($item['purchases_value'] / $item['spend'], 2) . 'X',
'spend' => '$' . number_format($item['spend'], 2), // 格式化支出
'impressions' => $item['impressions'],
'clicks' => $item['clicks'],
'ctr' => $ctr, // CTR 字段
'adds_to_cart' => $item['adds_to_cart'],
'cost_per_atc' => $item['cost_per_atc'],
'purchases' => $item['purchases'],
'purchases_value' => '$' . number_format($item['purchases_value'], 2), // 格式化购买金额
'cost_per_purchase' => $item['purchases'] > 0 ? '$' . number_format(($item['spend'] / $item['purchases']), 2) : '$0.00',
'revenue' => '$' . number_format($item['revenue'], 2), // 格式化收入
'total_cost' => '$' . number_format($item['total_cost'], 2), // 格式化总成本
'conversion_rate' => $conversion_rate, // 没有提供有效的计算,保持为 '-'
'net_profit' => $net_profit, // 没有提供 net_profit 计算,保持为 '-'
'net_profit_margin' => $net_profit_margin, // 没有提供 net_profit_margin 计算,保持为 '-'
'net_profit_on_ad_spend' => $net_profit_on_ad_spend, // 没有提供 net_profit_on_ad_spend 计算,保持为 '-'
];
}, $adsets->items());
// Pagination 数据
$pagination = [
'startIndex' => ($page - 1) * $pageSize,
'maxResults' => $pageSize,
'count' => $adsets->total(),
'pageNo' => $adsets->currentPage(),
'pageSize' => $pageSize,
'pages' => $adsets->lastPage(),
];
return [
'pagination' => $pagination,
'statistics' => $statistics,
'data' => $result,
];
}
public static function getAdList($platformType, $customerIds, $page, $pageSize, $keyword, $startDate = null, $endDate = null, $status = 0)
{
// 检查 customerIds 是否为空,直接返回空结构
if (empty($customerIds)) {
return [
'pagination' => [
'startIndex' => 0,
'maxResults' => $pageSize,
'count' => 0,
'pageNo' => $page,
'pageSize' => $pageSize,
'pages' => 0,
],
'statistics' => [],
'data' => [],
];
}
// 动态构建日期条件
$dateCondition = '';
if ($startDate && $endDate) {
$dateCondition = "d.date BETWEEN '{$startDate}' AND '{$endDate}'";
}
// 基础查询:广告和日数据表联接
$query = BpsAdAd::alias('a')
->cache(false) // 强制不使用缓存
->leftJoin('bps.bps_ads_insights d', "a.ad_id = d.ad_id AND a.platform_type = d.platform AND {$dateCondition}")
->leftJoin('bps.bps_ads_set s', 'a.ad_set_id = s.ad_set_id') // 联接广告组表,获取 ad_set_name
->field('a.ad_id, a.status as status, a.name, a.account_id,a.platform_type,s.name as ad_set_name,
COALESCE(SUM(d.assisted_purchases), 0) as assisted_purchases,
COALESCE(SUM(d.last_clicked_purchases), 0) as last_clicked_purchases,
COALESCE(SUM(d.clicks), 0) as clicks,
COALESCE(SUM(d.spend) / 1000000, 0) as spend,
COALESCE(SUM(d.impressions), 0) as impressions,
COALESCE(SUM(d.adds_to_cart), 0) as adds_to_cart,
COALESCE(SUM(d.cost_per_atc), 0) as cost_per_atc,
COALESCE(SUM(d.purchases), 0) as purchases,
COALESCE(SUM(d.purchases_value), 0) as purchases_value,
COALESCE(SUM(d.revenue), 0) as revenue,
COALESCE(SUM(d.total_cost), 0) as total_cost,
-1 as conversion_rate, -1 as roas, -1 as ctr, -1 as net_profit, -1 as net_profit_margin, -1 as net_profit_on_ad_spend')
->group('a.ad_id, a.status, a.account_id, a.name,a.platform_type, s.name')
->where('a.account_id', 'in', $customerIds); // 添加 customerIds 条件
// 如果传入了 status 参数,按状态筛选
if ($status !== 0) {
$query->where('a.status', '=', $status);
}
// 添加关键字过滤条件
$query->where(function ($query) use ($keyword, $platformType) {
if ($keyword) {
$query->where('a.name', 'like', '%' . $keyword . '%');
}
if ($platformType) {
$platformType = (int)$platformType;
$query->where('a.platform_type', '=', $platformType);
}
});
// 获取所有符合条件的数据(不分页)
$allAds = $query->select()->toArray();
// 汇总统计数据
$total_spend = array_sum(array_column($allAds, 'spend'));
$total_cost = array_sum(array_column($allAds, 'total_cost'));
$total_impressions = array_sum(array_column($allAds, 'impressions'));
$total_clicks = array_sum(array_column($allAds, 'clicks'));
$total_purchases_value = array_sum(array_column($allAds, 'purchases_value'));
$total_revenue = array_sum(array_column($allAds, 'revenue'));
$total_purchases = array_sum(array_column($allAds, 'purchases'));
$cost_per_purchase = $total_purchases == 0 ? 0 : round($total_spend / $total_purchases, 2);
$statistics = [
'assisted_purchases' => array_sum(array_column($allAds, 'assisted_purchases')),
'last_clicked_purchases' => array_sum(array_column($allAds, 'last_clicked_purchases')),
'roas' => $total_spend == 0 ? '-' : round($total_purchases_value / $total_spend, 2),
'amount_spend' => '$' . number_format($total_spend, 2) ?: '$0.00', // 格式化支出
'clicks' => $total_clicks,
'impressions' => array_sum(array_column($allAds, 'impressions')),
'adds_to_cart' => array_sum(array_column($allAds, 'adds_to_cart')),
'cost_per_atc' => array_sum(array_column($allAds, 'cost_per_atc')),
'purchases' => array_sum(array_column($allAds, 'purchases')),
'purchases_value' => array_sum(array_column($allAds, 'purchases_value')),
'cost_per_purchase' => '$' . number_format($cost_per_purchase, 2) ?: '$0.00',
'revenue' => '$' . number_format(array_sum(array_column($allAds, 'revenue')), 2) ?: '$0.00', // 格式化收入
'total_cost' => '$' . number_format($total_cost, 2) ?: '$0.00', // 格式化总成本
'conversion_rate' => $total_clicks == 0 ? '-' : round(($total_purchases / $total_clicks) * 100, 2) . '%', // 转换率
'net_profit' => ($total_revenue - $total_cost) >= 0 ? '+$' . number_format($total_revenue - $total_cost, 2) : '-$' . number_format($total_revenue - $total_cost, 2), // 净利润
'net_profit_margin' => $total_revenue == 0 ? '-' : round(($total_revenue - $total_cost) / $total_revenue, 2) * 100 . '%', // 净利润率
'net_profit_on_ad_spend' => $total_spend == 0 ? '-' : round(($total_revenue - $total_cost) / $total_spend, 2),
'ctr' => ($total_impressions > 0) ? number_format(($total_clicks / $total_impressions) * 100, 2) . '%' : '-', // 格式化为百分比
];
// 获取分页数据
$ads = $query->paginate($pageSize, false, ['page' => $page]);
// 确保数据格式统一
$result = array_map(function ($item) {
$ctr = $item['impressions'] > 0 ? number_format(($item['clicks'] / $item['impressions']) * 100, 2) . '%' : '-'; // 格式化为百分比
return [
'id' => $item['ad_id'],
'platform_type' => $item['platform_type'],
'account_id' => $item['account_id'], // 映射为 customer_id
'name' => $item['name'] ?: '-', // 若 name 为空则显示 '-'
'ad_set_name' => $item['ad_set_name'] ?: '-', // 若 name 为空则显示 '-'
'status' => $item['status'],
'assisted_purchases' => $item['assisted_purchases'],
'last_clicked_purchases' => $item['last_clicked_purchases'],
'roas' => $item['spend'] == 0 ? '-' : round($item['purchases_value'] / $item['spend'], 2),
'spend' => '$' . number_format($item['spend'], 2), // 格式化支出
'impressions' => $item['impressions'],
'clicks' => $item['clicks'],
'ctr' => $ctr, // CTR 字段
'adds_to_cart' => $item['adds_to_cart'],
'cost_per_atc' => $item['cost_per_atc'],
'purchases' => $item['purchases'],
'purchases_value' => '$' . number_format($item['purchases_value'], 2), // 格式化购买金额
'cost_per_purchase' => $item['purchases'] > 0 ? '$' . number_format(($item['spend'] / $item['purchases']), 2) : '$0.00',
'revenue' => '$' . number_format($item['revenue'], 2), // 格式化收入
'total_cost' => '$' . number_format($item['total_cost'], 2), // 格式化总成本
'conversion_rate' => '-', // 没有提供有效的计算,保持为 '-'
'net_profit' => '-', // 没有提供 net_profit 计算,保持为 '-'
'net_profit_margin' => '-', // 没有提供 net_profit_margin 计算,保持为 '-'
'net_profit_on_ad_spend' => '-', // 没有提供 net_profit_on_ad_spend 计算,保持为 '-'
];
}, $ads->items());
// Pagination 数据
$pagination = [
'startIndex' => ($page - 1) * $pageSize,
'maxResults' => $pageSize,
'count' => $ads->total(),
'pageNo' => $ads->currentPage(),
'pageSize' => $pageSize,
'pages' => $ads->lastPage(),
];
return [
'pagination' => $pagination,
'statistics' => $statistics,
'data' => $result,
];
}
public static function getThirdUserList($platformType, $userIds, $page, $pageSize, $keyword, $startDate = null, $endDate = null)
{
// 检查 userIds 是否为空,直接返回空结构
if (empty($userIds)) {
return [
'pagination' => [
'startIndex' => 0,
'maxResults' => $pageSize,
'count' => 0,
'pageNo' => $page,
'pageSize' => $pageSize,
'pages' => 0,
],
'statistics' => [],
'data' => [],
];
}
// dump($userIds);
// 动态构建日期条件
$dateCondition = '1=1'; // 默认没有日期限制
if ($startDate && $endDate) {
$dateCondition = "d.date BETWEEN '{$startDate}' AND '{$endDate}'";
}
// u.user_id as third_user_id, u.third_type, u.facebook_user_id,
// a.advertiser_id, a.advertiser_name, a.google_manager, a.google_test_account,
// 基础查询:第三方用户和广告商数据表联接
$query = ThirdUser::alias('u')
->cache(false) // 强制不使用缓存
->leftJoin('bps.bps_third_user_advertiser a', "u.id = a.doc_")
->field('u.id as user_id,u.third_type,a.advertiser_name,
COALESCE(SUM(d.assisted_purchases), 0) as assisted_purchases,
COALESCE(SUM(d.last_clicked_purchases), 0) as last_clicked_purchases,
COALESCE(SUM(d.spend) / 1000000, 0) as spend,
COALESCE(SUM(d.impressions), 0) as impressions,
COALESCE(SUM(d.clicks), 0) as clicks,
COALESCE(SUM(d.adds_to_cart), 0) as adds_to_cart,
COALESCE(SUM(d.cost_per_atc), 0) as cost_per_atc,
COALESCE(SUM(d.purchases), 0) as purchases,
COALESCE(SUM(d.purchases_value), 0) as purchases_value,
COALESCE(SUM(d.revenue), 0) as revenue,
COALESCE(SUM(d.total_cost), 0) as total_cost')
->leftJoin('bps.bps_ads_insights d', "a.advertiser_id = d.account_id AND {$dateCondition}")
->where('u.id', 'in', $userIds); // 添加 userIds 条件
// 添加关键字过滤条件
$query->where(function ($query) use ($keyword, $platformType) {
if ($keyword) {
$query->where('a.advertiser_name', 'like', '%' . $keyword . '%');
}
if ($platformType) {
$query->where('d.platform', '=', $platformType);
}
});
// 添加 GROUP BY 子句
// $query->group('u.id, u.user_id, u.third_type, u.facebook_user_id,
// a.advertiser_id, a.advertiser_name, a.google_manager, a.google_test_account');
$query->group('u.id,u.third_type,a.advertiser_name');
// 调试打印 SQL
// 获取所有符合条件的数据(不分页)
$allUsers = $query->select()->toArray();
// dump($query->getLastSql());
//
// dump($allUsers);
// 汇总统计数据
$total_spend = array_sum(array_column($allUsers, 'spend'));
$total_impressions = array_sum(array_column($allUsers, 'impressions'));
$total_clicks = array_sum(array_column($allUsers, 'clicks'));
$total_adds_to_cart = array_sum(array_column($allUsers, 'adds_to_cart'));
$total_cost_per_atc = array_sum(array_column($allUsers, 'cost_per_atc'));
$total_purchases = array_sum(array_column($allUsers, 'purchases'));
$total_purchases_value = array_sum(array_column($allUsers, 'purchases_value'));
$total_revenue = array_sum(array_column($allUsers, 'revenue'));
$total_cost = array_sum(array_column($allUsers, 'total_cost'));
// 计算 ROAS
$roas = $total_spend == 0 ? '-' : round($total_purchases_value / $total_spend, 2);
$cost_per_purchase = $total_purchases == 0 ? 0 : round($total_spend / $total_purchases, 2);
// 计算 CTR
$ctr = $total_impressions > 0 ? number_format(($total_clicks / $total_impressions) * 100, 2) . '%' : '-';
// 汇总统计字段初始化
$statistics = [
'assisted_purchases' => 0,
'last_clicked_purchases' => 0,
'roas' => $total_spend == 0 ? '-' : round($total_purchases_value / $total_spend, 2),
'amount_spend' => '$' . number_format($total_spend, 2) ?: '$0.00', // 格式化支出
'clicks' => $total_clicks,
'impressions' => $total_impressions,
'adds_to_cart' => $total_adds_to_cart,
'cost_per_atc' => $total_cost_per_atc,
'purchases' => $total_purchases,
'purchases_value' => '$' . number_format($total_purchases_value, 2) ?: '$0.00', // 格式化销售额$total_purchases_value,
'cost_per_purchase' => '$' . number_format($cost_per_purchase, 2) ?: '$0.00', // 格式化销售额$total_purchases_value,
'revenue' => '$' . number_format($total_revenue, 2) ?: '$0.00', // 格式化收入
'total_cost' => '$' . number_format($total_cost, 2) ?: '$0.00', // 格式化总成本
'conversion_rate' => '-', // 没有计算逻辑,保持为 '-'
'net_profit' => '-', // 没有提供 net_profit保持为 '-'
'net_profit_margin' => '-', // 没有提供 net_profit_margin保持为 '-'
'net_profit_on_ad_spend' => '-', // 没有提供 net_profit_on_ad_spend保持为 '-'
'ctr' => $ctr, // CTR 字段
];
// 获取分页数据
$users = $query->paginate($pageSize, false, ['page' => $page]);
// 按照 third_user_id 聚合统计字段
$aggregatedUsers = [];
// 遍历每个用户的统计数据
foreach ($users->items() as $item) {
$thirdUserId = $item['user_id'];
if (!isset($aggregatedUsers[$thirdUserId])) {
$aggregatedUsers[$thirdUserId] = [
'user_id' => $item['user_id'],
'platform_type' => self::getPlatformType($item['third_type']),
'advertiser_name' => $item['advertiser_name'],
'assisted_purchases' => 0,
'last_clicked_purchases' => 0,
'roas' => 0,
'spend' => 0,
'impressions' => 0,
'clicks' => 0,
'adds_to_cart' => 0,
'cost_per_atc' => 0,
'purchases' => 0,
'purchases_value' => 0,
'cost_per_purchase' => 0,
'revenue' => 0,
'total_cost' => 0,
'net_profit' => '-', // 没有提供 net_profit保持为 '-'
'net_profit_margin' => '-', // 没有提供 net_profit_margin保持为 '-'
'net_profit_on_ad_spend' => '-', // 没有提供 net_profit_on_ad_spend保持为 '-'
];
}
// 汇总统计
$aggregatedUsers[$thirdUserId]['spend'] += $item['spend'];
$aggregatedUsers[$thirdUserId]['impressions'] += $item['impressions'];
$aggregatedUsers[$thirdUserId]['clicks'] += $item['clicks'];
$aggregatedUsers[$thirdUserId]['adds_to_cart'] += $item['adds_to_cart'];
$aggregatedUsers[$thirdUserId]['cost_per_atc'] += $item['cost_per_atc'];
$aggregatedUsers[$thirdUserId]['purchases'] += $item['purchases'];
$aggregatedUsers[$thirdUserId]['purchases_value'] += $item['purchases_value'];
$aggregatedUsers[$thirdUserId]['revenue'] += $item['revenue'];
$aggregatedUsers[$thirdUserId]['total_cost'] += $item['total_cost'];
$aggregatedUsers[$thirdUserId]['assisted_purchases'] += $item['assisted_purchases'];
$aggregatedUsers[$thirdUserId]['last_clicked_purchases'] += $item['last_clicked_purchases'];
}
// 计算统计口径字段ROAS, CTR等
foreach ($aggregatedUsers as $userId => $data) {
$total_spend = $data['spend'];
$total_purchases_value = $data['purchases_value'];
$total_revenue = $data['revenue'];
$total_cost = $data['total_cost'];
$total_clicks = $data['clicks'];
$total_impressions = $data['impressions'];
// $total_adds_to_cart = $data['adds_to_cart'];
// $total_cost_per_atc = $data['cost_per_atc'];
// $total_purchases = $data['purchases'];
// $total_assisted_purchases = $data['assisted_purchases'];
// $total_last_clicked_purchases = $data['last_clicked_purchases'];
// 计算 ROAS, CTR 和其他需要的字段
$aggregatedUsers[$userId]['roas'] = $total_spend == 0 ? '-' : round($total_purchases_value / $total_spend, 2);
$aggregatedUsers[$userId]['amount_spend'] = '$' . number_format($total_spend, 2) ?: '$0.00';
$aggregatedUsers[$userId]['purchases_value'] = '$' . number_format($total_purchases_value, 2) ?: '$0.00';
$aggregatedUsers[$userId]['cost_per_purchase'] = $data['purchases'] == 0 ? '$0.00' : '$' . number_format($total_purchases_value / $data['purchases'], 2);
$aggregatedUsers[$userId]['revenue'] = '$' . number_format($total_revenue, 2) ?: '$0.00';
$aggregatedUsers[$userId]['total_cost'] = '$' . number_format($total_cost, 2) ?: '$0.00';
$aggregatedUsers[$userId]['conversion_rate'] = $total_impressions == 0 ? '-' : round(($total_clicks / $total_impressions) * 100, 2) . '%';
$aggregatedUsers[$userId]['ctr'] = $total_impressions == 0 ? '-' : round(($total_clicks / $total_impressions) * 100, 2) . '%';
}
// 使用 array_values 移除键名
$dataWithoutKeys = array_values($aggregatedUsers);
// 最终分页信息
$pagination = [
'startIndex' => ($page - 1) * $pageSize,
'maxResults' => $pageSize,
'count' => $users->total(),
'pageNo' => $page,
'pageSize' => $pageSize,
'pages' => ceil($users->total() / $pageSize),
];
return [
'pagination' => $pagination,
'statistics' => $statistics,
'data' => $dataWithoutKeys,
];
}
public function getCreativeInsightData($platformType, $customerIds, $page, $pageSize, $keyword, $startDate = null, $endDate = null)
{
// 1. 创建查询对象,初始化 BpsAdCreativeInsight 查询
$creativeDataQuery = BpsAdCreativeInsight::alias('i')
->join('bps.bps_ads_creative c', 'i.creative_id = c.creative_id', 'LEFT'); // 联接 bps_ads_creative 表
if ($platformType != 0) {
$creativeDataQuery->where('i.platform', $platformType); // 只有 platformType 不为 0 时才添加筛选
}
// 2. 日期范围筛选
if ($startDate && $endDate) {
$creativeDataQuery->whereBetween('i.date', [$startDate, $endDate]);
}
// 3. 客户 ID 过滤(如果提供了)
if (!empty($customerIds)) {
$creativeDataQuery->whereIn('i.account_id', $customerIds);
} else {
return [
'data' => [],
'total' => 0,
'statistics' => [],
'pagination' => [
'startIndex' => 0,
'maxResults' => $pageSize,
'count' => 0,
'pageNo' => 1,
'pageSize' => $pageSize
]
];
}
// 4. 关键词过滤
if ($keyword) {
$creativeDataQuery->where('c.name', 'like', '%' . $keyword . '%'); // 在 bps_ads_creative 表中查找关键词
}
// 5. 数据聚合(按 creative_id 和其他字段)
$creativeDataQuery->group('i.creative_id, i.platform, i.account_id, c.name, c.type,c.url, c.thumbnail_url') // 按需要的字段分组
->field([
'i.creative_id',
'c.name AS creative_name', // 从 bps_ads_creative 表中选择 name
'c.url AS creative_url', // 从 bps_ads_creative 表中选择 url
'c.type AS creative_type', // 从 bps_ads_creative 表中选择 url
'c.thumbnail_url', // 从 bps_ads_creative 表中选择 thumbnail_url
ThinkDb::raw('SUM(i.spend) AS total_spend'),
ThinkDb::raw('SUM(i.purchases_value) AS total_conversions_value'),
ThinkDb::raw('SUM(i.purchases) AS total_conversions'),
ThinkDb::raw('SUM(i.impressions) AS total_impressions'),
ThinkDb::raw('SUM(i.clicks) AS total_clicks'),
ThinkDb::raw('SUM(i.video_25) AS video_25'),
ThinkDb::raw('SUM(i.video_50) AS video_50'),
ThinkDb::raw('SUM(i.video_75) AS video_75'),
ThinkDb::raw('SUM(i.video_100) AS video_100'),
ThinkDb::raw('SUM(i.hold_rate) AS hold_rate')
]);
// 6. 执行查询并获取聚合结果
$aggregatedData = $creativeDataQuery->select();
// 打印调试 SQL 查询
//$sql = $creativeDataQuery->getLastSql();
// dump($sql);
// 7. 初始化广告创意的汇总数据和统计数据
$creativeSummaryData = [];
$statisticsData = $this->initializeStatistics();
// 8. 遍历查询结果并计算每个 creative 的相关统计数据
foreach ($aggregatedData as $creativeData) {
// 初始化该 creative_id 的数据(如果不存在)
if (!isset($creativeSummaryData[$creativeData->creative_id])) {
$creativeSummaryData[$creativeData->creative_id] = [
'creative_id' => $creativeData->creative_id,
'creative' => $creativeData->creative_name, // 使用联接查询中的 creative_name
'creative_type' => $creativeData->creative_type, // 使用联接查询中的 creative_name
'creative_url' => $creativeData->creative_url ?: '', // 使用联接查询中的 creative_url
'thumbnail_url' => $creativeData->thumbnail_url ?: '', // 使用联接查询中的 thumbnail_url
'title' => $creativeData->title ?: '', // 使用联接查询中的 title
'spend' => 0,
'purchase_value' => '-',
'roas' => 0,
'cpa' => '-',
'cpc_link_click' => '-',
'cpm' => '-',
'cpc_all' => '-',
'aov' => '-',
'click_to_atc_ratio' => '-',
'atc_to_purchase_ratio' => '-',
'purchases' => '-',
'first_frame_retention' => '-',
'thumbstop' => '-',
'ctr_outbound' => '-',
'click_to_purchase' => '-',
'ctr_all' => '-',
'video_plays_25_rate' => '-',
'video_plays_50_rate' => '-',
'video_plays_75_rate' => '-',
'video_plays_100_rate' => '-',
'hold_rate' => '-',
'total_conversions_value' => 0,
'total_conversions' => 0,
'total_impressions' => 0,
'ad_count' => 0
];
}
// 更新该 creative_id 的统计数据
$creativeSummaryData[$creativeData->creative_id]['spend'] += $creativeData->total_spend;
$creativeSummaryData[$creativeData->creative_id]['total_conversions_value'] += $creativeData->total_conversions_value;
$creativeSummaryData[$creativeData->creative_id]['total_impressions'] += $creativeData->total_impressions;
// 汇总总体统计数据
$statisticsData['spend'] += $creativeData->total_spend;
$statisticsData['conversions_value'] += $creativeData->total_conversions_value;
$statisticsData['impressions'] += $creativeData->total_impressions;
// 计算 ROAS
$roas = $creativeSummaryData[$creativeData->creative_id]['spend'] > 0
? $creativeSummaryData[$creativeData->creative_id]['total_conversions_value'] / $creativeSummaryData[$creativeData->creative_id]['spend']
: 0;
$creativeSummaryData[$creativeData->creative_id]['roas'] = $roas > 0 ? number_format($roas, 2) . 'X' : '-';
// 填充广告计数
$creativeSummaryData[$creativeData->creative_id]['ad_count'] = rand(10, 200); // 每个 creative_id 对应一个广告
}
// 9. 返回分页数据
return [
'data' => array_values($creativeSummaryData),
'total' => count($creativeSummaryData),
'statistics' => $statisticsData, // 汇总的统计数据
'pagination' => [
'startIndex' => ($page - 1) * $pageSize,
'maxResults' => $pageSize,
'count' => count($creativeSummaryData),
'pageNo' => $page,
'pageSize' => $pageSize,
'pages' => ceil(count($creativeSummaryData) / $pageSize)
]
];
}
/**
* 初始化统计数据
*/
private function initializeStatistics()
{
return [
'conversions_value' => 0,
'impressions' => 0,
'spend' => 0,
'purchase_value' => '-', // 可根据需求进一步计算
'roas' => 0, // 可以根据需要计算总体 ROAS
'cpa' => '-',
'cpc_link_click' => '-',
'cpm' => '-',
'cpc_all' => '-',
'aov' => '-',
'click_to_atc_ratio' => '-',
'atc_to_purchase_ratio' => '-',
'purchases' => '-',
'first_frame_retention' => '-',
'thumbstop' => '-',
'ctr_outbound' => '-',
'click_to_purchase' => '-',
'ctr_all' => '-',
'video_plays_25_rate' => '-',
'video_plays_50_rate' => '-',
'video_plays_75_rate' => '-',
'video_plays_100_rate' => '-',
'hold_rate' => '-'
// Add other stats as necessary
];
}
public function getAdcycleInsight($platformType, $customerIds, $cycle, $startDate = null, $endDate = null)
{
// 1. 查询全部数据集
$adcycleDataQuery = BpsAdInsight::alias('i');
if ($platformType != 0) {
$adcycleDataQuery->where('i.platform', $platformType); // 只有 platformType 不为 0 时才添加筛选
}
//dump($customerIds);
// 2. 客户 ID 过滤(如果提供了)
if (!empty($customerIds)) {
$adcycleDataQuery->whereIn('i.account_id', $customerIds);
} else {
return [
'data' => [],
'total' => 0
];
}
// 3. 时间范围筛选
if ($startDate && $endDate) {
$adcycleDataQuery->whereBetween('i.date', [$startDate, $endDate]);
}
// 4. 获取数据并按日期聚合
$adcycleData = $adcycleDataQuery->field([
'i.date',
ThinkDb::raw('COALESCE(SUM(i.spend) / 1000000, 0) as spend'),
ThinkDb::raw('COALESCE(SUM(i.revenue), 0) as revenue')
])
->group('i.date') // 按日期进行分组
->select();
// dump(ThinkDb::getLastSql());
// 5. 处理数据,按照周期进行分组
$processedData = $this->processDataByCycle($adcycleData->toArray(), $cycle);
// 6. 返回处理后的数据
return $processedData;
}
/**
* 按周期处理数据
*
* @param array $adcycleData 原始数据
* @param string $cycle 周期类型daily, weekly, monthly
* @return array 按照周期分组后的数据
*/
private function processDataByCycle($adcycleData, $cycle)
{
// 排序 adcycleData 数组,确保按照日期升序排列
usort($adcycleData, function ($a, $b) {
return $a['date'] <=> $b['date']; // 按照 'YYYYMMDD' 字符顺序排序
});
$groupedData = [];
foreach ($adcycleData as $data) {
// 根据周期类型,调整日期分组
switch ($cycle) {
case 1:
// 将 'YYYYMMDD' 格式的日期转换为 'YYYY-MM-DD'
$date = DateTime::createFromFormat('Ymd', $data['date']); // 转换为 DateTime 对象
$key = $date ? $date->format('Y-m-d') : ''; // 格式化为 'YYYY-MM-DD'
break;
case 2:
// 使用 ISO 周格式来分组
$key = $this->getWeekFromDate($data['date']);
break;
case 3:
// 按年-月格式分组
$key = $this->getMonthFromDate($data['date']);
break;
default:
throw new \InvalidArgumentException("Invalid cycle value. Use 'daily', 'weekly' or 'monthly'.");
}
// 汇总数据
if (!isset($groupedData[$key])) {
$groupedData[$key] = [
'spend' => 0,
'revenue' => 0,
// 'conversions_value' => 0,
// 'conversions' => 0,
// 'impressions' => 0,
// 'clicks' => 0
];
}
// 累加数据
$groupedData[$key]['spend'] += $data['spend'];
$groupedData[$key]['revenue'] += $data['revenue'];
// $groupedData[$key]['conversions_value'] += $data->purchases_value;
// $groupedData[$key]['conversions'] += $data->purchases;
// $groupedData[$key]['impressions'] += $data->impressions;
// $groupedData[$key]['clicks'] += $data->clicks;
}
// 格式化返回数据
$formattedData = [];
foreach ($groupedData as $key => $values) {
$roas = $values['spend'] > 0 ? $values['revenue'] / $values['spend'] : 0;
$formattedData[] = [
'date' => $key,
'spend' => round($values['spend'], 2),
'roas' => $roas > 0 ? round($roas, 2) : 0
];
}
// 如果没有数据,返回空数组并提供默认的分页信息
if (empty($formattedData)) {
return [
'data' => [],
'total' => 0,
];
}
// 返回格式化后的数据
return [
'data' => $formattedData,
'total' => count($formattedData),
];
}
/**
* 获取周数(基于 ISO 周格式)
*
* @param int $date 日期格式YYYYMMDD
* @return string ISO 周格式YYYY-Www
*/
private function getWeekFromDate($date)
{
// 将日期转换为 PHP DateTime 对象
$dateStr = (string)$date;
$dateObj = \DateTime::createFromFormat('Ymd', $dateStr);
// 获取 ISO 周格式
return $dateObj->format('o-W'). 'W';
}
/**
* 获取月分格式YYYY-MM
*
* @param int $date 日期格式YYYYMMDD
* @return string 月份格式YYYY-MM
*/
private function getMonthFromDate($date)
{
// 将日期转换为 PHP DateTime 对象
$dateStr = (string)$date;
$dateObj = \DateTime::createFromFormat('Ymd', $dateStr);
// 获取年-月格式
return $dateObj->format('Y-m');
}
public static function getPlatformType($thirdType)
{
$platformMapping = [
'facebook' => 1,
'google' => 2,
'tiktok' => 3,
];
return $platformMapping[$thirdType] ?? null; // 如果不存在的第三方类型,返回 null
}
}