You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

175 lines
8.0 KiB

1 day ago
<?php
namespace App\Console\Commands;
use App\Models\ActivityDay;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
class ExportVenueActivityStatsCommand extends Command
{
protected $signature = 'activities:export-venue-stats
{--from=2026-07-01 : 场次开始日期 Y-m-d}
{--to=2026-07-31 : 场次结束日期 Y-m-d}
{--output= : 导出 xlsx 路径}';
protected $description = '导出指定日期范围内各场馆活动汇总(按场馆一行)及场次明细 Excel';
public function handle(): int
{
$start = (string) $this->option('from');
$end = (string) $this->option('to');
$out = (string) ($this->option('output') ?: storage_path(
'app/export_venues_activities_'.str_replace('-', '', $start).'_'.str_replace('-', '', $end).'.xlsx'
));
$regPeople = "SUM(CASE WHEN reservations.status <> 'cancelled' THEN COALESCE(reservations.ticket_count, 1) ELSE 0 END)";
$regOrders = "SUM(CASE WHEN reservations.status <> 'cancelled' THEN 1 ELSE 0 END)";
$cancelOrders = "SUM(CASE WHEN reservations.status = 'cancelled' THEN 1 ELSE 0 END)";
$cancelPeople = "SUM(CASE WHEN reservations.status = 'cancelled' THEN COALESCE(reservations.ticket_count, 1) ELSE 0 END)";
$verifiedPeople = "SUM(CASE WHEN reservations.status = 'verified' THEN COALESCE(reservations.ticket_count, 1) ELSE 0 END)";
$pendingPeople = "SUM(CASE WHEN reservations.status = 'pending' THEN COALESCE(reservations.ticket_count, 1) ELSE 0 END)";
$expiredPeople = "SUM(CASE WHEN reservations.status = 'expired' THEN COALESCE(reservations.ticket_count, 1) ELSE 0 END)";
$baseJoin = function ($q) {
$q->join('activities', function ($j) {
$j->on('activities.id', '=', 'activity_days.activity_id')->whereNull('activities.deleted_at');
})
->join('venues', 'venues.id', '=', 'activities.venue_id')
->join('reservations', function ($j) {
$j->on('reservations.activity_day_id', '=', 'activity_days.id')
->whereNull('reservations.deleted_at')
->where(function ($qq) {
$qq->whereNull('reservations.reservation_kind')
->orWhere('reservations.reservation_kind', 'activity');
});
});
};
$venueRows = ActivityDay::query()
->select([
'venues.id as venue_id',
'venues.name as venue_name',
DB::raw("GROUP_CONCAT(DISTINCT DATE_FORMAT(activity_days.activity_date, '%Y-%m-%d') ORDER BY activity_days.activity_date SEPARATOR ', ') as activity_dates"),
DB::raw('COUNT(DISTINCT activities.id) as activity_count'),
DB::raw('COUNT(DISTINCT activity_days.id) as session_count'),
DB::raw("{$regOrders} as registration_orders"),
DB::raw("{$regPeople} as registration_people"),
DB::raw("{$cancelOrders} as cancelled_orders"),
DB::raw("{$cancelPeople} as cancelled_people"),
DB::raw("{$verifiedPeople} as verified_people"),
])
->tap($baseJoin)
->whereBetween('activity_days.activity_date', [$start, $end])
->groupBy('venues.id', 'venues.name')
->havingRaw("{$regOrders} > 0 OR {$cancelOrders} > 0")
->orderBy('venues.name')
->get();
$sessionRows = ActivityDay::query()
->select([
'venues.id as venue_id',
'venues.name as venue_name',
'activities.id as activity_id',
'activities.title as activity_title',
'activity_days.id as activity_day_id',
'activity_days.session_name',
'activity_days.activity_date',
'activity_days.session_start_at',
'activity_days.session_end_at',
'activity_days.day_quota',
'activity_days.booked_count',
DB::raw("{$regOrders} as registration_orders"),
DB::raw("{$regPeople} as registration_people"),
DB::raw("{$cancelOrders} as cancelled_orders"),
DB::raw("{$cancelPeople} as cancelled_people"),
DB::raw("{$verifiedPeople} as verified_people"),
DB::raw("{$pendingPeople} as pending_people"),
DB::raw("{$expiredPeople} as expired_people"),
])
->tap($baseJoin)
->whereBetween('activity_days.activity_date', [$start, $end])
->groupBy(
'venues.id',
'venues.name',
'activities.id',
'activities.title',
'activity_days.id',
'activity_days.session_name',
'activity_days.activity_date',
'activity_days.session_start_at',
'activity_days.session_end_at',
'activity_days.day_quota',
'activity_days.booked_count'
)
->havingRaw("{$regOrders} > 0 OR {$cancelOrders} > 0")
->orderBy('venues.name')
->orderBy('activity_days.activity_date')
->orderBy('activity_days.session_start_at')
->orderBy('activities.id')
->get();
$spreadsheet = new Spreadsheet;
$sheet1 = $spreadsheet->getActiveSheet();
$sheet1->setTitle('场馆汇总');
$sheet1Data = [['场馆ID', '场馆名称', '活动日期', '活动数量', '场次数量', '报名人数', '报名总人数', '已取消数', '已取消人数', '到场人数(已核销)']];
foreach ($venueRows as $r) {
$sheet1Data[] = [
$r->venue_id,
$r->venue_name,
$r->activity_dates,
(int) $r->activity_count,
(int) $r->session_count,
(int) $r->registration_orders,
(int) $r->registration_people,
(int) $r->cancelled_orders,
(int) $r->cancelled_people,
(int) $r->verified_people,
];
}
$sheet1->fromArray($sheet1Data, null, 'A1');
$sheet2 = $spreadsheet->createSheet();
$sheet2->setTitle('场次信息');
$sheet2Data = [['场馆ID', '场馆名称', '活动ID', '活动名称', '场次ID', '场次名称', '活动日期', '场次开始时间', '场次结束时间', '场次名额', '已占用名额', '报名人数', '报名总人数', '已取消数', '已取消人数', '已核销人数', '待核销人数', '已过期人数']];
foreach ($sessionRows as $r) {
$sheet2Data[] = [
$r->venue_id,
$r->venue_name,
$r->activity_id,
$r->activity_title,
$r->activity_day_id,
$r->session_name,
$r->activity_date ? (string) $r->activity_date : '',
$r->session_start_at ? (string) $r->session_start_at : '',
$r->session_end_at ? (string) $r->session_end_at : '',
$r->day_quota,
$r->booked_count,
(int) $r->registration_orders,
(int) $r->registration_people,
(int) $r->cancelled_orders,
(int) $r->cancelled_people,
(int) $r->verified_people,
(int) $r->pending_people,
(int) $r->expired_people,
];
}
$sheet2->fromArray($sheet2Data, null, 'A1');
$dir = dirname($out);
if (! is_dir($dir)) {
mkdir($dir, 0755, true);
}
(new Xlsx($spreadsheet))->save($out);
$this->info("场馆汇总:{$venueRows->count()} 行");
$this->info("场次信息:{$sessionRows->count()} 行");
$this->info("Excel 已生成:{$out}");
return self::SUCCESS;
}
}