quickbooks_types/
lib.rs

1//! # `QuickBooks` Types Library
2//!
3//! This library provides Rust types and traits for interacting with the `QuickBooks` Online API.
4//! It includes data models for various `QuickBooks` entities, as well as traits that define
5//! common behaviors such as creation, reading, updating, and deletion of these entities.
6
7#[cfg(feature = "builder")]
8#[macro_use]
9extern crate derive_builder;
10
11mod error;
12mod models;
13pub mod reports;
14use std::fmt::{Debug, Display};
15
16pub use error::*;
17use models::common::{MetaData, NtRef};
18pub use models::*;
19use serde::{de::DeserializeOwned, Serialize};
20
21/// Core trait for all `QuickBooks` entities.
22///
23/// This trait defines the fundamental interface that all `QuickBooks` entities must implement.
24/// It provides access to common fields like ID, sync token, and metadata that are present
25/// on all `QuickBooks` objects, as well as type information for API operations.
26///
27/// # Required Methods
28///
29/// - `id()`: Returns the entity's unique identifier
30/// - `clone_id()`: Returns a cloned copy of the ID
31/// - `sync_token()`: Returns the synchronization token for updates
32/// - `meta_data()`: Returns metadata about the entity
33/// - `name()`: Returns the entity type name for API calls
34/// - `qb_id()`: Returns the lowercase entity identifier for URLs
35///
36/// # Default Methods
37///
38/// - `has_read()`: Returns true if the entity has both ID and sync token (indicates it was read from QB)
39///
40/// # Examples
41///
42/// ```no_run
43/// use quickbooks_types::{QBItem, Customer};
44///
45/// let customer = Customer::default();
46///
47/// // Check if entity has been read from QuickBooks
48/// if customer.has_read() {
49///     println!("Customer ID: {:?}", customer.id());
50///     println!("Sync Token: {:?}", customer.sync_token());
51/// }
52///
53/// // Get type information
54/// println!("Entity name: {}", Customer::name()); // "Customer"
55/// println!("API identifier: {}", Customer::qb_id()); // "customer"
56/// ```
57pub trait QBItem: Serialize + Default + Clone + Sized + DeserializeOwned + Debug + Send {
58    fn id(&self) -> Option<&String>;
59    fn clone_id(&self) -> Option<String>;
60    fn sync_token(&self) -> Option<&String>;
61    fn meta_data(&self) -> Option<&MetaData>;
62    fn name() -> &'static str;
63    fn qb_id() -> &'static str;
64    fn has_read(&self) -> bool {
65        self.id().is_some() && self.sync_token().is_some()
66    }
67}
68
69macro_rules! impl_qb_data {
70    ($($x:ident),+) => {
71        $(
72            #[cfg(feature="builder")]
73            paste::paste! {
74                #[allow(clippy::new_ret_no_self)]
75                impl [<$x>] {
76                    #[must_use] pub fn new() -> [<$x Builder>] {
77                        [<$x Builder>]::default()
78                    }
79                }
80            }
81
82            impl QBItem for $x {
83                fn id(&self) -> Option<&String> {
84                    self.id.as_ref()
85                }
86
87                fn clone_id(&self) -> Option<String> {
88                    self.id.clone()
89                }
90
91                fn sync_token(&self) -> Option<&String> {
92                    self.sync_token.as_ref()
93                }
94
95                fn meta_data(&self) -> Option<&MetaData> {
96                    self.meta_data.as_ref()
97                }
98
99                #[inline]
100                fn name() -> &'static str {
101                    stringify!($x)
102                }
103
104                #[inline]
105                fn qb_id() -> &'static str {
106                    paste::paste! {
107                        stringify!([<$x:lower>])
108                    }
109                }
110            }
111
112            impl Display for $x {
113                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114                    write!(f, "{} : {}", Self::name(), serde_json::to_string_pretty(self).expect("Could not serialize object for display!"))
115                }
116            }
117        )+
118   }
119}
120
121impl_qb_data!(
122    Invoice,
123    Vendor,
124    Payment,
125    Item,
126    Estimate,
127    Employee,
128    Customer,
129    CompanyInfo,
130    Bill,
131    Attachable,
132    Account,
133    Preferences,
134    SalesReceipt,
135    BillPayment
136);
137
138/// Trait for entities that can be created in `QuickBooks`.
139///
140/// This trait defines the validation logic for determining whether an entity
141/// has the required fields to be successfully created via the `QuickBooks` API.
142/// Each entity implements its own validation rules based on `QuickBooks` requirements.
143///
144/// # Required Methods
145///
146/// - `can_create()`: Returns true if the entity has all required fields for creation
147///
148/// # Examples
149///
150/// ```no_run
151/// use quickbooks_types::{Customer, QBCreatable};
152///
153/// let mut customer = Customer::default();
154///
155/// // Check if customer can be created (will be false - missing required fields)
156/// assert!(!customer.can_create());
157///
158/// // Add required field
159/// customer.display_name = Some("John Doe".to_string());
160///
161/// // Now it can be created
162/// assert!(customer.can_create());
163/// ```
164///
165/// # Implementation Notes
166///
167/// Different entities have different creation requirements:
168/// - **Customer/Vendor**: Requires `display_name` or individual name components
169/// - **Account**: Requires name and `account_type` or `account_sub_type`
170/// - **Invoice**: Requires customer reference and line items
171/// - **Item**: Requires name and type
172pub trait QBCreatable {
173    fn can_create(&self) -> bool;
174}
175
176/// Trait for entities that can be read from `QuickBooks` by ID.
177///
178/// This trait is automatically implemented for all [`QBItem`] types and provides
179/// the ability to read entities from `QuickBooks` using their unique identifier.
180///
181/// # Default Implementation
182///
183/// The default implementation checks if the entity has an ID field, which is
184/// required for read operations.
185///
186/// # Examples
187///
188/// ```no_run
189/// use quickbooks_types::{Customer, QBReadable};
190///
191/// let mut customer = Customer::default();
192/// customer.id = Some("123".to_string());
193///
194/// // Can read because it has an ID
195/// assert!(customer.can_read());
196/// ```
197pub trait QBReadable: QBItem {
198    fn can_read(&self) -> bool;
199}
200
201impl<T: QBItem> QBReadable for T {
202    fn can_read(&self) -> bool {
203        self.id().is_some()
204    }
205}
206
207/// Trait for entities that can be queried from `QuickBooks`.
208///
209/// This trait is automatically implemented for all [`QBItem`] types and indicates
210/// that the entity supports `QuickBooks` SQL-like query operations.
211///
212/// # Examples
213///
214/// ```no_run
215/// use quickbooks_types::{Customer, QBQueryable};
216///
217/// // All QBItem types automatically implement QBQueryable
218/// let customer = Customer::default();
219/// ```
220pub trait QBQueryable: QBItem {}
221impl<T: QBItem> QBQueryable for T {}
222
223/// Trait for entities that can be deleted from `QuickBooks`.
224///
225/// This trait is automatically implemented for all [`QBItem`] types and provides
226/// validation for delete operations. Entities must have been read from `QuickBooks`
227/// (have both ID and sync token) to be deletable.
228///
229/// # Default Implementation
230///
231/// The default implementation uses [`QBItem::has_read()`] to verify the entity
232/// has both an ID and sync token, which are required for delete operations.
233///
234/// # Examples
235///
236/// ```no_run
237/// use quickbooks_types::{Invoice, QBDeletable};
238///
239/// let mut invoice = Invoice::default();
240/// invoice.id = Some("123".to_string());
241/// invoice.sync_token = Some("2".to_string());
242///
243/// // Can delete because it has both ID and sync token
244/// assert!(invoice.can_delete());
245/// ```
246pub trait QBDeletable: QBItem {
247    fn can_delete(&self) -> bool {
248        self.has_read()
249    }
250}
251
252/// Trait for entities that can be voided in `QuickBooks`.
253///
254/// Voiding is a special operation in `QuickBooks` that marks transactions as void
255/// while preserving them for audit purposes. Only certain entities support voiding.
256///
257/// # Default Implementation
258///
259/// The default implementation requires that the entity has been read from `QuickBooks`
260/// (has both ID and sync token).
261///
262/// # Supported Entities
263///
264/// Typically includes: Invoice, Payment, Bill, Check, `SalesReceipt`, and other transactional entities.
265///
266/// # Examples
267///
268/// ```no_run
269/// use quickbooks_types::{Invoice, QBVoidable};
270///
271/// let mut invoice = Invoice::default();
272/// invoice.id = Some("123".to_string());
273/// invoice.sync_token = Some("2".to_string());
274///
275/// // Can void because it has been read from QuickBooks
276/// assert!(invoice.can_void());
277/// ```
278pub trait QBVoidable: QBItem {
279    fn can_void(&self) -> bool {
280        self.has_read()
281    }
282}
283
284/// Trait for entities that support full update operations.
285///
286/// Full updates require sending the complete entity data to `QuickBooks`,
287/// replacing all fields with the provided values. This is in contrast to
288/// sparse updates which only update specified fields.
289///
290/// # Required Methods
291///
292/// - `can_full_update()`: Returns true if the entity can be fully updated
293///
294/// # Implementation Notes
295///
296/// Typically requires:
297/// - Entity has been read from `QuickBooks` (has ID and sync token)
298/// - Entity meets creation requirements (has required fields)
299/// - Some entities may have additional validation rules
300///
301/// # Examples
302///
303/// ```no_run
304/// use quickbooks_types::{Customer, QBFullUpdatable};
305///
306/// let mut customer = Customer::default();
307/// customer.id = Some("123".to_string());
308/// customer.sync_token = Some("2".to_string());
309/// customer.display_name = Some("John Doe".to_string());
310///
311/// // Check if can be fully updated
312/// if customer.can_full_update() {
313///     // Proceed with full update
314/// }
315/// ```
316pub trait QBFullUpdatable {
317    fn can_full_update(&self) -> bool;
318}
319
320/// Trait for entities that support sparse update operations.
321///
322/// Sparse updates allow updating only specific fields of an entity without
323/// affecting other fields. This is more efficient and safer than full updates
324/// when you only need to change specific values.
325///
326/// # Required Methods
327///
328/// - `can_sparse_update()`: Returns true if the entity can be sparse updated
329///
330/// # Implementation Notes
331///
332/// Typically requires:
333/// - Entity can perform full updates
334/// - Entity has the `sparse` field set to `true`
335/// - `QuickBooks` API supports sparse updates for this entity type
336///
337/// # Examples
338///
339/// ```no_run
340/// use quickbooks_types::{Customer, QBSparseUpdateable};
341///
342/// let mut customer = Customer::default();
343/// customer.id = Some("123".to_string());
344/// customer.sync_token = Some("2".to_string());
345/// customer.display_name = Some("John Doe".to_string());
346/// customer.sparse = Some(true);
347///
348/// // Check if can be sparse updated
349/// if customer.can_sparse_update() {
350///     // Proceed with sparse update
351/// }
352/// ```
353pub trait QBSparseUpdateable {
354    fn can_sparse_update(&self) -> bool;
355}
356
357/// Trait for entities that can be sent via email from `QuickBooks`.
358///
359/// This trait marks entities that support `QuickBooks`' built-in email functionality,
360/// such as sending invoices or estimates to customers via email.
361///
362/// # Supported Entities
363///
364/// Typically includes: Invoice, Estimate, `SalesReceipt`, and other customer-facing documents.
365pub trait QBSendable {}
366
367/// Trait for entities that can be generated as PDF documents.
368///
369/// This trait marks entities that support `QuickBooks`' PDF generation functionality,
370/// allowing you to retrieve formatted PDF versions of documents.
371///
372/// # Supported Entities
373///
374/// Typically includes: Invoice, Estimate, `SalesReceipt`, Statement, and other printable documents.
375pub trait QBPDFable {}
376
377/// Trait for entities that can be converted to `QuickBooks` entity references.
378///
379/// Entity references (`NtRef`) are used throughout `QuickBooks` to link entities together.
380/// For example, an invoice has a customer reference that points to a specific customer.
381///
382/// # Required Methods
383///
384/// - `to_ref()`: Converts the entity to an `NtRef` for use in other entities
385///
386/// # Returns
387///
388/// Returns a `Result<NtRef, QBTypeError>` where:
389/// - `Ok(NtRef)` if the entity can be referenced (has ID and name field)
390/// - `Err(QBTypeError::QBToRefError)` if the entity cannot be referenced
391///
392/// # Examples
393///
394/// ```no_run
395/// use quickbooks_types::{Invoice, Customer, QBToRef};
396///
397/// let mut customer = Customer::default();
398/// customer.id = Some("123".to_string());
399/// customer.display_name = Some("John Doe".to_string());
400///
401/// // Convert to reference for use in other entities
402/// let customer_ref = customer.to_ref().unwrap();
403///
404/// // Use the reference in an invoice
405/// let mut invoice = Invoice::default();
406/// invoice.customer_ref = Some(customer_ref);
407/// ```
408pub trait QBToRef: QBItem {
409    fn to_ref(&self) -> Result<NtRef, QBTypeError>;
410}
411
412macro_rules! impl_qb_to_ref {
413  ($($struct:ident {$name_field:ident}),+) => {
414    $(
415      impl QBToRef for $struct {
416        fn to_ref(&self) -> Result<NtRef, $crate::QBTypeError> {
417          if self.id.is_some() {
418            Ok(NtRef {
419              entity_ref_type: Some(Self::name().into()),
420              name: self.$name_field.clone(),
421              value: self.id.clone()
422            })
423          } else {
424            Err($crate::QBTypeError::QBToRefError)
425          }
426        }
427      }
428    )+
429  }
430}
431
432impl_qb_to_ref!(
433    Account {
434        fully_qualified_name
435    },
436    Attachable { file_name },
437    Invoice { doc_number },
438    SalesReceipt { doc_number },
439    Item { name },
440    Customer { display_name },
441    Vendor { display_name }
442);
443
444/*
445Create: ✓
446- Account
447- Attachable
448- Bill
449- Customer
450- Employee
451- Estimate
452- Invoice
453- Item (Category)
454- Payment
455- Sales Receipt
456- Vendor
457Read: ✓
458- Attachable
459- Account
460- Bill
461- CompanyInfo
462- Customer
463- Employee
464- Estimate
465- Invoice
466- Item (Category, Bundle)
467- Preferences
468- Sales Receipt
469- Vendor
470Query: ✓
471- Attachable
472- Account
473- Bill
474- CompanyInfo
475- Customer
476- Employee
477- Estimate
478- Invoice
479- Item (Category, Bundle)
480- Payment
481- Preferences
482- Sales Receipt
483- Vendor
484Delete: ✓
485- Attachable
486- Bill
487- Estimate
488- Invoice
489- Payment
490- Sales Receipt
491Void: ✓
492- Invoice
493- Payment
494- Sales Receipt
495Full Update: ✓
496- Account
497- Attachable
498- Bill
499- CompanyInfo
500- Customer
501- Employee
502- Estimate
503- Invoice
504- Item (Category)
505- Payment
506- Preferences
507- Sales Receipt
508- Vendor
509Sparse Update: ✓
510- CompanyInfo
511- Customer
512- Estimate
513- Invoice
514- Sales Receipt
515Send: ✓
516- Estimate
517- Invoice
518- Payment
519- Sales Receipt
520Get as PDF: ✓
521- Estimate
522- Invoice
523- Payment
524- Sales Receipt
525
526- Attachment has three other actions that are unique
527- Upload ✓
528
529*/