PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.8
Yatra – Travel Booking & Tour Operator Software v3.0.2.8
3.0.16 3.0.15 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 All 84 releases
yatra / app / Services / ItineraryService.php

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

241 lines 8.8 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\ItineraryRepository;
8 use Yatra\Repositories\TripRepository;
9 use InvalidArgumentException;
10
11 /**
12 * Itinerary Service
13 * Business logic for itinerary entries
14 */
15 class ItineraryService
16 {
17 private ItineraryRepository $repository;
18 private TripRepository $tripRepository;
19
20 public function __construct(ItineraryRepository $repository, ?TripRepository $tripRepository = null)
21 {
22 $this->repository = $repository;
23 $this->tripRepository = $tripRepository ?? new TripRepository();
24 }
25
26 /**
27 * Validate itinerary entry data
28 */
29 public function validate(array $data, ?int $id = null): void
30 {
31 // Required fields
32 if (empty($data['trip_id'])) {
33 throw new InvalidArgumentException(__('Trip ID is required', 'yatra'));
34 }
35
36 if (empty($data['day'])) {
37 throw new InvalidArgumentException(__('Day number is required', 'yatra'));
38 }
39
40 // For day entries (item_type_id and item_id are null), title can be empty if day_title is provided
41 // For activity entries, title is required
42 $isDayEntry = empty($data['item_type_id']) && empty($data['item_id']);
43 if (!$isDayEntry && (empty($data['title']) || trim($data['title']) === '')) {
44 throw new InvalidArgumentException(__('Title is required', 'yatra'));
45 }
46
47 // For day entries, if title is empty, use day_title or generate one
48 if ($isDayEntry && (empty($data['title']) || trim($data['title']) === '')) {
49 $dayNumber = (int) $data['day'];
50 $data['title'] = !empty($data['day_title']) ? trim($data['day_title']) : sprintf(__('Day %d', 'yatra'), $dayNumber);
51 }
52
53 // Validate trip exists
54 $trip = $this->tripRepository->find((int) $data['trip_id']);
55 if (!$trip) {
56 throw new InvalidArgumentException(__('Trip not found', 'yatra'));
57 }
58
59 // Validate day number
60 $dayNumber = (int) $data['day'];
61 if ($dayNumber < 1) {
62 throw new InvalidArgumentException(__('Day number must be at least 1', 'yatra'));
63 }
64
65 // Check for duplicate day numbers
66 // Only check if item_type_id and item_id are null (meaning it's a day creation/update, not an activity)
67 if (empty($data['item_type_id']) && empty($data['item_id'])) {
68 global $wpdb;
69
70 if (!$wpdb) {
71 // If for some reason the global $wpdb is not available, skip duplicate-day validation
72 return;
73 }
74
75 $itineraryRepository = new \Yatra\Repositories\ItineraryRepository();
76
77 // If updating, first check if the current entry is actually a day entry (not an activity)
78 if ($id !== null) {
79 $currentEntry = $itineraryRepository->getEntryWithRelations($id);
80
81 // If the current entry has item_type_id or item_id, it's an activity, not a day entry
82 // In this case, we're converting an activity to a day entry, which is unusual but allowed
83 // We'll still validate the day number
84 if ($currentEntry && (!empty($currentEntry->item_type_id) || !empty($currentEntry->item_id))) {
85 // This is converting an activity to a day entry - allow it but validate day number
86 // (fall through to day validation below)
87 } else if ($currentEntry && $currentEntry->day_id) {
88 // Get the current day's day_number
89 $currentDay = $itineraryRepository->getDayById((int) $currentEntry->day_id);
90
91 if ($currentDay && (int) $currentDay->day_number === $dayNumber) {
92 // Same day number - this is allowed (no change)
93 return; // No validation error
94 }
95 }
96 }
97
98 // Check if day entry already exists (not just day record)
99 // A day record can exist without a day entry (when only activities exist)
100 $existingDayEntry = $this->repository->findDayEntryByTripAndDayNumber((int) $data['trip_id'], $dayNumber);
101
102 if ($existingDayEntry) {
103 // Day entry already exists - this should be an update, not a create
104 // If updating, we've already checked above - allow it (frontend handles confirmation)
105 if ($id !== null) {
106 return; // Allow it, frontend handles the confirmation
107 } else {
108 // Creating new day entry but one already exists - this shouldn't happen
109 // The backend's createEntry will handle updating it, so allow it here
110 return; // Allow it, backend will update existing entry
111 }
112 }
113
114 // Check if day record exists (for informational purposes, but don't block)
115 // Day records can exist without day entries, so we allow creating day entries for existing day records
116 $existingDay = $itineraryRepository->findDayByTripAndDayNumber((int) $data['trip_id'], $dayNumber);
117
118 // If day record exists but day entry doesn't, allow creation (this is the normal case)
119 // Only block if we're creating a completely new day (both record and entry don't exist)
120 // and there's a conflict. But since we're creating a day entry, not a day record,
121 // we should allow it if the day record exists.
122 // The backend's createEntry will handle creating/updating the day record if needed.
123 }
124 }
125
126 /**
127 * Create itinerary entry
128 */
129 public function create(array $data): int
130 {
131 $this->validate($data);
132
133 global $wpdb;
134 // Clear any previous errors
135 $wpdb->last_error = '';
136
137 $result = $this->repository->createEntry($data);
138
139 // Check for database errors
140 if ($wpdb->last_error) {
141 // Check for duplicate entry error
142 if (strpos($wpdb->last_error, 'Duplicate entry') !== false) {
143 throw new \Exception('A day with this number already exists for this trip. Please choose a different day number.');
144 }
145
146 throw new \Exception('Failed to create itinerary entry. Please try again.');
147 }
148
149 return $result;
150 }
151
152 /**
153 * Update itinerary entry
154 * @param int $id Entry ID
155 * @param array $data Update data
156 * @param string|null $mode 'day' or 'activity' to specify which table to update
157 */
158 public function update(int $id, array $data, ?string $mode = null): bool
159 {
160 $this->validate($data, $id);
161
162 global $wpdb;
163 // Clear any previous errors
164 $wpdb->last_error = '';
165
166 $result = $this->repository->updateEntry($id, $data, $mode);
167
168 // Check for database errors
169 if ($wpdb->last_error) {
170 // Check for duplicate entry error
171 if (strpos($wpdb->last_error, 'Duplicate entry') !== false) {
172 throw new \Exception('A day with this number already exists for this trip. Please choose a different day number.');
173 }
174
175 throw new \Exception('Failed to update itinerary entry. Please try again.');
176 }
177
178 return $result;
179 }
180
181 /**
182 * Get itinerary entry by ID
183 */
184 public function find(int $id): ?\stdClass
185 {
186 $result = $this->repository->getEntryWithRelations($id);
187 return $result;
188 }
189
190 /**
191 * Get activity entry by ID (specifically from entries table)
192 */
193 public function findActivity(int $id): ?\stdClass
194 {
195 return $this->repository->getActivityEntry($id);
196 }
197
198 /**
199 * Delete itinerary entry
200 * @param int $id Entry ID
201 * @param string|null $mode 'day' or 'activity' to specify which table to delete from
202 */
203 public function delete(int $id, ?string $mode = null): bool
204 {
205 return $this->repository->delete($id, $mode);
206 }
207
208 /**
209 * Bulk delete itinerary entries
210 * @param array $ids Array of entry IDs to delete
211 * @return array ['deleted' => count, 'failed' => count]
212 */
213 public function bulkDelete(array $ids): array
214 {
215 if (empty($ids)) {
216 return ['deleted' => 0, 'failed' => 0];
217 }
218
219 // Validate all IDs are integers
220 $ids = array_map('intval', $ids);
221 $ids = array_filter($ids, function($id) {
222 return $id > 0;
223 });
224
225 if (empty($ids)) {
226 return ['deleted' => 0, 'failed' => 0];
227 }
228
229 return $this->repository->bulkDelete($ids);
230 }
231
232 /**
233 * Get all itinerary entries for a specific trip
234 */
235 public function getByTripId(int $tripId): array
236 {
237 return $this->repository->getByTripId($tripId);
238 }
239 }
240
241