koprogo_api/application/use_cases/
resource_booking_use_cases.rs1use crate::application::dto::{
2 BookingStatisticsDto, CreateResourceBookingDto, ResourceBookingResponseDto,
3 UpdateResourceBookingDto,
4};
5use crate::application::ports::{OwnerRepository, ResourceBookingRepository};
6use crate::domain::entities::{BookingStatus, ResourceBooking, ResourceType};
7use chrono::Utc;
8use std::sync::Arc;
9use uuid::Uuid;
10
11pub struct ResourceBookingUseCases {
16 booking_repo: Arc<dyn ResourceBookingRepository>,
17 owner_repo: Arc<dyn OwnerRepository>,
18}
19
20impl ResourceBookingUseCases {
21 pub fn new(
22 booking_repo: Arc<dyn ResourceBookingRepository>,
23 owner_repo: Arc<dyn OwnerRepository>,
24 ) -> Self {
25 Self {
26 booking_repo,
27 owner_repo,
28 }
29 }
30
31 pub async fn create_booking(
42 &self,
43 booked_by: Uuid,
44 dto: CreateResourceBookingDto,
45 ) -> Result<ResourceBookingResponseDto, String> {
46 let booking = ResourceBooking::new(
48 dto.building_id,
49 dto.resource_type.clone(),
50 dto.resource_name.clone(),
51 booked_by,
52 dto.start_time,
53 dto.end_time,
54 dto.notes.clone(),
55 dto.recurring_pattern.clone(),
56 dto.recurrence_end_date,
57 dto.max_duration_hours,
58 dto.max_advance_days,
59 )?;
60
61 let conflicts = self
63 .booking_repo
64 .find_conflicts(
65 dto.building_id,
66 dto.resource_type,
67 &dto.resource_name,
68 dto.start_time,
69 dto.end_time,
70 None, )
72 .await?;
73
74 if !conflicts.is_empty() {
75 return Err(format!(
76 "Booking conflicts with {} existing booking(s) for this resource",
77 conflicts.len()
78 ));
79 }
80
81 let created = self.booking_repo.create(&booking).await?;
83
84 self.enrich_booking_response(created).await
86 }
87
88 pub async fn get_booking(
90 &self,
91 booking_id: Uuid,
92 ) -> Result<ResourceBookingResponseDto, String> {
93 let booking = self
94 .booking_repo
95 .find_by_id(booking_id)
96 .await?
97 .ok_or("Booking not found".to_string())?;
98
99 self.enrich_booking_response(booking).await
100 }
101
102 pub async fn list_building_bookings(
104 &self,
105 building_id: Uuid,
106 ) -> Result<Vec<ResourceBookingResponseDto>, String> {
107 let bookings = self.booking_repo.find_by_building(building_id).await?;
108 self.enrich_bookings_response(bookings).await
109 }
110
111 pub async fn list_by_resource_type(
113 &self,
114 building_id: Uuid,
115 resource_type: ResourceType,
116 ) -> Result<Vec<ResourceBookingResponseDto>, String> {
117 let bookings = self
118 .booking_repo
119 .find_by_building_and_resource_type(building_id, resource_type)
120 .await?;
121 self.enrich_bookings_response(bookings).await
122 }
123
124 pub async fn list_by_resource(
126 &self,
127 building_id: Uuid,
128 resource_type: ResourceType,
129 resource_name: String,
130 ) -> Result<Vec<ResourceBookingResponseDto>, String> {
131 let bookings = self
132 .booking_repo
133 .find_by_resource(building_id, resource_type, &resource_name)
134 .await?;
135 self.enrich_bookings_response(bookings).await
136 }
137
138 pub async fn list_user_bookings(
140 &self,
141 user_id: Uuid,
142 ) -> Result<Vec<ResourceBookingResponseDto>, String> {
143 let bookings = self.booking_repo.find_by_user(user_id).await?;
144 self.enrich_bookings_response(bookings).await
145 }
146
147 pub async fn list_user_bookings_by_status(
149 &self,
150 user_id: Uuid,
151 status: BookingStatus,
152 ) -> Result<Vec<ResourceBookingResponseDto>, String> {
153 let bookings = self
154 .booking_repo
155 .find_by_user_and_status(user_id, status)
156 .await?;
157 self.enrich_bookings_response(bookings).await
158 }
159
160 pub async fn list_building_bookings_by_status(
162 &self,
163 building_id: Uuid,
164 status: BookingStatus,
165 ) -> Result<Vec<ResourceBookingResponseDto>, String> {
166 let bookings = self
167 .booking_repo
168 .find_by_building_and_status(building_id, status)
169 .await?;
170 self.enrich_bookings_response(bookings).await
171 }
172
173 pub async fn list_upcoming_bookings(
175 &self,
176 building_id: Uuid,
177 limit: Option<i64>,
178 ) -> Result<Vec<ResourceBookingResponseDto>, String> {
179 let bookings = self.booking_repo.find_upcoming(building_id, limit).await?;
180 self.enrich_bookings_response(bookings).await
181 }
182
183 pub async fn list_active_bookings(
185 &self,
186 building_id: Uuid,
187 ) -> Result<Vec<ResourceBookingResponseDto>, String> {
188 let bookings = self.booking_repo.find_active(building_id).await?;
189 self.enrich_bookings_response(bookings).await
190 }
191
192 pub async fn list_past_bookings(
194 &self,
195 building_id: Uuid,
196 limit: Option<i64>,
197 ) -> Result<Vec<ResourceBookingResponseDto>, String> {
198 let bookings = self.booking_repo.find_past(building_id, limit).await?;
199 self.enrich_bookings_response(bookings).await
200 }
201
202 pub async fn update_booking(
209 &self,
210 booking_id: Uuid,
211 updater_id: Uuid,
212 dto: UpdateResourceBookingDto,
213 ) -> Result<ResourceBookingResponseDto, String> {
214 let mut booking = self
215 .booking_repo
216 .find_by_id(booking_id)
217 .await?
218 .ok_or("Booking not found".to_string())?;
219
220 if booking.booked_by != updater_id {
222 return Err("Only the booking owner can update this booking".to_string());
223 }
224
225 booking.update_details(dto.resource_name, dto.notes)?;
227
228 let updated = self.booking_repo.update(&booking).await?;
230
231 self.enrich_booking_response(updated).await
233 }
234
235 pub async fn cancel_booking(
240 &self,
241 booking_id: Uuid,
242 canceller_id: Uuid,
243 ) -> Result<ResourceBookingResponseDto, String> {
244 let mut booking = self
245 .booking_repo
246 .find_by_id(booking_id)
247 .await?
248 .ok_or("Booking not found".to_string())?;
249
250 booking.cancel(canceller_id)?;
252
253 let updated = self.booking_repo.update(&booking).await?;
255
256 self.enrich_booking_response(updated).await
258 }
259
260 pub async fn complete_booking(
265 &self,
266 booking_id: Uuid,
267 ) -> Result<ResourceBookingResponseDto, String> {
268 let mut booking = self
269 .booking_repo
270 .find_by_id(booking_id)
271 .await?
272 .ok_or("Booking not found".to_string())?;
273
274 booking.complete()?;
276
277 let updated = self.booking_repo.update(&booking).await?;
279
280 self.enrich_booking_response(updated).await
282 }
283
284 pub async fn mark_no_show(
289 &self,
290 booking_id: Uuid,
291 ) -> Result<ResourceBookingResponseDto, String> {
292 let mut booking = self
293 .booking_repo
294 .find_by_id(booking_id)
295 .await?
296 .ok_or("Booking not found".to_string())?;
297
298 booking.mark_no_show()?;
300
301 let updated = self.booking_repo.update(&booking).await?;
303
304 self.enrich_booking_response(updated).await
306 }
307
308 pub async fn confirm_booking(
313 &self,
314 booking_id: Uuid,
315 ) -> Result<ResourceBookingResponseDto, String> {
316 let mut booking = self
317 .booking_repo
318 .find_by_id(booking_id)
319 .await?
320 .ok_or("Booking not found".to_string())?;
321
322 booking.confirm()?;
324
325 let updated = self.booking_repo.update(&booking).await?;
327
328 self.enrich_booking_response(updated).await
330 }
331
332 pub async fn delete_booking(&self, booking_id: Uuid, deleter_id: Uuid) -> Result<(), String> {
338 let booking = self
339 .booking_repo
340 .find_by_id(booking_id)
341 .await?
342 .ok_or("Booking not found".to_string())?;
343
344 if booking.booked_by != deleter_id {
346 return Err("Only the booking owner can delete this booking".to_string());
347 }
348
349 self.booking_repo.delete(booking_id).await
350 }
351
352 pub async fn check_conflicts(
358 &self,
359 building_id: Uuid,
360 resource_type: ResourceType,
361 resource_name: String,
362 start_time: chrono::DateTime<Utc>,
363 end_time: chrono::DateTime<Utc>,
364 exclude_booking_id: Option<Uuid>,
365 ) -> Result<Vec<ResourceBookingResponseDto>, String> {
366 let conflicts = self
367 .booking_repo
368 .find_conflicts(
369 building_id,
370 resource_type,
371 &resource_name,
372 start_time,
373 end_time,
374 exclude_booking_id,
375 )
376 .await?;
377
378 self.enrich_bookings_response(conflicts).await
379 }
380
381 pub async fn get_statistics(&self, building_id: Uuid) -> Result<BookingStatisticsDto, String> {
383 self.booking_repo.get_statistics(building_id).await
384 }
385
386 async fn enrich_booking_response(
388 &self,
389 booking: ResourceBooking,
390 ) -> Result<ResourceBookingResponseDto, String> {
391 let owner = self
393 .owner_repo
394 .find_by_id(booking.booked_by)
395 .await?
396 .ok_or("Booking owner not found".to_string())?;
397
398 let booked_by_name = format!("{} {}", owner.first_name, owner.last_name);
399
400 Ok(ResourceBookingResponseDto::from_entity(
401 booking,
402 booked_by_name,
403 ))
404 }
405
406 async fn enrich_bookings_response(
408 &self,
409 bookings: Vec<ResourceBooking>,
410 ) -> Result<Vec<ResourceBookingResponseDto>, String> {
411 let mut result = Vec::with_capacity(bookings.len());
412
413 for booking in bookings {
414 let enriched = self.enrich_booking_response(booking).await?;
415 result.push(enriched);
416 }
417
418 Ok(result)
419 }
420}