quickbooks_types/
lib.rs

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