square_ox/api/
bookings.rs

1/*!
2Bookings functionality of the [Square API](https://developer.squareup.com).
3 */
4
5use crate::client::SquareClient;
6use crate::api::{Verb, SquareAPI};
7use crate::errors::{SquareError, SearchQueryBuildError, BookingsPostBuildError, BookingsCancelBuildError, ValidationError};
8use crate::response::SquareResponse;
9use crate::objects::{AppointmentSegment, Booking, FilterValue, enums::BusinessAppointmentSettingsBookingLocationType, StartAtRange, SegmentFilter, AvailabilityQueryFilter};
10
11use serde::{Deserialize, Serialize};
12use uuid::Uuid;
13use crate::builder::{AddField, Builder, ParentBuilder, Validate, Buildable, BackIntoBuilder};
14use square_ox_derive::Builder;
15
16impl SquareClient {
17    pub fn bookings(&self) -> Bookings {
18        Bookings {
19            client: &self
20        }
21    }
22}
23
24pub struct Bookings<'a> {
25    client: &'a SquareClient,
26}
27
28impl<'a> Bookings<'a> {
29    /// Search for availability with the given search query to the Square API
30    /// and get the response back.
31    ///
32    /// # Arguments
33    /// * `search_query` - A vector of search query parameter created through the
34    /// [ListBookingsQueryBuilder](ListBookingsQueryBuilder)
35    pub async fn list(self, search_query: Option<Vec<(String, String)>>)
36                               -> Result<SquareResponse, SquareError> {
37        self.client.request(
38            Verb::GET,
39            SquareAPI::Bookings("".to_string()),
40            None::<&BookingsPost>,
41            search_query,
42        ).await
43    }
44
45    /// Search for availability with the given search query to the Square API
46    /// and get the response back.
47    ///
48    /// # Arguments
49    /// * `search_query` - A search query.
50    pub async fn search_availability(self, search_query: SearchAvailabilityQuery)
51                                     -> Result<SquareResponse, SquareError> {
52        self.client.request(
53            Verb::POST,
54            SquareAPI::Bookings("/availability/search".to_string()),
55            Some(&search_query),
56            None,
57        ).await
58    }
59
60    /// Create a booking with the given [BookingsPost](BookingsPost) to the Square API
61    /// and get the response back.
62    ///
63    /// # Arguments
64    /// * `create_booking` - A [BookingsPost](BookingsPost)
65    pub async fn create(self, booking_post: BookingsPost)
66                                -> Result<SquareResponse, SquareError> {
67        self.client.request(
68            Verb::POST,
69            SquareAPI::Bookings("".to_string()),
70            Some(&booking_post),
71            None,
72        ).await
73    }
74
75    /// Update a booking with the given [BookingsPost](BookingsPost) to the Square API
76    /// and get the response back.
77    ///
78    /// # Arguments
79    /// * `updated_booking` - A [BookingsPost](BookingsPost).
80    pub async fn update(self, updated_booking: BookingsPost, booking_id: String)
81                                -> Result<SquareResponse, SquareError> {
82        self.client.request(
83            Verb::PUT,
84            SquareAPI::Bookings(format!("/{}", booking_id)),
85            Some(&updated_booking),
86            None,
87        ).await
88    }
89
90    /// Retrieve an existing booking from the Square API.
91    ///
92    /// # Arguments
93    /// * `booking_id` - The id of the booking as a String
94    pub async fn retrieve(self, booking_id: String)
95                                  -> Result<SquareResponse, SquareError> {
96        self.client.request(
97            Verb::GET,
98            SquareAPI::Bookings(format!("/{}", booking_id)),
99            None::<&BookingsPost>,
100            None,
101        ).await
102    }
103
104    /// Create a booking with the given [Bookings](Bookings) to the Square API
105    /// and get the response back.
106    ///
107    /// # Arguments
108    /// * `booking_to_cancel` - A [BookingsCancel](BookingsCancel) created from the
109    /// [BookingsCancelBuilder](BookingsCancelBuilder)
110    pub async fn cancel(&self, booking_to_cancel: BookingsCancel)
111                                -> Result<SquareResponse, SquareError> {
112        self.client.request(
113            Verb::POST,
114            SquareAPI::Bookings(format!("/{}/cancel",
115                                        booking_to_cancel.booking_id.unwrap().clone())),
116            Some(&booking_to_cancel.body),
117            None,
118        ).await
119    }
120
121    /// Retrieves a seller's booking profile at the [Square API](https://developer.squareup.com).
122    pub async fn retrieve_business_profile(self)
123                                                   -> Result<SquareResponse, SquareError> {
124        self.client.request(
125            Verb::GET,
126            SquareAPI::Bookings("/business-booking-profile".to_string()),
127            None::<&BookingsPost>,
128            None,
129        ).await
130    }
131
132    /// Lists booking profiles for team members at the [Square API](https://developer.squareup.com).
133    ///
134    /// # Arguments
135    /// * `search_query` - A search query created by the
136    /// [ListTeamMemberBookingsProfileBuilder](ListTeamMemberBookingsProfileBuilder).
137    pub async fn list_team_member_profiles(self, search_query: Option<Vec<(String, String)>>)
138                                                   -> Result<SquareResponse, SquareError> {
139        self.client.request(
140            Verb::GET,
141            SquareAPI::Bookings("/team-member-booking-profiles".to_string()),
142            None::<&BookingsPost>,
143            search_query,
144        ).await
145    }
146
147    /// Lists booking profiles for team members at the [Square API](https://developer.squareup.com).
148    ///
149    /// # Arguments
150    /// * `team_member_id` - The id of the team member you would like to retrieve from the
151    /// [Square API](https://developer.squareup.com).
152    pub async fn retrieve_team_member_profiles(self, team_member_id: String)
153                                                       -> Result<SquareResponse, SquareError> {
154        self.client.request(
155            Verb::GET,
156            SquareAPI::Bookings(format!("/team-member-booking-profiles/{}", team_member_id)),
157            None::<&BookingsPost>,
158            None,
159        ).await
160    }
161}
162
163// -------------------------------------------------------------------------------------------------
164// ListBookingsQueryBuilder implementation
165// -------------------------------------------------------------------------------------------------
166#[derive(Default)]
167pub struct ListBookingsQueryBuilder {
168    limit: Option<i64>,
169    cursor: Option<String>,
170    team_member_id: Option<String>,
171    location_id: Option<String>,
172    start_at_min: Option<String>,
173    start_at_max: Option<String>,
174}
175
176impl ListBookingsQueryBuilder {
177    pub fn new() -> Self {
178        Default::default()
179    }
180
181    /// The maximum number of results per page to return in a paged response.
182    pub fn limit(mut self, limit: i64) -> Self {
183        self.limit = Some(limit);
184
185        self
186    }
187
188    /// The pagination cursor from the preceding response to return the next page of the results.
189    /// Do not set this when retrieving the first page of the results.
190    pub fn cursor<S: Into<String>>(mut self, cursor: S) -> Self {
191        self.cursor = Some(cursor.into());
192
193        self
194    }
195
196    /// The team member for whom to retrieve bookings.
197    /// If this is not set, bookings of all members are retrieved.
198    pub fn team_member_id<S: Into<String>>(mut self, team_member_id: S) -> Self {
199        self.team_member_id = Some(team_member_id.into());
200
201        self
202    }
203
204    /// The location for which to retrieve bookings.
205    /// If this is not set, all locations' bookings are retrieved.
206    pub fn location_id<S: Into<String>>(mut self, location_id: S) -> Self {
207        self.location_id = Some(location_id.into());
208
209        self
210    }
211
212    /// The RFC 3339 timestamp specifying the earliest of the start time.
213    /// If this is not set, the current time is used.
214    //
215    // Examples for January 25th, 2020 6:25:34pm Pacific Standard Time:
216    //
217    // UTC: 2020-01-26T02:25:34Z
218    //
219    // Pacific Standard Time with UTC offset: 2020-01-25T18:25:34-08:00
220    pub fn start_at_min<S: Into<String>>(mut self, start_at_min: S) -> Self {
221        self.start_at_min = Some(start_at_min.into());
222
223        self
224    }
225
226    /// The RFC 3339 timestamp specifying the latest of the start time.
227    /// If this is not set, the time of 31 days after start_at_min is used.
228    //
229    // Examples for January 25th, 2020 6:25:34pm Pacific Standard Time:
230    //
231    // UTC: 2020-01-26T02:25:34Z
232    //
233    // Pacific Standard Time with UTC offset: 2020-01-25T18:25:34-08:00
234    pub fn start_at_max<S: Into<String>>(mut self, start_at_max: S) -> Self {
235        self.start_at_max = Some(start_at_max.into());
236
237        self
238    }
239
240    pub async fn build(self) -> Vec<(String, String)> {
241        let ListBookingsQueryBuilder {
242            limit,
243            cursor,
244            team_member_id,
245            location_id,
246            start_at_min,
247            start_at_max,
248
249        } = self;
250
251        let mut res = vec![];
252
253        if let Some(limit) = limit {
254            res.push(("limit".to_string(), limit.to_string()))
255        }
256
257        if let Some(cursor) = cursor {
258            res.push(("cursor".to_string(), cursor))
259        }
260
261        if let Some(team_member_id) = team_member_id {
262            res.push(("team_member_id".to_string(), team_member_id))
263        }
264
265        if let Some(location_id) = location_id {
266            res.push(("location_id".to_string(), location_id))
267        }
268
269        if let Some(start_at_min) = start_at_min {
270            res.push(("start_at_min".to_string(), start_at_min))
271        }
272
273        if let Some(start_at_max) = start_at_max {
274            res.push(("start_at_max".to_string(), start_at_max))
275        }
276
277        res
278    }
279}
280
281// -------------------------------------------------------------------------------------------------
282// ListTeamMemberBookingsProfileBuilder implementation
283// -------------------------------------------------------------------------------------------------
284#[derive(Default)]
285pub struct ListTeamMemberBookingsProfileBuilder {
286    limit: Option<i32>,
287    cursor: Option<String>,
288    bookable_only: Option<bool>,
289    location_id: Option<String>,
290}
291
292impl ListTeamMemberBookingsProfileBuilder {
293    pub fn new() -> Self {
294        Default::default()
295    }
296
297    /// The maximum number of results to return in a paged response.
298    pub fn limit(mut self, limit: i32) -> Self {
299        self.limit = Some(limit);
300
301        self
302    }
303
304    /// The pagination cursor from the preceding response to return the next page of the results.
305    /// Do not set this when retrieving the first page of the results.
306    pub fn cursor<S: Into<String>>(mut self, cursor: S) -> Self {
307        self.cursor = Some(cursor.into());
308
309        self
310    }
311
312    /// Indicates whether to include only bookable team members in the returned result.
313    pub fn bookable_only(mut self) -> Self {
314        self.bookable_only = Some(true);
315
316        self
317    }
318
319    /// Indicates whether to include only team members enabled at the given location in the
320    /// returned result.
321    pub fn location_id<S: Into<String>>(mut self, location_id: S) -> Self {
322        self.location_id = Some(location_id.into());
323
324        self
325    }
326
327    pub async fn build(self) -> Vec<(String, String)> {
328        let ListTeamMemberBookingsProfileBuilder {
329            limit,
330            cursor,
331            bookable_only,
332            location_id,
333        } = self;
334
335        let mut res = vec![];
336
337        if let Some(limit) = limit {
338            res.push(("limit".to_string(), limit.to_string()))
339        }
340        if let Some(cursor) = cursor {
341            res.push(("cursor".to_string(), cursor))
342        }
343        if let Some(bookable_only) = bookable_only {
344            res.push(("bookable_only".to_string(), bookable_only.to_string()))
345        }
346        if let Some(location_id) = location_id {
347            res.push(("location_id".to_string(), location_id))
348        }
349
350        res
351    }
352}
353
354// -------------------------------------------------------------------------------------------------
355// BookingsPost builders implementation
356// -------------------------------------------------------------------------------------------------
357
358/// [BookingsPost](BookingsPost)
359///
360/// To build a valid BookingPost and to avoid returning one must previously pass all of these:
361/// * `.customer_id()`
362/// * `.location_id()`
363/// * `.add_appointment_segment()`
364/// * `.start_at()`
365///
366/// # Example: Build a [BookingPost](BookingPost)
367/// ```
368/// use square_ox::{
369///     objects::AppointmentSegment,
370///     builder::Builder,
371///     api::bookings::BookingsPost,
372///     builder::Buildable,
373/// };
374///
375/// async {
376///     let builder = Builder::from(BookingsPost::default())
377///     .customer_id("some_id".to_string())
378///     .location_id("some_id".to_string())
379///     .start_at("some_start_at_date_time".to_string())
380///     .add_appointment_segment(AppointmentSegment::default())
381///     .build()
382///     .await;
383/// };
384/// ```
385#[derive(Serialize, Debug, Deserialize, Default, Builder)]
386pub struct BookingsPost {
387    #[builder_rand("uuid")]
388    idempotency_key: Option<String>,
389    booking: Booking,
390}
391
392impl AddField<Booking> for BookingsPost {
393    fn add_field(&mut self, field: Booking) {
394        self.booking = field;
395    }
396}
397
398// -------------------------------------------------------------------------------------------------
399// BookingsPost builders implementation
400// -------------------------------------------------------------------------------------------------
401#[derive(Serialize, Debug, Deserialize, Default)]
402pub struct BookingsCancel {
403    #[serde(skip_serializing_if = "Option::is_none")]
404    booking_id: Option<String>,
405    #[serde(skip_serializing_if = "Option::is_none")]
406    body: Option<BookingsCancelBody>,
407}
408
409impl Validate for BookingsCancel {
410    fn validate(mut self) -> Result<Self, ValidationError> where Self: Sized {
411        if self.booking_id.is_some() {
412            if let Some(body) = self.body.as_mut() {
413                body.idempotency_key = Some(Uuid::new_v4().to_string())
414            };
415
416            Ok(self)
417        } else {
418            Err(ValidationError)
419        }
420    }
421}
422
423impl<T: ParentBuilder> Builder<BookingsCancel, T>  {
424    pub fn booking_id<S: Into<String>>(mut self, booking_id: S) -> Self {
425        self.body.booking_id = Some(booking_id.into());
426
427        self
428    }
429
430    pub fn booking_version(mut self, booking_version: i32) -> Self {
431        if let Some(body) = self.body.body.as_mut() {
432            body.booking_version = Some(booking_version)
433        } else {
434            self.body.body = Some(BookingsCancelBody {
435                idempotency_key: None,
436                booking_version: Some(booking_version)
437            })
438        }
439
440        self
441    }
442}
443
444#[derive(Serialize, Debug, Deserialize)]
445pub struct BookingsCancelBody {
446    #[serde(skip_serializing_if = "Option::is_none")]
447    idempotency_key: Option<String>,
448    #[serde(skip_serializing_if = "Option::is_none")]
449    booking_version: Option<i32>,
450}
451
452// -------------------------------------------------------------------------------------------------
453// SearchAvailabilityQuery builders implementation
454// -------------------------------------------------------------------------------------------------
455// holds a QueryBody struct which contains the actual query data, as this is the way it is expected
456// by the Square API
457#[derive(Serialize, Debug, Deserialize, Default)]
458pub struct SearchAvailabilityQuery {
459    query: QueryBody,
460}
461
462impl Validate for SearchAvailabilityQuery {
463    fn validate(self) -> Result<Self, ValidationError> where Self: Sized {
464        if self.query.filter.start_at_range.is_some() {
465            Ok(self)
466        } else {
467            Err(ValidationError)
468        }
469    }
470}
471
472impl<T: ParentBuilder> Builder<SearchAvailabilityQuery, T> {
473    pub fn start_at_range<S: Into<String>>(mut self, start: S, end: S) -> Self {
474        self.body.query.filter.start_at_range = Some(StartAtRange {
475            end_at: end.into(),
476            start_at: start.into(),
477        });
478
479        self
480    }
481
482    pub fn location_id<S: Into<String>>(mut self, location_id: S) -> Self {
483        self.body.query.filter.location_id = Some(location_id.into());
484
485        self
486    }
487
488    pub fn segment_filters<S: Into<String>>(mut self, service_variation_id: S) -> Self {
489        let new_filter = SegmentFilter {
490            service_variation_id: service_variation_id.into(),
491            team_member_id_filter: None
492        };
493
494        match self.body.query.filter.segment_filters.as_mut() {
495            Some(filters) => {
496                filters.push(new_filter);
497            },
498            None => {
499                let filters = vec![new_filter];
500                self.body.query.filter.segment_filters = Some(filters)
501            }
502        };
503
504        self
505    }
506}
507
508#[derive(Serialize, Debug, Deserialize, Default)]
509pub struct QueryBody {
510    filter: AvailabilityQueryFilter,
511}
512
513#[cfg(test)]
514mod test_bookings {
515    use super::*;
516
517    #[tokio::test]
518    async fn test_search_query_builder() {
519        let expected = SearchAvailabilityQuery {
520            query: QueryBody {
521                filter: AvailabilityQueryFilter {
522                    start_at_range: Some(StartAtRange {
523                        end_at: "2023-10-12T07:20:50.52Z".to_string(),
524                        start_at: "2022-10-12T07:20:50.52Z".to_string(),
525                    }),
526                    booking_id: None,
527                    location_id: Some("LPNXWH14W6S47".to_string()),
528                    segment_filters: None
529                }
530            }
531        };
532
533        let actual = Builder::from(SearchAvailabilityQuery::default())
534            .start_at_range(
535                "2022-10-12T07:20:50.52Z",
536                "2023-10-12T07:20:50.52Z")
537            .location_id("LPNXWH14W6S47")
538            .build()
539            .unwrap();
540
541        assert_eq!(format!("{:?}", expected), format!("{:?}", actual))
542    }
543
544    #[tokio::test]
545    async fn test_search_availability() {
546        use dotenv::dotenv;
547        use std::env;
548
549        dotenv().ok();
550        let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
551        let sut = SquareClient::new(&access_token);
552
553        let input = Builder::from(SearchAvailabilityQuery::default())
554            .start_at_range(
555                "2022-09-12T07:20:50.52Z",
556                "2022-10-12T07:20:50.52Z")
557            .location_id("L1JC53TYHS40Z")
558            .segment_filters("BJHURKYAIAQIDMY267GZNYNW")
559            .build().unwrap();
560
561        let result = sut.bookings().search_availability(input).await;
562
563        assert!(result.is_ok())
564    }
565
566    #[tokio::test]
567    async fn test_booking_post_builder() {
568        let actual = Builder::from(BookingsPost::default())
569            .sub_builder_from(Booking::default())
570            .start_at("2022-10-11T16:30:00Z")
571            .location_id("L1JC53TYHS40Z")
572            .customer_id("7PB8P9553RYA3F672D15369VK4")
573            .sub_builder_from(AppointmentSegment::default())
574            .duration_minutes(60.00)
575            .team_member_id("TMKFnToW8ByXrcm6")
576            .service_variation_id("BSOL4BB6RCMX6SH4KQIFWZDP")
577            .service_variation_version(1655427266071)
578            .build()
579            .unwrap()
580            .build()
581            .unwrap()
582            .build();
583
584        let expected = Booking {
585            id: None,
586            all_day: None,
587            appointment_segments: Some(vec![AppointmentSegment {
588                duration_minutes: 60.00,
589                team_member_id: "TMKFnToW8ByXrcm6".to_string(),
590                any_team_member_id: None,
591                intermission_minutes: None,
592                resource_ids: None,
593                service_variation_id: "BSOL4BB6RCMX6SH4KQIFWZDP".to_string(),
594                service_variation_version:  1655427266071,
595            }]),
596            created_at: None,
597            booking_creator_details: None,
598            customer_id: Some("7PB8P9553RYA3F672D15369VK4".to_string()),
599            customer_note: None,
600            location_id: Some("L1JC53TYHS40Z".to_string()),
601            location_type: None,
602            seller_note: None,
603            source: None,
604            start_at: Some("2022-10-11T16:30:00Z".to_string()),
605            status: None,
606            transition_time_minutes: None,
607            updated_at: None,
608            version: None
609        };
610
611        assert!(actual.is_ok());
612        assert_eq!(format!("{:?}", expected), format!("{:?}", actual.unwrap().booking))
613    }
614
615    #[tokio::test]
616    async fn test_booking_post_builder_fail() {
617        let res = Builder::from(BookingsPost::default())
618            .sub_builder_from(Booking::default())
619            .start_at("2022-10-11T16:30:00Z")
620            .customer_id("7PB8P9553RYA3F672D15369VK4")
621            .sub_builder_from(AppointmentSegment::default())
622            .duration_minutes(60.00)
623            .team_member_id("TMKFnToW8ByXrcm6")
624            .service_variation_id("BSOL4BB6RCMX6SH4KQIFWZDP")
625            .service_variation_version(1655427266071)
626            .build()
627            .unwrap()
628            .build();
629
630        assert!(res.is_err());
631    }
632
633    // #[tokio::test]
634    async fn test_create_booking() {
635        use dotenv::dotenv;
636        use std::env;
637
638        dotenv().ok();
639        let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
640        let sut = SquareClient::new(&access_token);
641
642        let input = BookingsPost {
643            idempotency_key: Some(Uuid::new_v4().to_string()),
644            booking: Booking {
645                id: None,
646                all_day: None,
647                appointment_segments: Some(vec![AppointmentSegment {
648                    duration_minutes: 60.00,
649                    team_member_id: "TMKFnToW8ByXrcm6".to_string(),
650                    any_team_member_id: None,
651                    intermission_minutes: None,
652                    resource_ids: None,
653                    service_variation_id: "BJHURKYAIAQIDMY267GZNYNW".to_string(),
654                    service_variation_version:  1655427266071,
655                }]),
656                created_at: None,
657                booking_creator_details: None,
658                customer_id: Some("7PB8P9553RYA3F672D15369VK4".to_string()),
659                customer_note: None,
660                location_id: Some("L1JC53TYHS40Z".to_string()),
661                location_type: None,
662                seller_note: None,
663                source: None,
664                start_at: Some("2022-10-11T16:30:00Z".to_string()),
665                status: None,
666                transition_time_minutes: None,
667                updated_at: None,
668                version: None
669            }
670        };
671
672        let res = sut.bookings().create(input).await;
673
674        assert!(res.is_ok())
675    }
676
677    #[tokio::test]
678    async fn test_retrieve_booking() {
679        use dotenv::dotenv;
680        use std::env;
681
682        dotenv().ok();
683        let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
684        let sut = SquareClient::new(&access_token);
685
686        let res = sut.bookings()
687            .retrieve("burxkwa4ot1ydg".to_string())
688            .await;
689
690        assert!(res.is_ok())
691    }
692
693    #[tokio::test]
694    async fn test_bookings_cancel_builder() {
695        let expected = BookingsCancel {
696            booking_id: Some("9uv6i3p5x5ao1p".to_string()),
697            body: Some(BookingsCancelBody {
698                idempotency_key: Some(Uuid::new_v4().to_string()),
699                booking_version: None
700            })
701        };
702        let actual = Builder::from(BookingsCancel::default())
703            .booking_id("9uv6i3p5x5ao1p").build();
704
705        assert!(actual.is_ok());
706        assert_eq!(format!("{:?}", expected.booking_id),
707                   format!("{:?}", actual.unwrap().booking_id));
708    }
709
710    #[tokio::test]
711    async fn test_bookings_cancel_builder_fail() {
712
713        let res = Builder::from(BookingsCancel::default()).build();
714
715        assert!(res.is_err());
716    }
717
718    #[tokio::test]
719    async fn test_cancel_booking() {
720        use dotenv::dotenv;
721        use std::env;
722
723        dotenv().ok();
724        let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
725        let sut = SquareClient::new(&access_token);
726
727        let input = BookingsCancel {
728            booking_id: Some("pi7kr2va3y4h4f".to_string()),
729            body: Some(BookingsCancelBody {
730                idempotency_key: Some(Uuid::new_v4().to_string()),
731                booking_version: None
732            })
733        };
734
735        let res = sut.bookings().cancel(input).await;
736
737        assert!(res.is_ok())
738    }
739
740    #[tokio::test]
741    async fn test_update_booking() {
742        use dotenv::dotenv;
743        use std::env;
744
745        dotenv().ok();
746        let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
747        let sut = SquareClient::new(&access_token);
748
749        let input = BookingsPost {
750            idempotency_key: Some(Uuid::new_v4().to_string()),
751            booking: Booking {
752                id: None,
753                all_day: None,
754                appointment_segments: Some(vec![AppointmentSegment {
755                    duration_minutes: 60.00,
756                    team_member_id: "TMKFnToW8ByXrcm6".to_string(),
757                    any_team_member_id: None,
758                    intermission_minutes: None,
759                    resource_ids: None,
760                    service_variation_id: "BSOL4BB6RCMX6SH4KQIFWZDP".to_string(),
761                    service_variation_version:  1655427266071,
762                }]),
763                created_at: None,
764                booking_creator_details: None,
765                customer_id: Some("7PB8P9553RYA3F672D15369VK4".to_string()),
766                customer_note: None,
767                location_id: Some("L1JC53TYHS40Z".to_string()),
768                location_type: None,
769                seller_note: Some("be nice!".to_string()),
770                source: None,
771                start_at: Some("2022-10-11T16:30:00Z".to_string()),
772                status: None,
773                transition_time_minutes: None,
774                updated_at: None,
775                version: None
776            }
777        };
778
779        let res = sut.bookings()
780            .update(input, "oruft3c9lh0duq".to_string())
781            .await;
782
783        assert!(res.is_ok())
784    }
785
786    #[tokio::test]
787    async fn test_list_bookings_query_builder() {
788        let expected = vec![
789            ("location_id".to_string(), "L1JC53TYHS40Z".to_string()),
790            ("start_at_min".to_string(), "2022-09-12T07:20:50.52Z".to_string()),
791        ];
792
793        let actual = ListBookingsQueryBuilder::new()
794            .location_id("L1JC53TYHS40Z")
795            .start_at_min("2022-09-12T07:20:50.52Z")
796            .build()
797            .await;
798
799        assert_eq!(expected, actual)
800
801
802    }
803
804    #[tokio::test]
805    async fn test_list_bookings() {
806        use dotenv::dotenv;
807        use std::env;
808
809        dotenv().ok();
810        let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
811        let sut = SquareClient::new(&access_token);
812
813        let input = vec![
814            ("start_at_min".to_string(), "2022-09-12T07:20:50.52Z".to_string())
815        ];
816
817        let res = sut.bookings().list(Some(input)).await;
818
819        assert!(res.is_ok())
820    }
821
822    #[tokio::test]
823    async fn test_retrieve_business_booking_profile() {
824        use dotenv::dotenv;
825        use std::env;
826
827        dotenv().ok();
828        let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
829        let sut = SquareClient::new(&access_token);
830
831        let res = sut.bookings().retrieve_business_profile().await;
832
833        assert!(res.is_ok())
834    }
835
836    #[tokio::test]
837    async fn test_list_team_member_booking_profile_query_builder() {
838        let expected = vec![
839            ("limit".to_string(), "10".to_string()),
840            ("bookable_only".to_string(), "true".to_string()),
841            ("location_id".to_string(), "L1JC53TYHS40Z".to_string()),
842        ];
843
844        let actual = ListTeamMemberBookingsProfileBuilder::new()
845            .bookable_only()
846            .limit(10)
847            .location_id("L1JC53TYHS40Z")
848            .build()
849            .await;
850
851        assert_eq!(expected, actual)
852
853
854    }
855
856    #[tokio::test]
857    async fn test_list_team_member_booking_profiles() {
858        use dotenv::dotenv;
859        use std::env;
860
861        dotenv().ok();
862        let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
863        let sut = SquareClient::new(&access_token);
864
865        let input = vec![
866            ("limit".to_string(), "10".to_string()),
867            ("bookable_only".to_string(), "true".to_string()),
868            ("location_id".to_string(), "L1JC53TYHS40Z".to_string()),
869        ];
870
871        let res = sut.bookings()
872            .list_team_member_profiles(Some(input))
873            .await;
874
875        assert!(res.is_ok())
876    }
877
878    #[tokio::test]
879    async fn test_retrieve_team_member_booking_profile() {
880        use dotenv::dotenv;
881        use std::env;
882
883        dotenv().ok();
884        let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
885        let sut = SquareClient::new(&access_token);
886
887        let res = sut.bookings()
888            .retrieve_team_member_profiles("TMKFnToW8ByXrcm6".to_string())
889            .await;
890
891        assert!(res.is_ok())
892    }
893}
894