| @@ -76,11 +76,52 @@ | ||
| 76 | 76 | 'callback' => [$this, 'get_day_entry_id_by_day_id'], |
| 77 | 77 | 'permission_callback' => [$this, 'check_permission'], |
| 78 | 78 | ], |
| 79 | 79 | ]); |
| 80 | + | |
| 81 | + // Bulk-update / -create activities for a single day in one request. | |
| 82 | + // Replaces the React day-edit "save" path that previously fired one PUT | |
| 83 | + // per activity (5 activities = 5 sequential round-trips). The endpoint | |
| 84 | + // accepts an `activities` array carrying full payloads + optional `id` | |
| 85 | + // per row; rows with `id` are PUTs, rows without are POSTs. | |
| 86 | + register_rest_route($namespace, '/' . $base . '/day/(?P<day_id>[\d]+)/activities/bulk', [ | |
| 87 | + [ | |
| 88 | + 'methods' => \WP_REST_Server::EDITABLE, | |
| 89 | + 'callback' => [$this, 'bulk_save_day_activities'], | |
| 90 | + 'permission_callback' => [$this, 'check_permission'], | |
| 91 | + ], | |
| 92 | + ]); | |
| 80 | 93 | } |
| 81 | 94 | |
| 82 | 95 | /** |
| 96 | + * Itinerary endpoints are bound to a trip. Reads use the view-trips | |
| 97 | + * cap (Sales Agent / Front Desk / Guide / Auditor all hold this); | |
| 98 | + * writes use the edit-trips cap. WP admins pass via the Team | |
| 99 | + * module's admin-fallback filter. Overrides BaseController's default | |
| 100 | + * which was the coarse `manage_options` check. | |
| 101 | + */ | |
| 102 | + public function check_permission(?WP_REST_Request $request = null): bool | |
| 103 | + { | |
| 104 | + if ($request === null) { | |
| 105 | + return true; | |
| 106 | + } | |
| 107 | + if (!is_user_logged_in()) { | |
| 108 | + return false; | |
| 109 | + } | |
| 110 | + switch ($request->get_method()) { | |
| 111 | + case 'GET': | |
| 112 | + return current_user_can('yatra_view_trips'); | |
| 113 | + case 'POST': | |
| 114 | + case 'PUT': | |
| 115 | + case 'PATCH': | |
| 116 | + case 'DELETE': | |
| 117 | + return current_user_can('yatra_edit_trips'); | |
| 118 | + default: | |
| 119 | + return current_user_can('yatra_view_trips'); | |
| 120 | + } | |
| 121 | + } | |
| 122 | + | |
| 123 | + /** | |
| 83 | 124 | * Get items by trip ID |
| 84 | 125 | */ |
| 85 | 126 | public function get_items_by_trip(WP_REST_Request $request) |
| 86 | 127 | { |
| @@ -202,8 +243,50 @@ | ||
| 202 | 243 | } |
| 203 | 244 | } |
| 204 | 245 | |
| 205 | 246 | /** |
| 247 | + * Bulk-save the activities of a single day in one request. | |
| 248 | + * | |
| 249 | + * Body shape: | |
| 250 | + * { | |
| 251 | + * "trip_id": 14, | |
| 252 | + * "activities": [ | |
| 253 | + * { "id": 39, "title": "...", "order": 0, ...full activity fields... }, | |
| 254 | + * { "title": "...", "order": 1, ... } // no id => create | |
| 255 | + * ] | |
| 256 | + * } | |
| 257 | + * | |
| 258 | + * Each row goes through the same validation / sanitisation as a single | |
| 259 | + * update / create — the difference is one HTTP round-trip and one cache | |
| 260 | + * invalidation instead of N. Returns per-row results so the React layer | |
| 261 | + * can surface partial failures without losing the rows that succeeded. | |
| 262 | + */ | |
| 263 | + public function bulk_save_day_activities(WP_REST_Request $request) | |
| 264 | + { | |
| 265 | + try { | |
| 266 | + $dayId = (int) $request->get_param('day_id'); | |
| 267 | + $body = $request->get_json_params() ?: []; | |
| 268 | + $tripId = isset($body['trip_id']) ? (int) $body['trip_id'] : 0; | |
| 269 | + $activities = $body['activities'] ?? []; | |
| 270 | + | |
| 271 | + if ($dayId <= 0) { | |
| 272 | + return $this->error_response('Invalid day_id', 400); | |
| 273 | + } | |
| 274 | + if (!is_array($activities)) { | |
| 275 | + return $this->error_response('activities must be an array', 400); | |
| 276 | + } | |
| 277 | + | |
| 278 | + $result = $this->service->bulkSaveDayActivities($dayId, $tripId, $activities); | |
| 279 | + | |
| 280 | + return $this->success_response($result); | |
| 281 | + } catch (\InvalidArgumentException $e) { | |
| 282 | + return $this->error_response($e->getMessage(), 400); | |
| 283 | + } catch (\Exception $e) { | |
| 284 | + return $this->error_response($e->getMessage(), 500); | |
| 285 | + } | |
| 286 | + } | |
| 287 | + | |
| 288 | + /** | |
| 206 | 289 | * Bulk delete items |
| 207 | 290 | */ |
| 208 | 291 | public function bulk_delete_items(WP_REST_Request $request) |
| 209 | 292 | { |
| @@ -209,20 +292,25 @@ | ||
| 209 | 292 | { |
| 210 | 293 | try { |
| 211 | 294 | $data = $request->get_json_params(); |
| 212 | 295 | $ids = $data['ids'] ?? []; |
| 296 | + $dayIds = $data['day_ids'] ?? []; | |
| 213 | 297 | |
| 214 | - if (empty($ids) || !is_array($ids)) { | |
| 298 | + $ids = is_array($ids) ? $ids : []; | |
| 299 | + $dayIds = is_array($dayIds) ? $dayIds : []; | |
| 300 | + | |
| 301 | + if (empty($ids) && empty($dayIds)) { | |
| 215 | 302 | return $this->error_response('No IDs provided for bulk delete', 400); |
| 216 | 303 | } |
| 217 | 304 | |
| 218 | - $result = $this->service->bulkDelete($ids); | |
| 305 | + $result = $this->service->bulkDelete($ids, $dayIds); | |
| 219 | 306 | |
| 220 | 307 | return $this->success_response([ |
| 221 | 308 | 'deleted' => $result['deleted'], |
| 222 | 309 | 'failed' => $result['failed'], |
| 223 | 310 | 'message' => sprintf( |
| 224 | - __('%d item(s) deleted successfully. %d item(s) failed to delete.', 'yatra'), | |
| 311 | + /* translators: 1: number of items deleted, 2: number of items that failed to delete. */ | |
| 312 | + __('%1$d item(s) deleted successfully. %2$d item(s) failed to delete.', 'yatra'), | |
| 225 | 313 | $result['deleted'], |
| 226 | 314 | $result['failed'] |
| 227 | 315 | ), |
| 228 | 316 | ]); |