Skip to content

Commit 1e45fac

Browse files
authored
Merge pull request #3063 from codeeu/dev
Dev
2 parents 3fdca5e + 1edfd74 commit 1e45fac

1 file changed

Lines changed: 148 additions & 0 deletions

File tree

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<?php
2+
3+
namespace App\Console\Commands;
4+
5+
use App\Event;
6+
use Carbon\Carbon;
7+
use Illuminate\Console\Command;
8+
use Illuminate\Support\Facades\DB;
9+
use Illuminate\Support\Facades\Log;
10+
11+
/**
12+
* Generate recurring events based on `recurring_event` setting.
13+
*
14+
* Duplicates APPROVED events whose recurring_event is in ['daily','weekly','monthly']
15+
* and whose next occurrence does not exceed end_date.
16+
*
17+
* Clones event data and related pivot tables (themes, tags, audiences).
18+
* Stores reference in `source_ref` as "parent:{event_id}" for traceability.
19+
*
20+
* Options:
21+
* --dry-run : simulate generation without DB writes.
22+
*
23+
* Usage:
24+
* php artisan events:generate-recurring
25+
* php artisan events:generate-recurring --dry-run
26+
*/
27+
class GenerateRecurringEvents extends Command
28+
{
29+
protected $signature = 'events:generate-recurring {--dry-run : Simulate without saving any data}';
30+
31+
protected $description = 'Generate next occurrences for recurring events (daily, weekly, monthly)';
32+
33+
public function handle(): int
34+
{
35+
$dryRun = $this->option('dry-run');
36+
37+
$this->info(sprintf('Starting recurring event generation (dry-run: %s)', $dryRun ? 'YES' : 'NO'));
38+
39+
$now = Carbon::now();
40+
41+
// Only consider APPROVED events not yet ended
42+
$events = Event::query()
43+
->where('status', 'APPROVED')
44+
->whereIn('recurring_event', Event::RECURRING_EVENTS)
45+
->where('end_date', '>=', $now)
46+
->orderBy('start_date')
47+
->get();
48+
49+
if ($events->isEmpty()) {
50+
$this->info('No eligible recurring events found.');
51+
return self::SUCCESS;
52+
}
53+
54+
$this->info(sprintf('Found %d base recurring event(s)', $events->count()));
55+
56+
$created = 0;
57+
$bar = $this->output->createProgressBar($events->count());
58+
$bar->start();
59+
60+
foreach ($events as $event) {
61+
try {
62+
$nextStart = $this->getNextDate($event->start_date, $event->recurring_event);
63+
64+
// Skip if next start exceeds event's end_date
65+
if ($nextStart->gt($event->end_date)) {
66+
$bar->advance();
67+
continue;
68+
}
69+
70+
// Avoid duplicates: check for same parent + same next date
71+
$exists = Event::query()
72+
->where('source_ref', 'parent:' . $event->id)
73+
->whereDate('start_date', $nextStart->toDateString())
74+
->exists();
75+
76+
if ($exists) {
77+
$bar->advance();
78+
continue;
79+
}
80+
81+
// Clone event data
82+
$newEventData = $event->replicate([
83+
'id', 'created_at', 'updated_at'
84+
])->toArray();
85+
86+
$durationSeconds = Carbon::parse($event->end_date)->diffInSeconds(Carbon::parse($event->start_date));
87+
88+
$newEventData['start_date'] = $nextStart;
89+
$newEventData['end_date'] = $nextStart->copy()->addSeconds($durationSeconds);
90+
$newEventData['source_ref'] = 'parent:' . $event->id;
91+
$newEventData['status'] = $event->status;
92+
93+
if ($dryRun) {
94+
$this->line("\nWould create event for '{$event->title}' on {$nextStart->toDateTimeString()}");
95+
} else {
96+
DB::transaction(function () use ($event, $newEventData) {
97+
$newEvent = Event::create($newEventData);
98+
99+
// Clone relationships
100+
$newEvent->themes()->sync($event->themes->pluck('id')->toArray());
101+
$newEvent->tags()->sync($event->tags->pluck('id')->toArray());
102+
$newEvent->audiences()->sync($event->audiences->pluck('id')->toArray());
103+
});
104+
105+
$created++;
106+
}
107+
} catch (\Throwable $e) {
108+
Log::error('Recurring generation error', [
109+
'event_id' => $event->id,
110+
'message' => $e->getMessage(),
111+
]);
112+
} finally {
113+
$bar->advance();
114+
}
115+
}
116+
117+
$bar->finish();
118+
$this->newLine(2);
119+
$this->info(sprintf('Recurring generation completed. %d new event(s) created.', $created));
120+
121+
return self::SUCCESS;
122+
}
123+
124+
/**
125+
* Determine the next valid date for the recurrence type relative to "now".
126+
*/
127+
private function getNextDate(Carbon $startDate, string $recurrence): Carbon
128+
{
129+
$next = $startDate->copy();
130+
$now = Carbon::now();
131+
132+
while ($next->lte($now)) {
133+
switch ($recurrence) {
134+
case 'daily':
135+
$next->addDay();
136+
break;
137+
case 'weekly':
138+
$next->addWeek();
139+
break;
140+
case 'monthly':
141+
$next->addMonth();
142+
break;
143+
}
144+
}
145+
146+
return $next;
147+
}
148+
}

0 commit comments

Comments
 (0)