From 368c45f0505d3ac6fba207a526c8cdf19cb9992f Mon Sep 17 00:00:00 2001 From: huangguancheng Date: Fri, 10 Jan 2025 21:50:48 +0800 Subject: [PATCH] =?UTF-8?q?creative=E5=9B=BE=E8=A1=A8=E7=9A=84=E6=97=A5?= =?UTF-8?q?=E6=9C=9F=E6=8E=92=E5=BA=8F=E3=80=81=E6=A0=BC=E5=BC=8F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/service/AdsInsightService.php | 107 +++++++++++++++++++----------- 1 file changed, 69 insertions(+), 38 deletions(-) diff --git a/app/service/AdsInsightService.php b/app/service/AdsInsightService.php index 3a76cc4..b22a607 100644 --- a/app/service/AdsInsightService.php +++ b/app/service/AdsInsightService.php @@ -13,6 +13,7 @@ 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; @@ -103,7 +104,7 @@ class AdsInsightService $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_purchases_value / $total_spend, 2), + '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, @@ -127,7 +128,16 @@ class AdsInsightService // 确保数据格式统一 $result = array_map(function ($item) { - $ctr = $item['impressions'] > 0 ? number_format(($item['clicks'] / $item['impressions']) * 100, 2) . '%' : '-'; // 格式化为百分比 + // 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'], @@ -136,7 +146,7 @@ class AdsInsightService '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), + 'roas' => $item['spend'] == 0 ? '-' : round($item['revenue'] / $item['spend'], 2) . 'X', 'spend' => '$' . number_format($item['spend'], 2), // 格式化支出 'impressions' => $item['impressions'], 'clicks' => $item['clicks'], @@ -148,10 +158,10 @@ class AdsInsightService '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 计算,保持为 '-' + '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()); @@ -243,6 +253,7 @@ class AdsInsightService $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); @@ -250,7 +261,7 @@ class AdsInsightService $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_purchases_value / $total_spend, 2), + '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')), @@ -261,10 +272,10 @@ class AdsInsightService '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' => '-', // 没有计算逻辑,保持为 '-' - 'net_profit' => '-', // 没有计算 net_profit,保持为 '-' - 'net_profit_margin' => '-', // 没有计算 net_profit_margin,保持为 '-' - 'net_profit_on_ad_spend' => '-', // 没有计算 net_profit_on_ad_spend,保持为 '-' + '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) . '%' : '-', // 格式化为百分比 ]; @@ -274,7 +285,16 @@ class AdsInsightService // 确保数据格式统一 $result = array_map(function ($item) { - $ctr = $item['impressions'] > 0 ? number_format(($item['clicks'] / $item['impressions']) * 100, 2) . '%' : '-'; // 格式化为百分比 + // 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'], @@ -284,7 +304,7 @@ class AdsInsightService '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), + 'roas' => $item['spend'] == 0 ? '-' : round($item['purchases_value'] / $item['spend'], 2) . 'X', 'spend' => '$' . number_format($item['spend'], 2), // 格式化支出 'impressions' => $item['impressions'], 'clicks' => $item['clicks'], @@ -296,10 +316,10 @@ class AdsInsightService '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 计算,保持为 '-' + '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()); @@ -388,6 +408,7 @@ class AdsInsightService $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); @@ -405,10 +426,10 @@ class AdsInsightService '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' => '-', // 没有计算逻辑,保持为 '-' - 'net_profit' => '-', // 没有计算 net_profit,保持为 '-' - 'net_profit_margin' => '-', // 没有计算 net_profit_margin,保持为 '-' - 'net_profit_on_ad_spend' => '-', // 没有计算 net_profit_on_ad_spend,保持为 '-' + '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) . '%' : '-', // 格式化为百分比 ]; @@ -669,9 +690,9 @@ class AdsInsightService // 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 时才添加筛选 - } + if ($platformType != 0) { + $creativeDataQuery->where('i.platform', $platformType); // 只有 platformType 不为 0 时才添加筛选 + } // 2. 日期范围筛选 if ($startDate && $endDate) { @@ -702,11 +723,12 @@ class AdsInsightService } // 5. 数据聚合(按 creative_id 和其他字段) - $creativeDataQuery->group('i.creative_id, i.platform, i.account_id, c.name, c.url, c.thumbnail_url') // 按需要的字段分组 + $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'), @@ -737,9 +759,10 @@ class AdsInsightService $creativeSummaryData[$creativeData->creative_id] = [ 'creative_id' => $creativeData->creative_id, 'creative' => $creativeData->creative_name, // 使用联接查询中的 creative_name - 'creative_url' => $creativeData->creative_url?: '', // 使用联接查询中的 creative_url - 'thumbnail_url' => $creativeData->thumbnail_url?: '', // 使用联接查询中的 thumbnail_url - 'title' => $creativeData->title?: '', // 使用联接查询中的 title + '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, @@ -841,8 +864,10 @@ class AdsInsightService public function getAdcycleInsight($platformType, $customerIds, $cycle, $startDate = null, $endDate = null) { // 1. 查询全部数据集 - $adcycleDataQuery = BpsAdInsight::alias('i') - ->where('i.platform', $platformType); // 根据 platform 筛选 + $adcycleDataQuery = BpsAdInsight::alias('i'); + if ($platformType != 0) { + $adcycleDataQuery->where('i.platform', $platformType); // 只有 platformType 不为 0 时才添加筛选 + } //dump($customerIds); // 2. 客户 ID 过滤(如果提供了) if (!empty($customerIds)) { @@ -871,7 +896,7 @@ class AdsInsightService // dump(ThinkDb::getLastSql()); // 5. 处理数据,按照周期进行分组 - $processedData = $this->processDataByCycle($adcycleData, $cycle); + $processedData = $this->processDataByCycle($adcycleData->toArray(), $cycle); // 6. 返回处理后的数据 return $processedData; @@ -886,21 +911,27 @@ class AdsInsightService */ 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: - $key = $data->date; // 按日期直接分组 + // 将 '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); + $key = $this->getWeekFromDate($data['date']); break; case 3: // 按年-月格式分组 - $key = $this->getMonthFromDate($data->date); + $key = $this->getMonthFromDate($data['date']); break; default: throw new \InvalidArgumentException("Invalid cycle value. Use 'daily', 'weekly' or 'monthly'."); @@ -919,8 +950,8 @@ class AdsInsightService } // 累加数据 - $groupedData[$key]['spend'] += $data->spend; - $groupedData[$key]['revenue'] += $data->revenue; + $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; @@ -965,7 +996,7 @@ class AdsInsightService $dateObj = \DateTime::createFromFormat('Ymd', $dateStr); // 获取 ISO 周格式 - return $dateObj->format('o-W'); + return $dateObj->format('o-W'). 'W'; } /**