PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.4
Yatra – Travel Booking & Tour Operator Software v3.0.4
3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 2.0.10 2.0.11 All 82 releases
yatra / app / Services / RecurringRuleService.php

RecurringRuleService.php in Yatra – Travel Booking & Tour Operator Software 3.0.4, at app/Services/RecurringRuleService.php

297 lines 9.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Yatra\Services;
6
7 use Yatra\Repositories\RecurringRuleRepository;
8 use Yatra\Repositories\DepartureRepository;
9 use Yatra\Models\RecurringRule;
10
11 /**
12 * Recurring Rule Service
13 * Handles business logic for recurring rules and dynamic date generation
14 *
15 * Rules do NOT generate dates automatically in the database.
16 * They only act as rules for dynamic date generation on the frontend.
17 */
18 class RecurringRuleService
19 {
20 private RecurringRuleRepository $ruleRepository;
21 private DepartureRepository $departureRepository;
22
23 public function __construct(
24 RecurringRuleRepository $ruleRepository,
25 DepartureRepository $departureRepository
26 ) {
27 $this->ruleRepository = $ruleRepository;
28 $this->departureRepository = $departureRepository;
29 }
30
31 /**
32 * Generate dates based on recurring rules for a trip
33 *
34 * This method dynamically generates dates without storing them in the database.
35 * It checks for manually created departures and excludes those dates.
36 *
37 * @param int $tripId Trip ID
38 * @param string $fromDate Start date (YYYY-MM-DD)
39 * @param string $toDate End date (YYYY-MM-DD)
40 * @return array Array of generated date information
41 */
42 public function generateDatesForTrip(int $tripId, string $fromDate, string $toDate): array
43 {
44 // Get active recurring rules for this trip
45 $rules = $this->ruleRepository->findActiveForDateRange($tripId, $fromDate, $toDate);
46
47 if (empty($rules)) {
48 return [];
49 }
50
51 // Get all manually created departures for this date range
52 $manualDepartures = $this->departureRepository->findByTripId($tripId, [
53 'date_from' => $fromDate,
54 'date_to' => $toDate,
55 'source' => 'manual',
56 ]);
57
58 // Create a map of dates that have manual departures (these override rules)
59 $manualDates = [];
60 foreach ($manualDepartures as $departure) {
61 $manualDates[$departure->date] = true;
62 }
63
64 $generatedDates = [];
65
66 // Generate dates for each rule
67 foreach ($rules as $rule) {
68 $ruleDates = $this->generateDatesForRule($rule, $fromDate, $toDate);
69
70 // Filter out dates that have manual departures
71 foreach ($ruleDates as $date) {
72 if (!isset($manualDates[$date['date']])) {
73 $generatedDates[$date['date']] = $date;
74 }
75 }
76 }
77
78 // Sort by date
79 ksort($generatedDates);
80
81 return array_values($generatedDates);
82 }
83
84 /**
85 * Generate dates for a specific rule
86 *
87 * @param RecurringRule $rule The recurring rule
88 * @param string $fromDate Start date
89 * @param string $toDate End date
90 * @return array Array of date information
91 */
92 private function generateDatesForRule(RecurringRule $rule, string $fromDate, string $toDate): array
93 {
94 $dates = [];
95 $start = max($fromDate, $rule->start_date ?? $fromDate);
96 $end = min($toDate, $rule->end_date ?? $toDate);
97
98 if ($start > $end) {
99 return [];
100 }
101
102 $current = strtotime($start);
103 $endTimestamp = strtotime($end);
104
105 switch ($rule->recurrence_type) {
106 case 'daily':
107 // Generate every day
108 while ($current <= $endTimestamp) {
109 $dateStr = date('Y-m-d', $current);
110 if ($rule->isActiveForDate($dateStr)) {
111 $dates[] = $this->buildDateInfo($dateStr, $rule);
112 }
113 $current = strtotime('+1 day', $current);
114 }
115 break;
116
117 case 'weekly':
118 // Generate for specified weekdays
119 if (empty($rule->days_of_week)) {
120 break;
121 }
122
123 while ($current <= $endTimestamp) {
124 $dayOfWeek = (int) date('w', $current); // 0 = Sunday, 6 = Saturday
125 $dateStr = date('Y-m-d', $current);
126
127 if (in_array($dayOfWeek, $rule->days_of_week, true) && $rule->isActiveForDate($dateStr)) {
128 $dates[] = $this->buildDateInfo($dateStr, $rule);
129 }
130
131 $current = strtotime('+1 day', $current);
132 }
133 break;
134
135 case 'monthly':
136 // Generate for specific day of month (e.g., first Monday)
137 // This is simplified - you may want to enhance this
138 while ($current <= $endTimestamp) {
139 $dateStr = date('Y-m-d', $current);
140 if ($rule->isActiveForDate($dateStr)) {
141 $dates[] = $this->buildDateInfo($dateStr, $rule);
142 }
143 $current = strtotime('+1 month', $current);
144 }
145 break;
146
147 case 'custom_days':
148 // Generate for custom weekdays
149 if (empty($rule->days_of_week)) {
150 break;
151 }
152
153 while ($current <= $endTimestamp) {
154 $dayOfWeek = (int) date('w', $current);
155 $dateStr = date('Y-m-d', $current);
156
157 if (in_array($dayOfWeek, $rule->days_of_week, true) && $rule->isActiveForDate($dateStr)) {
158 $dates[] = $this->buildDateInfo($dateStr, $rule);
159 }
160
161 $current = strtotime('+1 day', $current);
162 }
163 break;
164 }
165
166 return $dates;
167 }
168
169 /**
170 * Build date information array from rule
171 */
172 private function buildDateInfo(string $date, RecurringRule $rule): array
173 {
174 $cap = (int) ($rule->capacity_value ?? 0);
175
176 return [
177 'date' => $date,
178 'max_capacity' => $cap,
179 'base_price' => $rule->price_override,
180 'pricing_by_traveler_type' => null, // This field doesn't exist in the new schema
181 'source' => 'recurring_rule',
182 'rule_id' => $rule->id,
183 ];
184 }
185
186 /**
187 * Get preview of next N generated dates for a rule
188 *
189 * @param int $ruleId Rule ID
190 * @param int $count Number of dates to preview
191 * @return array Preview dates
192 */
193 public function getPreviewDates(int $ruleId, int $count = 10): array
194 {
195 $rule = $this->ruleRepository->findModel($ruleId);
196
197 if (!$rule) {
198 return [];
199 }
200
201 $fromDate = date('Y-m-d');
202 $toDate = date('Y-m-d', strtotime('+12 months'));
203
204 $dates = $this->generateDatesForRule($rule, $fromDate, $toDate);
205
206 return array_slice($dates, 0, $count);
207 }
208
209 /**
210 * Validate rule data
211 */
212 public function validate(array $data, ?int $id = null): void
213 {
214 if (empty($data['trip_id'])) {
215 throw new \InvalidArgumentException('Trip ID is required');
216 }
217
218 if (empty($data['recurrence_type'])) {
219 throw new \InvalidArgumentException('Recurrence type is required');
220 }
221
222 $validTypes = ['daily', 'weekly', 'monthly', 'custom_days'];
223 if (!in_array($data['recurrence_type'], $validTypes, true)) {
224 throw new \InvalidArgumentException('Invalid recurrence type. Must be: ' . implode(', ', $validTypes));
225 }
226
227 // Validate weekdays for weekly/custom_days
228 if (in_array($data['recurrence_type'], ['weekly', 'custom_days'], true)) {
229 if (empty($data['weekdays']) || !is_array($data['weekdays'])) {
230 throw new \InvalidArgumentException('Weekdays are required for weekly/custom_days rules');
231 }
232
233 foreach ($data['weekdays'] as $day) {
234 $dayInt = (int) $day;
235 if ($dayInt < 0 || $dayInt > 6) {
236 throw new \InvalidArgumentException('Weekdays must be 0-6 (Sunday-Saturday)');
237 }
238 }
239 }
240
241 // Validate dates
242 if (!empty($data['start_date']) && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $data['start_date'])) {
243 throw new \InvalidArgumentException('Invalid start date format. Use YYYY-MM-DD');
244 }
245
246 if (!empty($data['end_date'])) {
247 if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $data['end_date'])) {
248 throw new \InvalidArgumentException('Invalid end date format. Use YYYY-MM-DD');
249 }
250
251 if (!empty($data['start_date']) && strtotime($data['end_date']) < strtotime($data['start_date'])) {
252 throw new \InvalidArgumentException('End date must be after start date');
253 }
254 }
255
256 // Validate capacity
257 if (isset($data['max_capacity']) && (int) $data['max_capacity'] < 1) {
258 throw new \InvalidArgumentException('Max capacity must be at least 1');
259 }
260 }
261
262 /**
263 * Create a recurring rule
264 */
265 public function create(array $data): int
266 {
267 $this->validate($data);
268 return $this->ruleRepository->create($data);
269 }
270
271 /**
272 * Update a recurring rule
273 */
274 public function update(int $id, array $data): bool
275 {
276 $this->validate($data, $id);
277 return $this->ruleRepository->update($id, $data);
278 }
279
280 /**
281 * Delete a recurring rule
282 */
283 public function delete(int $id): bool
284 {
285 return $this->ruleRepository->delete($id);
286 }
287
288 /**
289 * Get rules by trip ID
290 */
291 public function getByTripId(int $tripId, bool $activeOnly = false): array
292 {
293 return $this->ruleRepository->findByTripId($tripId, $activeOnly);
294 }
295 }
296
297