webman_ad/app/service/GoogleAdsReportService.php
2025-01-06 19:41:46 +08:00

1189 lines
52 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\DayData;
use app\model\Campaign;
use app\model\AdGroup;
use app\model\Asset;
use app\model\AssetRelation;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use think\db\exception\DbException;
use think\facade\Db as ThinkDb;
use think\model\Collection;
class GoogleAdsReportService
{
// 状态映射数组
private static $statusMapping = [
2 => 'ENABLE', // 2 代表广告已启用
3 => 'PAUSED', // 3 代表广告待审核等状态
// 其他状态可以继续添加
];
/**
* 获取广告列表
*/
public static function getAdList($customerIds, $page, $pageSize, $keyword, $dateRange, $startDate = null, $endDate = null)
{
// 基础查询:广告表和日数据表联接
$query = Ad::alias('a')
->cache(false) // 强制不使用缓存
->leftJoin('bps.bps_google_ads_ad_group g', 'a.ad_group_id = g.ad_group_id') // 关联广告组表
->leftJoin('bps.bps_google_ads_campaign c', 'a.campaign_id = c.campaign_id'); // 关联广告系列表
// 如果提供了 customerIds增加查询条件
if (!empty($customerIds)) {
$query->whereIn('a.customer_id', $customerIds); // 添加 customer_id 的查询约束
} else {
return [
'pagination' => [
'startIndex' => 0,
'maxResults' => $pageSize,
'count' => 0,
'pageNo' => $page,
'pageSize' => $pageSize,
'pages' => 0,
],
'statistics' => [
'results' => '-',
'reach' => '-',
'spend' => '-',
'revenue' => '-',
'roas' => '-',
'profit' => '-',
'be_roas' => '-',
],
'data' => [],
];
}
// 动态拼接 LEFT JOIN 的 ON 条件
$onCondition = 'a.ad_id = d.ad_id';
if ($startDate && $endDate) {
$onCondition .= " AND d.date BETWEEN '{$startDate}' AND '{$endDate}'";
} else {
switch ($dateRange) {
case 'Today':
$onCondition .= " AND d.date = '" . date('Y-m-d') . "'";
break;
case 'Yesterday':
$onCondition .= " AND d.date = '" . date('Y-m-d', strtotime('-1 day')) . "'";
break;
case 'Last Week':
$onCondition .= " AND d.date >= '" . date('Y-m-d', strtotime('-1 week')) . "'";
break;
case 'Last Month':
$onCondition .= " AND d.date >= '" . date('Y-m-d', strtotime('-1 month')) . "'";
break;
case 'Last Year':
$onCondition .= " AND d.date >= '" . date('Y-m-d', strtotime('-1 year')) . "'";
break;
default:
break;
}
}
// 添加 LEFT JOIN 日数据表
$query->leftJoin('bps.bps_google_ad_day_data d', $onCondition);
// 添加字段、分组和查询条件
$query->field('a.ad_id, a.ad_name, a.status as ad_status, a.customer_id, a.ad_group_id, g.ad_group_name as ad_group_name, a.campaign_id, c.campaign_name as campaign_name,
COALESCE(SUM(d.clicks), 0) as clicks,
COALESCE(SUM(d.cost_micros) / 1000000, 0) as spend,
COALESCE(SUM(d.conversions), 0) as results,
COALESCE(SUM(d.conversions_value), 0) as conversions_value,
COALESCE(SUM(d.impressions), 0) as reach,
-1 as roas, -1 as be_roas, -1 as revenue, -1 as profit, -1 as delivery,
a.metadata') // Include metadata field
->group('a.ad_id, a.ad_name, a.status, a.customer_id, a.ad_group_id, g.ad_group_name, a.campaign_id, c.campaign_name')
->where(function ($query) use ($keyword) {
// if ($keyword) {
// $query->where('a.ad_name', 'like', '%' . $keyword . '%');
// }
});
// 获取所有符合条件的数据(不分页)
$allAds = $query->select()->toArray(); // 使用 toArray() 将对象转化为数组
$total_spend = array_sum(array_column($allAds, 'spend'));
$total_reach = array_sum(array_column($allAds, 'reach'));
$total_conversions_value = array_sum(array_column($allAds, 'conversions_value'));
// 汇总统计数据:基于所有数据而不是分页数据
$statistics = [
'results' => '-',
'reach' => $total_reach,
'spend' => '$' . number_format($total_spend, 2) ?: '$0.00',
'revenue' => '-',
'roas' => $total_spend == 0 ? '-' : number_format($total_conversions_value / $total_spend * 100, 2) . '%',
'profit' => '-',
'be_roas' => '-'
];
// 获取查询结果
$ads = $query->paginate($pageSize, false, ['page' => $page]);
// 确保转换为数值,并格式化 -1 为 "-"
$result = array_map(function ($item) {
// Extract square marketing images from metadata
// Extract square marketing images from metadata
$metadata = $item['metadata'];
// 如果是对象,转换为数组
if (is_object($metadata)) {
$metadata = (array)$metadata;
}
$imageUrls = isset($metadata['marketing_images']) ? $metadata['marketing_images'] : (isset($metadata['square_marketing_images']) ? $metadata['square_marketing_images'] : []);
// Extract asset_id from the image URL
$assetId = 0;
if (!empty($imageUrls)) {
// Example: "customers/8452924576/assets/191936503309"
$imagePath = $imageUrls[0]; // Get the last element
preg_match('/assets\/(\d+)/', $imagePath, $matches);
if (isset($matches[1])) {
$assetId = $matches[1];
}
}
// Query the asset_url from the bps_google_ads_asset table
$assetUrl = '';
if ($assetId) {
$asset = Asset::find($assetId);
if ($asset) {
$assetUrl = $asset['asset_url'];
}
}
//ROAS = conversion_value / (cost_micros / 1000000)
return [
'id' => $item['ad_id'],
'customer_id' => $item['customer_id'],
'ad_group_id' => $item['ad_group_id'],
'name' => $item['ad_name'] ?: '-', // 默认值为 '-'
'status' => $item['ad_status'],
'results' => $item['results'],
'reach' => $item['reach'],
'revenue' => $item['revenue'] == -1 ? '-' : $item['revenue'],
// 'roas' => $item['roas'] == -1 ? '-' : $item['roas'],
'roas' => $item['spend'] == 0 ? '-' : number_format($item['conversions_value'] / $item['spend'] * 100, 2) . '%',
'profit' => $item['profit'] == -1 ? '-' : $item['profit'],
'spend' => '$' . number_format($item['spend'], 2),
'campaign_name' => $item['campaign_name'],
'ad_set_name' => $item['ad_group_name'], // Assuming ad_group_name as ad_set_name
'delivery' => self::$statusMapping[$item['ad_status']],
'delivery_status' => $item['ad_status'], // Assuming active as '活动'
'be_roas' => $item['be_roas'] == -1 ? '-' : $item['be_roas'],
'image_url' => $assetUrl, // Add logic to populate image URLs if available
];
}, $ads->items());
// 汇总统计数据
// $statistics = [
// 'results' => '-',
// 'reach' => array_sum(array_column($result, 'reach')) ?: '-',
// 'revenue' => '-',
// 'roas' => '-',
// 'profit' => '-',
// 'spend' => array_sum(array_column($result, 'spend')) ?: '-',
// 'be_roas' => '-'
// ];
// Pagination data
$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,
];
}
/**
* 导出广告列表到 Excel
*
* @param string $keyword
* @param string $dateRange
* @return void
*/
public static function exportAdListToExcel($customerIds, $keyword, $dateRange, $startDate = null, $endDate = null)
{
if (empty($customerIds)) {
return [];
}
// 基础查询:广告表和日数据表联接
$query = Ad::alias('a')
->cache(false) // 强制不使用缓存
->leftJoin('bps.bps_google_ads_ad_group g', 'a.ad_group_id = g.ad_group_id') // 关联广告组表
->leftJoin('bps.bps_google_ads_campaign c', 'a.campaign_id = c.campaign_id'); // 关联广告系列表
// 动态拼接 LEFT JOIN 的 ON 条件
$onCondition = 'a.ad_id = d.ad_id';
if ($startDate && $endDate) {
$onCondition .= " AND d.date BETWEEN '{$startDate}' AND '{$endDate}'";
} else {
switch ($dateRange) {
case 'Today':
$onCondition .= " AND d.date = '" . date('Y-m-d') . "'";
break;
case 'Yesterday':
$onCondition .= " AND d.date = '" . date('Y-m-d', strtotime('-1 day')) . "'";
break;
case 'Last Week':
$onCondition .= " AND d.date >= '" . date('Y-m-d', strtotime('-1 week')) . "'";
break;
case 'Last Month':
$onCondition .= " AND d.date >= '" . date('Y-m-d', strtotime('-1 month')) . "'";
break;
case 'Last Year':
$onCondition .= " AND d.date >= '" . date('Y-m-d', strtotime('-1 year')) . "'";
break;
default:
break;
}
}
// 添加 LEFT JOIN 日数据表
$query->leftJoin('bps.bps_google_ad_day_data d', $onCondition);
// 添加字段、分组和查询条件
$query->field('a.ad_id, -1 as ad_name, a.status as ad_status, a.customer_id, a.ad_group_id, g.ad_group_name as ad_group_name, a.campaign_id, c.campaign_name as campaign_name,
COALESCE(SUM(d.clicks), -1) as clicks,
COALESCE(SUM(d.cost_micros) / 1000000, -1) as spend,
COALESCE(SUM(d.conversions), -1) as results,
COALESCE(SUM(d.conversions_value), -1) as conversions_value,
COALESCE(SUM(d.impressions), -1) as reach,
-1 as roas, -1 as be_roas, -1 as revenue, -1 as profit, -1 as delivery')
->group('a.ad_id, a.ad_name, a.status, a.customer_id, a.ad_group_id, g.ad_group_name, a.campaign_id, c.campaign_name')
->where('a.customer_id', 'in', $customerIds)
->where(function ($query) use ($keyword) {
// if ($keyword) {
// $query->where('a.ad_name', 'like', '%' . $keyword . '%');
// }
});
// 获取所有广告数据
$ads = $query->select();
// 创建一个新的 Spreadsheet 对象
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$ad_status = [
0 => 'UNSPECIFIED',
1 => 'UNKNOWN', // UNKNOW
2 => 'ENABLED', // ENABLED
3 => 'PAUSED', // PAUSED
4 => 'REMOVED', // REMOVED
];
// 设置表头
$sheet->setCellValue('A1', 'Ad ID');
$sheet->setCellValue('B1', 'Ad Status');
$sheet->setCellValue('C1', 'Name');
$sheet->setCellValue('D1', 'Campaign');
$sheet->setCellValue('E1', 'Ad Set');
$sheet->setCellValue('F1', 'Delivery');
$sheet->setCellValue('G1', 'Results');
$sheet->setCellValue('H1', 'Reach');
$sheet->setCellValue('I1', 'Revenue');
$sheet->setCellValue('J1', 'ROAS');
$sheet->setCellValue('K1', 'beROAS');
$sheet->setCellValue('L1', 'Profit');
$sheet->setCellValue('M1', 'Spend');
// $sheet->setCellValue('N1', 'Customer ID');
// $sheet->setCellValue('E1', 'Clicks');
// $sheet->setCellValue('F1', 'Cost Micros');
// $sheet->setCellValue('G1', 'Conversions');
// $sheet->setCellValue('H1', 'Conversions Value');
// $sheet->setCellValue('I1', 'Impressions');
// 填充数据
$row = 2; // 从第二行开始
foreach ($ads as $ad) {
$sheet->setCellValueExplicit('A' . $row, (string)$ad->ad_id, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING); // 设置 ad_id 为文本
$sheet->setCellValue('B' . $row, $ad_status[$ad->ad_status]);
$sheet->setCellValue('C' . $row, $ad->ad_name); // 直接设置 ad_name
$sheet->setCellValue('D' . $row, $ad->campaign_name); // 直接设置 ad_name
$sheet->setCellValue('E' . $row, $ad->ad_group_name); // 直接设置 ad_name
$sheet->setCellValue('F' . $row, $ad->delivery); // 直接设置 ad_name
$sheet->setCellValue('G' . $row, $ad->results);
$sheet->setCellValue('H' . $row, $ad->reach);
$sheet->setCellValue('I' . $row, $ad->revenue);
$sheet->setCellValue('J' . $row, $ad->roas);
$sheet->setCellValue('K' . $row, $ad->be_roas);
$sheet->setCellValue('L' . $row, $ad->profit);
$sheet->setCellValue('M' . $row, $ad->spend);
// $sheet->setCellValueExplicit('N' . $row, (string)$ad->customer_id, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING); // 设置 customer_id 为文本
$row++;
}
// 设置 Excel 文件名
$fileName = 'Ad_Report_' . $dateRange . '_' . date('Y-m-d_H-i-s') . '.xlsx';
// 创建 Excel 文件并保存
$writer = new Xlsx($spreadsheet);
$filePath = public_path() . '/' . $fileName;
try {
$writer->save($filePath);
return response()->download($filePath, $fileName);
// return ['success' => true, 'file_path' => $filePath];
} catch (\Exception $e) {
return ['' => false, 'message' => $e->getMessage()];
}
}
/**
* 获取广告系列列表
*/
public static function getCampaignList($customerIds, $page, $pageSize, $keyword, $dateRange, $startDate = null, $endDate = null)
{
// 检查 customerIds 是否为空,直接返回空结构
if (empty($customerIds)) {
return [
'pagination' => [
'startIndex' => 0,
'maxResults' => $pageSize,
'count' => 0,
'pageNo' => $page,
'pageSize' => $pageSize,
'pages' => 0,
],
'statistics' => [
'results' => '-',
'reach' => '-',
'spend' => '-',
'revenue' => '-',
'roas' => '-',
'profit' => '-',
'be_roas' => '-',
],
'data' => [],
];
}
// 动态构建日期条件
$dateCondition = '';
if ($startDate && $endDate) {
$dateCondition = "d.date BETWEEN '{$startDate}' AND '{$endDate}'";
} else {
switch ($dateRange) {
case 'Today':
$dateCondition = "d.date = '" . date('Y-m-d') . "'";
break;
case 'Yesterday':
$dateCondition = "d.date = '" . date('Y-m-d', strtotime('-1 day')) . "'";
break;
case 'Last Week':
$dateCondition = "d.date >= '" . date('Y-m-d', strtotime('-1 week')) . "'";
break;
case 'Last Month':
$dateCondition = "d.date >= '" . date('Y-m-d', strtotime('-1 month')) . "'";
break;
case 'Last Year':
$dateCondition = "d.date >= '" . date('Y-m-d', strtotime('-1 year')) . "'";
break;
default:
$dateCondition = "1=1"; // 无日期限制,默认条件始终为真
break;
}
}
// 基础查询:广告活动和日数据表联接
$query = Campaign::alias('c')
->cache(false) // 强制不使用缓存
->leftJoin('bps.bps_google_ad_day_data d', "c.campaign_id = d.campaign_id AND {$dateCondition}")
->field('c.campaign_id, c.status as campaign_status, c.campaign_name, c.customer_id,
COALESCE(SUM(d.clicks), 0) as clicks,
COALESCE(SUM(d.cost_micros) / 1000000, 0) as spend,
COALESCE(SUM(d.conversions), 0) as results,
COALESCE(SUM(d.conversions_value), 0) as conversions_value,
COALESCE(SUM(d.impressions), 0) as reach,
-1 as roas, -1 as be_roas, -1 as revenue, -1 as profit, -1 as delivery')
->group('c.campaign_id, c.status, c.customer_id, c.campaign_name')
->where('c.customer_id', 'in', $customerIds); // 添加 customerIds 条件
// 添加关键字过滤条件
$query->where(function ($query) use ($keyword) {
if ($keyword) {
$query->where('c.campaign_name', 'like', '%' . $keyword . '%');
}
});
// 获取所有符合条件的数据(不分页)
$allCampaigns = $query->select()->toArray();
$total_spend = array_sum(array_column($allCampaigns, 'spend'));
$total_reach = array_sum(array_column($allCampaigns, 'reach'));
$total_conversions_value = array_sum(array_column($allCampaigns, 'conversions_value'));
// 汇总统计数据
$statistics = [
'results' => '-',
'reach' => $total_reach,
'spend' => '$' . number_format($total_spend, 2) ?: '$0.00',
'revenue' => '-',
'roas' => $total_spend == 0 ? '-' : number_format($total_conversions_value / $total_spend * 100, 2) . '%',
'profit' => '-',
'be_roas' => '-',
];
// 获取分页数据
$campaigns = $query->paginate($pageSize, false, ['page' => $page]);
// 确保数据格式统一
$result = array_map(function ($item) {
return [
'id' => $item['campaign_id'],
'customer_id' => $item['customer_id'],
'name' => $item['campaign_name'] ?: '-',
'status' => $item['campaign_status'],
'results' => $item['results'],
'reach' => $item['reach'],
'revenue' => $item['revenue'] == -1 ? '-' : $item['revenue'],
// 'roas' => $item['roas'] == -1 ? '-' : $item['roas'],
'roas' => $item['spend'] == 0 ? '-' : number_format($item['conversions_value'] / $item['spend'] * 100, 2) . '%',
'profit' => $item['profit'] == -1 ? '-' : $item['profit'],
'spend' => '$' . number_format($item['spend'], 2),
'delivery' => self::$statusMapping[$item['campaign_status']],
'delivery_status' => $item['campaign_status'], // 默认状态
'be_roas' => $item['be_roas'] == -1 ? '-' : $item['be_roas'],
];
}, $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,
];
}
/**
* 导出广告系列数据到 Excel
*
* @param string $keyword
* @param string $dateRange
* @return void
*/
public function exportCampaignsToExcel($customerIds, $keyword, $dateRange, $startDate = null, $endDate = null)
{
if (empty($customerIds)) {
return [];
}
// 动态构建日期条件
$dateCondition = '';
if ($startDate && $endDate) {
$dateCondition = "d.date BETWEEN '{$startDate}' AND '{$endDate}'";
} else {
switch ($dateRange) {
case 'Today':
$dateCondition = "d.date = '" . date('Y-m-d') . "'";
break;
case 'Yesterday':
$dateCondition = "d.date = '" . date('Y-m-d', strtotime('-1 day')) . "'";
break;
case 'Last Week':
$dateCondition = "d.date >= '" . date('Y-m-d', strtotime('-1 week')) . "'";
break;
case 'Last Month':
$dateCondition = "d.date >= '" . date('Y-m-d', strtotime('-1 month')) . "'";
break;
case 'Last Year':
$dateCondition = "d.date >= '" . date('Y-m-d', strtotime('-1 year')) . "'";
break;
default:
$dateCondition = "1=1"; // 无日期限制,默认条件始终为真
break;
}
}
// 基础查询:广告活动和日数据表联接
$query = Campaign::alias('c')
->leftJoin('bps.bps_google_ad_day_data d', "c.campaign_id = d.campaign_id AND {$dateCondition}") // 将日期条件加入到 ON 子句中
->field('c.campaign_id, c.status as campaign_status, c.campaign_name, c.customer_id,
COALESCE(SUM(d.clicks), -1) as clicks,
COALESCE(SUM(d.cost_micros) / 1000000, -1) as spend,
COALESCE(SUM(d.conversions), -1) as results,
COALESCE(SUM(d.conversions_value), -1) as conversions_value,
COALESCE(SUM(d.impressions), -1) as reach,
-1 as roas, -1 as be_roas, -1 as revenue, -1 as profit, -1 as delivery')
->group('c.campaign_id, c.status, c.customer_id, c.campaign_name')
->where('c.customer_id', 'in', $customerIds)
->where(function ($query) use ($keyword) {
if ($keyword) {
$query->where('c.campaign_name', 'like', '%' . $keyword . '%');
}
});
// 获取所有广告系列数据
$campaigns = $query->select();
// 创建一个新的 Spreadsheet 对象
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$campaignsStatus = [
0 => 'UNSPECIFIED', // UNSPECIFIED
1 => 'UNKNOWN', // UNKNOWN
2 => 'ENABLED', // ENABLED
3 => 'PAUSED', // PAUSED
4 => 'REMOVED', // REMOVED
];
// 设置表头
$sheet->setCellValue('A1', 'Campaign ID');
$sheet->setCellValue('B1', 'Status');
$sheet->setCellValue('C1', 'Name');
$sheet->setCellValue('D1', 'Delivery');
$sheet->setCellValue('E1', 'Results');
$sheet->setCellValue('F1', 'Reach');
$sheet->setCellValue('G1', 'Revenue');
$sheet->setCellValue('H1', 'ROAS');
$sheet->setCellValue('I1', 'beROAS');
$sheet->setCellValue('J1', 'Profit');
$sheet->setCellValue('K1', 'Spend');
// 填充数据
$row = 2; // 从第二行开始
foreach ($campaigns as $campaign) {
//使用 setCellValueExplicit 显式设置为文本格式
$sheet->setCellValueExplicit('A' . $row, (string)$campaign->campaign_id, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING); // 设置为文本格式
$sheet->setCellValue('B' . $row, $campaignsStatus[$campaign->campaign_status]);
$sheet->setCellValue('C' . $row, $campaign->campaign_name);
$sheet->setCellValue('D' . $row, $campaign->delivery); // 直接设置 ad_name
$sheet->setCellValue('E' . $row, $campaign->results);
$sheet->setCellValue('F' . $row, $campaign->reach);
$sheet->setCellValue('G' . $row, $campaign->revenue);
$sheet->setCellValue('H' . $row, $campaign->roas);
$sheet->setCellValue('I' . $row, $campaign->be_roas);
$sheet->setCellValue('J' . $row, $campaign->profit);
$sheet->setCellValue('K' . $row, $campaign->spend);
// $sheet->setCellValueExplicit('N' . $row, (string)$campaign->customer_id, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING); // 设置为文本格式
$row++;
}
// 设置 Excel 文件名
$fileName = 'Campaign_Report_' . $dateRange . '_' . date('Y-m-d_H-i-s') . '.xlsx';
// 创建 Excel 文件并保存
$writer = new Xlsx($spreadsheet);
$filePath = public_path() . '/' . $fileName;
try {
$writer->save($filePath);
return response()->download($filePath, $fileName);
// return ['success' => true, 'file_path' => $filePath];
} catch (\Exception $e) {
return ['' => false, 'message' => $e->getMessage()];
}
}
/**
* 获取广告组列表
*/
public static function getAdGroupList($customerIds, $page, $pageSize, $keyword, $dateRange, $startDate = null, $endDate = null)
{
// 检查 customerIds 是否为空,直接返回空结构
if (empty($customerIds)) {
return [
'pagination' => [
'startIndex' => 0,
'maxResults' => $pageSize,
'count' => 0,
'pageNo' => $page,
'pageSize' => $pageSize,
'pages' => 0,
],
'statistics' => [
'results' => '-',
'reach' => '-',
'spend' => '-',
'revenue' => '-',
'roas' => '-',
'profit' => '-',
'be_roas' => '-',
],
'data' => [],
];
}
// 动态构建日期条件
$dateCondition = '';
if ($startDate && $endDate) {
$dateCondition = "d.date BETWEEN '{$startDate}' AND '{$endDate}'";
} else {
switch ($dateRange) {
case 'Today':
$dateCondition = "d.date = '" . date('Y-m-d') . "'";
break;
case 'Yesterday':
$dateCondition = "d.date = '" . date('Y-m-d', strtotime('-1 day')) . "'";
break;
case 'Last Week':
$dateCondition = "d.date >= '" . date('Y-m-d', strtotime('-1 week')) . "'";
break;
case 'Last Month':
$dateCondition = "d.date >= '" . date('Y-m-d', strtotime('-1 month')) . "'";
break;
case 'Last Year':
$dateCondition = "d.date >= '" . date('Y-m-d', strtotime('-1 year')) . "'";
break;
default:
$dateCondition = "1=1"; // 无日期限制,默认条件始终为真
break;
}
}
// 初始化查询
$query = AdGroup::alias('ag')
->leftJoin("bps.bps_google_ad_day_data d", "ag.ad_group_id = d.ad_group_id AND {$dateCondition}") // 日期条件放入 ON 子句
->leftJoin("bps.bps_google_ads_campaign c", "ag.campaign_id = c.campaign_id") // 关联广告系列表
->field('ag.ad_group_id, ag.ad_group_name, ag.status as ad_group_status, ag.campaign_id, c.campaign_name, ag.customer_id,
COALESCE(SUM(d.clicks), 0) as clicks,
COALESCE(SUM(d.cost_micros) / 1000000, 0) as spend,
COALESCE(SUM(d.conversions), 0) as results,
COALESCE(SUM(d.conversions_value), 0) as conversions_value,
COALESCE(SUM(d.impressions), 0) as reach,
-1 as roas, -1 as be_roas, -1 as revenue, -1 as profit, -1 as delivery')
->group('ag.ad_group_id, ag.ad_group_name, ag.status, ag.campaign_id, c.campaign_name, ag.customer_id')
->where('ag.customer_id', 'in', $customerIds) // 添加 customerIds 条件
->where(function ($query) use ($keyword) {
if ($keyword) {
$query->where('ag.ad_group_name', 'like', '%' . $keyword . '%');
}
});
// 获取所有符合条件的数据(不分页)
$allAdGroups = $query->select()->toArray();
$total_spend = array_sum(array_column($allAdGroups, 'spend'));
$total_reach = array_sum(array_column($allAdGroups, 'reach'));
$total_conversions_value = array_sum(array_column($allAdGroups, 'conversions_value'));
// 汇总统计数据:基于所有数据而不是分页数据
$statistics = [
'results' => '-',
'reach' => $total_reach,
'spend' => '$' . number_format($total_spend, 2) ?: '$0.00',
'revenue' => '-',
'roas' => $total_spend == 0 ? '-' : number_format($total_conversions_value / $total_spend * 100, 2) . '%',
'profit' => '-',
'be_roas' => '-'
];
// 获取分页数据
$adGroups = $query->paginate($pageSize, false, ['page' => $page]);
// 格式化结果
$result = array_map(function ($item) {
return [
'id' => $item['ad_group_id'],
'customer_id' => $item['customer_id'],
'name' => $item['ad_group_name'] ?: '-',
'status' => $item['ad_group_status'],
'delivery' => self::$statusMapping[$item['ad_group_status']],
'delivery_status' => $item['ad_group_status'], // Assuming active as '活动'
'campaign_name' => $item['campaign_name'],
'results' => $item['results'],
'reach' => $item['reach'],
'spend' => '$' . number_format($item['spend'], 2),
'revenue' => $item['revenue'] == -1 ? '-' : $item['revenue'],
// 'roas' => $item['roas'] == -1 ? '-' : $item['roas'],
'roas' => $item['spend'] == 0 ? '-' : number_format($item['conversions_value'] / $item['spend'] * 100, 2) . '%',
'profit' => $item['profit'] == -1 ? '-' : $item['profit'],
'be_roas' => $item['be_roas'] == -1 ? '-' : $item['be_roas'],
];
}, $adGroups->items());
// 分页信息
$pagination = [
'startIndex' => ($page - 1) * $pageSize,
'maxResults' => $pageSize,
'count' => $adGroups->total(),
'pageNo' => $adGroups->currentPage(),
'pageSize' => $pageSize,
'pages' => $adGroups->lastPage(),
];
// 返回结果
return [
'pagination' => $pagination,
'statistics' => $statistics,
'data' => $result,
];
}
/**
* 将广告组数据导出到 Excel 文件
*/
public function exportAdGroupsToExcel($customerIds, $keyword = '', $dateRange = 'Today', $startDate = null, $endDate = null)
{
if (empty($customerIds)) {
return [];
}
// 动态构建日期条件
$dateCondition = '';
if ($startDate && $endDate) {
$dateCondition = "d.date BETWEEN '{$startDate}' AND '{$endDate}'";
} else {
switch ($dateRange) {
case 'Today':
$dateCondition = "d.date = '" . date('Y-m-d') . "'";
break;
case 'Yesterday':
$dateCondition = "d.date = '" . date('Y-m-d', strtotime('-1 day')) . "'";
break;
case 'Last Week':
$dateCondition = "d.date >= '" . date('Y-m-d', strtotime('-1 week')) . "'";
break;
case 'Last Month':
$dateCondition = "d.date >= '" . date('Y-m-d', strtotime('-1 month')) . "'";
break;
case 'Last Year':
$dateCondition = "d.date >= '" . date('Y-m-d', strtotime('-1 year')) . "'";
break;
default:
$dateCondition = "1=1"; // 默认无日期限制
break;
}
}
// 初始化查询
$query = AdGroup::alias('ag')
->leftJoin("bps.bps_google_ad_day_data d", "ag.ad_group_id = d.ad_group_id AND {$dateCondition}") // 日期条件放入 ON 子句
->leftJoin("bps.bps_google_ads_campaign c", "ag.campaign_id = c.campaign_id") // 关联广告系列表
->field('ag.ad_group_id, ag.ad_group_name, ag.status as ad_group_status, ag.campaign_id, c.campaign_name, ag.customer_id,
COALESCE(SUM(d.clicks), -1) as clicks,
COALESCE(SUM(d.cost_micros) / 1000000, -1) as spend,
COALESCE(SUM(d.conversions), -1) as results,
COALESCE(SUM(d.conversions_value), -1) as conversions_value,
COALESCE(SUM(d.impressions), -1) as reach,
-1 as roas, -1 as be_roas, -1 as revenue, -1 as profit, -1 as delivery')
->group('ag.ad_group_id, ag.ad_group_name, ag.status, ag.campaign_id, c.campaign_name, ag.customer_id')
->where('ag.customer_id', 'in', $customerIds) // 添加 customerIds 条件
->where(function ($query) use ($keyword) {
if ($keyword) {
$query->where('ag.ad_group_name', 'like', '%' . $keyword . '%');
}
});
// 获取所有广告组数据
$ad_groups = $query->select();
// 创建一个新的 Spreadsheet 对象
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
// 设置表头
$sheet->setCellValue('A1', 'Ad Group ID');
$sheet->setCellValue('B1', 'Status');
$sheet->setCellValue('C1', 'Name');
$sheet->setCellValue('D1', 'Campaign');
$sheet->setCellValue('E1', 'Delivery');
$sheet->setCellValue('F1', 'Results');
$sheet->setCellValue('G1', 'Reach');
$sheet->setCellValue('H1', 'Revenue');
$sheet->setCellValue('I1', 'ROAS');
$sheet->setCellValue('J1', 'beROAS');
$sheet->setCellValue('K1', 'Profit');
$sheet->setCellValue('L1', 'Spend');
$ad_groups_status = [
0 => 'UNSPECIFIED', // UNSPECIFIED
1 => 'UNKNOWN', // UNKNOWN
2 => 'ENABLED', // ENABLED
3 => 'PAUSED', // PAUSED
4 => 'REMOVED', // REMOVED
];
// 填充数据
$row = 2;
foreach ($ad_groups as $adGroup) {
// 使用 setCellValueExplicit 显式设置为文本格式
$sheet->setCellValueExplicit('A' . $row, (string)$adGroup->ad_group_id, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING); // 设置为文本格式
// $sheet->setCellValueExplicit('B' . $row, (string)$adGroup->campaign_id, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING);
$sheet->setCellValue('B' . $row, $ad_groups_status[$adGroup->ad_group_status]);
$sheet->setCellValue('C' . $row, $adGroup->ad_group_name);
$sheet->setCellValue('D' . $row, $adGroup->campaign_name);
$sheet->setCellValue('E' . $row, $adGroup->delivery); // 直接设置 ad_name
$sheet->setCellValue('F' . $row, $adGroup->results);
$sheet->setCellValue('G' . $row, $adGroup->reach);
$sheet->setCellValue('H' . $row, $adGroup->revenue);
$sheet->setCellValue('I' . $row, $adGroup->roas);
$sheet->setCellValue('J' . $row, $adGroup->be_roas);
$sheet->setCellValue('K' . $row, $adGroup->profit);
$sheet->setCellValue('L' . $row, $adGroup->spend);
// $sheet->setCellValueExplicit('M' . $row, (string)$adGroup->customer_id, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING);
$row++;
}
// 自动调整所有列宽
foreach (range('A', 'H') as $column) {
$spreadsheet->getActiveSheet()->getColumnDimension($column)->setAutoSize(true);
}
// 设置 Excel 文件名
$fileName = 'AdGroup_Report_' . $dateRange . '_' . date('Y-m-d_H-i-s') . '.xlsx';
// 创建 Excel 文件并保存
$writer = new Xlsx($spreadsheet);
$filePath = public_path() . '/' . $fileName;
try {
$writer->save($filePath);
return response()->download($filePath, $fileName);
// return ['success' => true, 'file_path' => $filePath];
} catch (\Exception $e) {
return ['' => false, 'message' => $e->getMessage()];
}
}
/**
* 获取广告资产报告
*
* @param string $keyword 关键词(广告素材名称模糊搜索)
* @param string $dateRange 日期范围Today, Yesterday, Last Week, Last Month, Last Year
* @param string|null $startDate 起始日期(可选)
* @param string|null $endDate 结束日期(可选)
* @param int $page 当前页码
* @param int $pageSize 每页数量
* @return array
*/
public function getAssetConversionData($customerIds, $page, $pageSize, $keyword, $dateRange, $startDate = null, $endDate = null)
{
$isSameMonth = true; // 判断是否跨月
$currentMonth = date('Y-m'); //一个月内的统计需要确定是什么月份。
if ($startDate && $endDate) {
// 比较日期的年月是否相同,若不同则为跨月
$isSameMonth = date('Y-m', strtotime($startDate)) === date('Y-m', strtotime($endDate));
if ($isSameMonth) {
$currentMonth = date('Y-m', strtotime($startDate));
}
}
// 1. 获取符合条件的 ad_id 列表,并且筛选 AssetRelation 的 date 字段
$adIdsQuery = AssetRelation::alias('r')
->leftJoin('bps.bps_google_ads_asset a', 'r.asset_id = a.asset_id') // 关联资产表
->where(function ($query) use ($keyword) {
if ($keyword) {
$query->where('a.asset_name', 'like', '%' . $keyword . '%'); // 关键词模糊匹配
}
});
// 如果提供了 customerIds增加查询条件
if (!empty($customerIds)) {
$adIdsQuery->whereIn('a.customer_id', $customerIds); // 添加 customer_id 的查询约束
} else {
return [
'data' => [],
'chat_1_data' => [], // 返回按月汇总的 chat_data
'total' => 0,
'pagination' => [
'startIndex' => 0,
'maxResults' => $pageSize,
'count' => 0,
'pageNo' => 1,
'pageSize' => $pageSize,
'pages' => 1
]
];
}
// 日期范围处理,筛选 AssetRelation 的 date 字段
if ($startDate && $endDate) {
$adIdsQuery->whereBetween('r.date', [$startDate, $endDate]);
} else {
switch ($dateRange) {
case 'Today':
$adIdsQuery->where('r.date', '=', date('Y-m-d'));
$currentMonth = date('Y-m');
$isSameMonth = true;
break;
case 'Yesterday':
$adIdsQuery->where('r.date', '=', date('Y-m-d', strtotime('-1 day')));
$currentMonth = date('Y-m', strtotime('-1 day'));
$isSameMonth = true;
break;
case 'Last Week':
$adIdsQuery->where('r.date', '>=', date('Y-m-d', strtotime('-1 week')));
// 比较日期的年月是否相同,若不同则为跨月
$isSameMonth = date('Y-m', strtotime('-1 week')) === date('Y-m');
break;
case 'Last Month':
$adIdsQuery->where('r.date', '>=', date('Y-m-d', strtotime('-1 month')));
$isSameMonth = false;
break;
case 'Last Year':
$adIdsQuery->where('r.date', '>=', date('Y-m-d', strtotime('-1 year')));
$isSameMonth = false;
break;
default:
break;
}
}
// 获取唯一的 ad_id 列表
$adIds = $adIdsQuery->distinct(true)->column('r.ad_id');
// dump($adIds);return($adIds);
// 2. 根据 ad_id 和日期范围去查询 DayData 表,按 ad_id 和日期聚合统计
$dayDataQuery = DayData::alias('d')
// ->leftJoin('bps.bps_google_ads_campaign c', 'd.campaign_id = c.campaign_id') // 关联广告系列表(如果有)
// ->leftJoin('bps.bps_google_ads_ad_group ag', 'd.ad_group_id = ag.ad_group_id') // 关联广告组表(如果有)
->whereIn('d.ad_id', $adIds) // 使用 ad_id 过滤
->where(function ($query) use ($startDate, $endDate, $dateRange) {
// 日期范围的筛选
if ($startDate && $endDate) {
$query->whereBetween('d.date', [$startDate, $endDate]);
} else {
switch ($dateRange) {
case 'Today':
$query->where('d.date', '=', date('Y-m-d'));
break;
case 'Yesterday':
$query->where('d.date', '=', date('Y-m-d', strtotime('-1 day')));
break;
case 'Last Week':
$query->where('d.date', '>=', date('Y-m-d', strtotime('-1 week')));
break;
case 'Last Month':
$query->where('d.date', '>=', date('Y-m-d', strtotime('-1 month')));
break;
case 'Last Year':
$query->where('d.date', '>=', date('Y-m-d', strtotime('-1 year')));
break;
default:
break;
}
}
});
// 如果跨月,按月聚合;如果不跨月,按 ad_id 聚合
if (!$isSameMonth) {
$dayDataQuery->group("d.month,d.ad_id,d.ad_name, d.ad_resource_name, d.ad_group_id, d.campaign_id, d.customer_id"); // 按月聚合
$dayDataQuery->field([
'd.ad_id',
'd.month',
// ThinkDb::raw("TO_CHAR(d.date, 'YYYY-MM') AS month"), // 格式化为 YYYY-MM 格式
ThinkDb::raw('SUM(d.cost_micros) / 1000000 AS total_spend'),
ThinkDb::raw('SUM(d.conversions_value) AS total_conversions_value'),
ThinkDb::raw('SUM(d.conversions) AS total_conversions'),
ThinkDb::raw('SUM(d.impressions) AS total_impressions'),
ThinkDb::raw('SUM(d.clicks) AS total_clicks')
]);
} else {
$dayDataQuery->group('d.ad_id,d.ad_name, d.ad_resource_name, d.ad_group_id, d.campaign_id, d.customer_id'); // 按 ad_id 聚合
$dayDataQuery->field([
'd.ad_id',
ThinkDb::raw('SUM(d.cost_micros) / 1000000 AS total_spend'),
ThinkDb::raw('SUM(d.conversions_value) AS total_conversions_value'),
ThinkDb::raw('SUM(d.conversions) AS total_conversions'),
ThinkDb::raw('SUM(d.impressions) AS total_impressions'),
ThinkDb::raw('SUM(d.clicks) AS total_clicks')
]);
}
// 获取聚合数据,不需要再传递字段给 select()
$aggregatedData = $dayDataQuery->select();
// dump($aggregatedData);return($aggregatedData);
// 3. 获取与 ad_id 关联的 asset_id 以及相关数据
$assetRelationsQuery = AssetRelation::whereIn('ad_id', $adIds)
->whereBetween('date', [$startDate, $endDate]) // 加上 AssetRelation 的 date 过滤
->field(['asset_id', 'ad_id'])->select();
// dump($assetRelationsQuery);return($assetRelationsQuery);
// 4. 汇总每个 asset_id 下的所有 ad_id 的聚合数据
$assetSummaryData = [];
foreach ($assetRelationsQuery as $assetRelation) {
// 从聚合数据中找到当前 ad_id 的相关数据
// $adStats = $aggregatedData->where('ad_id', $assetRelation->ad_id)->first();
// 获取该 ad_id 的所有聚合数据(可能有多条记录)
$adStatsCollection = $aggregatedData->where('ad_id', $assetRelation->ad_id);
// dump($adStatsCollection);return($adStatsCollection);
if (!$adStatsCollection->isEmpty()) {
// 如果该 ad_id 有对应的聚合数据,初始化资产汇总数据
if (!isset($assetSummaryData[$assetRelation->asset_id])) {
// 获取 asset 相关信息,通过模型查询 Asset
$asset = Asset::find($assetRelation->asset_id); // 根据 asset_id 查找对应的 Asset 数据
$assetSummaryData[$assetRelation->asset_id] = [
'asset_id' => $assetRelation->asset_id,
'asset_name' => '-', // 获取 asset_name
'asset_type' => 0, // 获取 asset_type
'asset_url' => '-', // 获取 asset_url
'total_spend' => 0,
'total_conversions_value' => 0,
'total_conversions' => 0,
'total_impressions' => 0,
'ad_count' => 0,
'monthly_data' => [], // 按月存储数据
];
}
// 遍历该 ad_id 所有的统计数据
foreach ($adStatsCollection as $adStats) {
// 获取当前月份
if ($isSameMonth) {
$month = $currentMonth; // 格式化为 YYYY-MM
} else {
$month = $adStats->month; // 格式化日期为 'YYYY-MM'
}
// 累加该 ad_id 的统计数据到对应的 asset_id 汇总
$assetSummaryData[$assetRelation->asset_id]['asset_name'] = $asset->asset_name;
$assetSummaryData[$assetRelation->asset_id]['asset_type'] = $asset->asset_type;
$assetSummaryData[$assetRelation->asset_id]['asset_url'] = $asset->asset_url;
$assetSummaryData[$assetRelation->asset_id]['total_spend'] += $adStats->total_spend;
$assetSummaryData[$assetRelation->asset_id]['total_conversions_value'] += $adStats->total_conversions_value;
$assetSummaryData[$assetRelation->asset_id]['total_conversions'] += $adStats->total_conversions;
$assetSummaryData[$assetRelation->asset_id]['total_impressions'] += $adStats->total_impressions;
$assetSummaryData[$assetRelation->asset_id]['ad'][] = $adStats->ad_id; // 存储 ad_id下一步统计数量
// 按月分开存储每个月的 spend 和 ROAS
if (!isset($assetSummaryData[$assetRelation->asset_id]['monthly_data'][$month])) {
$assetSummaryData[$assetRelation->asset_id]['monthly_data'][$month] = [
'month' => $month,
'spend' => 0,
'conversions_value' => 0,
];
}
// 累加每个月的数据
$assetSummaryData[$assetRelation->asset_id]['monthly_data'][$month]['spend'] += $adStats->total_spend;
$assetSummaryData[$assetRelation->asset_id]['monthly_data'][$month]['conversions_value'] += $adStats->total_conversions_value; // 避免除以零
}
}
}
// 5. 格式化输出数据
// 生成最终输出的数据
$resultData = [];
$chat_data = [];
foreach ($assetSummaryData as $assetId => $data) {
// 计算 ROAS
$roas = $data['total_spend'] ? ($data['total_conversions_value'] / $data['total_spend']) * 100 : 0;
// 合并月度数据到总数据
$resultData[] = [
'creative_id' => $assetId,
'creative' => $data['asset_name'],
'creative_type' => $data['asset_type'],
'creative_url' => $data['asset_url'],
'spend' => '$' . number_format($data['total_spend'], 2),
'purchase_value' => '-',
'roas' => number_format($roas, 2) . '%',
'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' => '$' . number_format($data['total_conversions_value'], 2),
'total_conversions' => $data['total_conversions'],
'total_impressions' => $data['total_impressions'],
'ad_count' => count($data['ad']),
// 'monthly_data' => $data['monthly_data'], // 合并月度数据
];
// 计算每个月的汇总数据,用于 chat_data
foreach ($data['monthly_data'] as $month => $monthlyData) {
// 如果 chat_data 中已有该月的数据,则累加
if (!isset($chat_data[$month])) {
$chat_data[$month] = [
'month' => $month,
'total_spend' => 0,
'total_conversions_value' => 0,
'roas' => 0,
];
}
// 累加该月的 spend 和 conversions_value
$chat_data[$month]['total_spend'] += $monthlyData['spend'];
$chat_data[$month]['total_conversions_value'] += $monthlyData['conversions_value'];
}
}
// 计算每个月的总 ROASROAS = 总的转换值 / 总的支出
foreach ($chat_data as $month => $data) {
// 计算 ROAS
$totalSpend = $data['total_spend'];
$totalConversionsValue = $data['total_conversions_value'];
// 如果有支出数据,计算 ROAS
$chat_data[$month]['roas'] = $totalSpend ? ($totalConversionsValue / $totalSpend) * 100 : 0;
// 格式化 ROAS 为百分比
$chat_data[$month]['roas'] = number_format($chat_data[$month]['roas'], 2) . '%';
}
// 返回分页数据
$totalItems = count($assetSummaryData);
$totalPages = ceil($totalItems / $pageSize);
$startIndex = ($page - 1) * $pageSize;
// 截取返回的数据
// $pagedData = array_slice($assetSummaryData, $startIndex, $pageSize);
$resultDataPaginated = array_slice($resultData, ($page - 1) * $pageSize, $pageSize);
return [
'pagination' => [
'startIndex' => $startIndex,
'maxResults' => $pageSize,
'count' => $totalItems,
'pageNo' => $page,
'pageSize' => $pageSize,
'pages' => $totalPages
],
'chat_1_data' => array_values($chat_data), // 返回按月汇总的 chat_data
'data' => $resultDataPaginated
];
}
}