1232 lines
		
	
	
		
			54 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			1232 lines
		
	
	
		
			54 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?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,
 | ||
|                 'statistics' => [],
 | ||
|                 '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])) {
 | ||
| 
 | ||
| 
 | ||
|                     $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'
 | ||
|                     }
 | ||
|                     // 获取 asset 相关信息,通过模型查询 Asset
 | ||
|                     $asset = Asset::find($assetRelation->asset_id);  // 根据 asset_id 查找对应的 Asset 数据
 | ||
|                     // 累加该 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; // 避免除以零
 | ||
|                 }
 | ||
|             }
 | ||
|         }
 | ||
| //return $assetSummaryData;
 | ||
|         // 5. 格式化输出数据
 | ||
|         // 生成最终输出的数据
 | ||
|         $resultData = [];
 | ||
|         $chat_data  = [];
 | ||
|         $statistics = $this->initializeStatistics(); // Initialize statistics before processing
 | ||
| 
 | ||
|         foreach ($assetSummaryData as $assetId => $data) {
 | ||
|             // 计算 ROAS
 | ||
|             $roas = $data['total_spend'] ? ($data['total_conversions_value'] / $data['total_spend'])  : 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) . 'X',
 | ||
|                 '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'];
 | ||
| 
 | ||
|                 // Aggregate statistics for overall summary
 | ||
|                 $statistics['spend'] += $monthlyData['spend'];
 | ||
|                 $statistics['conversions_value'] += $monthlyData['conversions_value'];
 | ||
| 
 | ||
|             }
 | ||
| 
 | ||
| 
 | ||
|         }
 | ||
| 
 | ||
|         // 计算每个月的总 ROAS,ROAS = 总的转换值 / 总的支出
 | ||
|         foreach ($chat_data as $month => $data) {
 | ||
|             // 计算 ROAS
 | ||
|             $totalSpend            = $data['total_spend'];
 | ||
|             $totalConversionsValue = $data['total_conversions_value'];
 | ||
| 
 | ||
|             // 如果有支出数据,计算 ROAS
 | ||
|             $chat_data[$month]['roas'] = $totalSpend ? ($totalConversionsValue / $totalSpend) : 0;
 | ||
|             // 格式化 ROAS 为倍数
 | ||
|             $chat_data[$month]['roas'] = round($chat_data[$month]['roas'], 2);
 | ||
|         }
 | ||
| 
 | ||
|          // 计算整体的 ROAS
 | ||
|          $statistics['spend'] = '$'.number_format($statistics['spend'], 2);
 | ||
|          $statistics['roas'] = $statistics['spend'] > 0 ? ($statistics['conversions_value'] / $statistics['spend'])  : 0;
 | ||
| 
 | ||
| 
 | ||
|         // 返回分页数据
 | ||
|         $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,
 | ||
|             'statistics' => $statistics
 | ||
|         ];
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * 初始化统计数据
 | ||
|      */
 | ||
|     private function initializeStatistics()
 | ||
|     {
 | ||
|         return [
 | ||
|             'conversions_value' => 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
 | ||
|         ];
 | ||
|     }
 | ||
| }
 |