square_ox/api/
inventory.rs

1/*!
2Inventory functionality of the [Square API](https://developer.squareup.com).
3 */
4
5use crate::client::SquareClient;
6use crate::api::{Verb, SquareAPI};
7use crate::errors::{InventoryChangeBodyBuildError, SquareError, ValidationError};
8use crate::response::SquareResponse;
9use crate::objects::{CatalogObject, InventoryChange, InventoryPhysicalCount,
10                     InventoryTransfer};
11use crate::objects::enums::{InventoryChangeType, InventoryState};
12
13use serde::{Deserialize, Serialize};
14use uuid::Uuid;
15use square_ox_derive::Builder;
16use crate::builder::{AddField, Builder, ParentBuilder, Validate, Buildable};
17
18
19impl SquareClient {
20    /// Returns an [Inventory](Inventory) object through which you can make calls specifically to
21    /// the Inventory endpoint of the [Square API](https://developer.squareup.com).
22    /// # Example: Using the inventory endpoint to make a retrieve_count request.
23    /// ```rust
24    /// use square_ox::{
25    ///         response::{SquareResponse, ResponseError},
26    ///         client::SquareClient,
27    ///         api::inventory::Inventory,
28    ///     };
29    ///
30    /// async {
31    ///     let count = SquareClient::new("some_token")
32    ///         .inventory()
33    ///         .retrieve_count(
34    ///             "some_obj_id".to_string(),
35    ///             Some("some_loc_id".to_string())
36    ///         )
37    ///         .await;
38    ///     };
39    /// ```
40    pub fn inventory(&self) -> Inventory {
41        Inventory {
42            client: &self,
43        }
44    }
45}
46
47/// Allows you to make calls to the [Square API](https://developer.squareup.com) at the Inventory
48/// endpoint with all currently implemented methods.
49pub struct Inventory<'a> {
50    client: &'a SquareClient
51}
52
53impl<'a> Inventory<'a> {
54
55    /// Applies adjustments and counts to the provided item quantities.
56    /// [Open in API Reference](https://developer.squareup.com/reference/square/inventory/batch-change-inventory)
57    pub async fn batch_change(self, body: InventoryChangeBody)
58                                -> Result<SquareResponse, SquareError>{
59        self.client.request(
60            Verb::POST,
61            SquareAPI::Inventory("/changes/batch-create".to_string()),
62            Some(&body),
63            None,
64        ).await
65    }
66
67    /// Retrieves the current calculated stock count for a given [CatalogObject](crate::objects::CatalogObject) at
68    /// a given set of [Location](crate::objects::Location)s.
69    /// [Open in API Reference](https://developer.squareup.com/reference/square/inventory/retrieve-inventory-count)
70    pub async fn retrieve_count(self, object_id: String, location_id: Option<String>)
71        -> Result<SquareResponse, SquareError>{
72        let parameters = match location_id {
73            Some(location_id) => Some(vec![("location_id".to_string(), location_id)]),
74            None => None
75        };
76
77        self.client.request(
78            Verb::GET,
79            SquareAPI::Inventory(format!("/{}", object_id)),
80            None::<&CatalogObject>,
81            parameters,
82        ).await
83    }
84
85    /// Returns the [InventoryAdjustment](InventoryAdjustment) object with the provided adjustment_id.
86    /// [Open in API Reference](https://developer.squareup.com/reference/square/inventory/retrieve-inventory-adjustment)
87    pub async fn retrieve_adjustment(self, adjustment_id: String)
88                                -> Result<SquareResponse, SquareError>{
89        self.client.request(
90            Verb::GET,
91            SquareAPI::Inventory(format!("/adjustments/{}", adjustment_id)),
92            None::<&CatalogObject>,
93            None,
94        ).await
95    }
96
97    /// Returns the [InventoryTransfer](InventoryTransfer) object with the provided `transfer_id`.
98    /// [Open in API Reference](https://developer.squareup.com/reference/square/inventory/retrieve-inventory-transfer)
99    pub async fn retrieve_transfer(self, transfer_id: String)
100                                -> Result<SquareResponse, SquareError>{
101        self.client.request(
102            Verb::GET,
103            SquareAPI::Inventory(format!("/transfer/{}", transfer_id)),
104            None::<&CatalogObject>,
105            None,
106        ).await
107    }
108
109    /// Returns the [InventoryPhysicalCount](InventoryPhysicalCount) object with the provided `physical_count_id`.
110    /// [Open in API Reference](https://developer.squareup.com/reference/square/inventory/retrieve-inventory-physical-count)
111    pub async fn retrieve_physical_count(self, physical_count_id: String)
112                                -> Result<SquareResponse, SquareError>{
113        self.client.request(
114            Verb::GET,
115            SquareAPI::Inventory(format!("/physical-counts/{}", physical_count_id)),
116            None::<&CatalogObject>,
117            None,
118        ).await
119    }
120
121    /// Returns current counts for the provided [CatalogObject](CatalogObject)s at the requested
122    /// [Location](Location)s.
123    /// [Open in API Reference](https://developer.squareup.com/reference/square/inventory/retrieve-inventory-physical-count)
124    pub async fn batch_retrieve_counts(self, body: BatchRetrieveCounts)
125                                -> Result<SquareResponse, SquareError>{
126        self.client.request(
127            Verb::POST,
128            SquareAPI::Inventory("/counts/batch-retrieve".to_string()),
129            Some(&body),
130            None,
131        ).await
132    }
133}
134
135// -------------------------------------------------------------------------------------------------
136// InventoryChangeBody builder implementation
137// -------------------------------------------------------------------------------------------------
138#[derive(Clone, Debug, Serialize, Deserialize, Default, Builder)]
139pub struct InventoryChangeBody {
140    #[builder_rand("uuid")]
141    idempotency_key: Option<String>,
142    #[builder_validate("len")]
143    changes: Vec<InventoryChange>,
144    ignore_unchanged_counts: Option<bool>,
145}
146
147impl AddField<InventoryChange> for InventoryChangeBody {
148    fn add_field(&mut self, field: InventoryChange) {
149        self.changes.push(field);
150    }
151}
152
153
154// -------------------------------------------------------------------------------------------------
155// BatchRetrieveCounts builder implementation
156// -------------------------------------------------------------------------------------------------
157#[derive(Clone, Debug, Serialize, Deserialize, Default)]
158pub struct BatchRetrieveCounts {
159    catalog_object_ids: Vec<String>,
160    cursor: Option<String>,
161    limit: Option<i32>,
162    location_ids: Vec<String>,
163    states: Option<Vec<InventoryState>>,
164    updated_after: Option<String>,
165}
166
167impl Validate for BatchRetrieveCounts {
168    fn validate(self) -> Result<Self, ValidationError> where Self: Sized {
169        if self.location_ids.len() > 0 && self.catalog_object_ids.len() > 0 {
170            Ok(self)
171        } else {
172            Err(ValidationError)
173        }
174    }
175}
176
177impl<T: ParentBuilder> Builder<BatchRetrieveCounts, T> {
178    pub fn object_ids(mut self, ids: Vec<String>) -> Self {
179        self.body.catalog_object_ids = ids;
180
181        self
182    }
183
184    pub fn location_ids(mut self, ids: Vec<String>) -> Self {
185        self.body.location_ids = ids;
186
187        self
188    }
189
190    pub fn add_location_id(mut self, id: String) -> Self {
191        self.body.location_ids.push(id);
192
193        self
194    }
195}
196
197#[cfg(test)]
198mod test_inventory {
199    use crate::builder::BackIntoBuilder;
200    use crate::objects::enums::InventoryState;
201    use super::*;
202
203    #[tokio::test]
204    async fn test_retrieve_count() {
205        use dotenv::dotenv;
206        use std::env;
207
208        dotenv().ok();
209        let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
210        let sut = SquareClient::new(&access_token);
211
212        let input = (
213            "DUHTRFG3OEYAXG5I22XLFN23".to_string(),
214            "L1JC53TYHS40Z".to_string()
215        );
216
217        let res = sut.inventory()
218            .retrieve_count(input.0, Some(input.1))
219            .await;
220
221        assert!(res.is_ok())
222    }
223
224    #[tokio::test]
225    async fn test_change_body_builder() {
226        let expected = InventoryChangeBody {
227            idempotency_key: None,
228            changes: vec![
229                InventoryChange {
230                    adjustment: None,
231                    measurement_unit: None,
232                    measurement_unit_id: None,
233                    physical_count: Some(InventoryPhysicalCount {
234                        id: None,
235                        catalog_object_id: "".to_string(),
236                        catalog_object_type: None,
237                        created_at: None,
238                        location_id: "L1JC53TYHS40Z".to_string(),
239                        occurred_at: "2022-07-09T12:25:34Z".to_string(),
240                        quantity: "30".to_string(),
241                        reference_id: None,
242                        source: None,
243                        state: InventoryState::InStock,
244                        team_member_id: None
245                    }),
246                    transfer: None,
247                    inventory_change_type: InventoryChangeType::PhysicalCount
248                }
249            ],
250            ignore_unchanged_counts: None
251        };
252
253        let mut actual = Builder::from(InventoryChangeBody::default())
254            .sub_builder_from(InventoryChange::default())
255            .change_type(InventoryChangeType::PhysicalCount)
256            .sub_builder_from(InventoryPhysicalCount::default())
257            .catalog_object_id("".to_string())
258            .location_id("L1JC53TYHS40Z".to_string())
259            .occurred_at("2022-07-09T12:25:34Z".to_string())
260            .quantity("30".to_string())
261            .state(InventoryState::InStock)
262            .build()
263            .unwrap()
264            .build()
265            .unwrap()
266            .build()
267            .unwrap();
268
269        assert!(actual.idempotency_key.is_some());
270
271        actual.idempotency_key = None;
272
273        assert_eq!(format!("{:?}",expected), format!("{:?}",actual));
274    }
275
276    // #[tokio::test]
277    async fn test_batch_change() {
278        use dotenv::dotenv;
279        use std::env;
280
281        dotenv().ok();
282        let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
283        let sut = SquareClient::new(&access_token);
284
285        let input = InventoryChangeBody {
286            idempotency_key: Some(Uuid::new_v4().to_string()),
287            changes: vec![
288                InventoryChange {
289                    adjustment: None,
290                    measurement_unit: None,
291                    measurement_unit_id: None,
292                    physical_count: Some(InventoryPhysicalCount {
293                        id: None,
294                        catalog_object_id: "DUHTRFG3OEYAXG5I22XLFN23".to_string(),
295                        catalog_object_type: None,
296                        created_at: None,
297                        location_id: "L1JC53TYHS40Z".to_string(),
298                        occurred_at: "2022-07-09T12:25:34Z".to_string(),
299                        quantity: "30".to_string(),
300                        reference_id: None,
301                        source: None,
302                        state: InventoryState::InStock,
303                        team_member_id: None
304                    }),
305                    transfer: None,
306                    inventory_change_type: InventoryChangeType::PhysicalCount
307                }
308            ],
309            ignore_unchanged_counts: None
310        };
311
312        let res = sut.inventory()
313            .batch_change(input)
314            .await;
315
316        assert!(res.is_ok())
317    }
318}