quickbooks_types/models/
customer.rs

1use chrono::NaiveDate;
2use serde::{Deserialize, Serialize};
3use serde_with::skip_serializing_none;
4
5use super::common::{Addr, Email, MetaData, NtRef, PhoneNumber, WebAddr};
6#[cfg(feature = "builder")]
7use crate::error::QBTypeError;
8use crate::{QBCreatable, QBFullUpdatable, QBReadable, QBSparseUpdateable};
9
10#[skip_serializing_none]
11#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Default)]
12#[serde(rename_all = "PascalCase", default)]
13#[cfg_attr(
14    feature = "builder",
15    derive(Builder),
16    builder(default, build_fn(error = "QBTypeError"), setter(into, strip_option))
17)]
18/// Customer
19///
20/// Represents an individual or organization that purchases goods or services and is billed in `QuickBooks` Online.
21///
22/// Creation requirements:
23/// - `QBCreatable::can_create()` returns true when at least one of these fields is present:
24///   `display_name`, `given_name`, `family_name`, `middle_name`, `title`, or `suffix`.
25///
26/// Update semantics:
27/// - `QBFullUpdatable::can_full_update()` requires `can_read()` (ID present) and `can_create()`.
28///
29/// API reference:
30/// <https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/customer>
31pub struct Customer {
32    /// The unique ID of the entity
33    pub id: Option<String>,
34    /// The unique sync token of the entity, used for concurrency control
35    pub sync_token: Option<String>,
36    /// Metadata about the entity
37    #[serde(skip_serializing)]
38    pub meta_data: Option<MetaData>,
39    /// Display name of the customer
40    pub display_name: Option<String>,
41    /// Title of the customer (e.g., Mr., Mrs., Ms.)
42    pub title: Option<String>,
43    /// First name of the customer
44    pub given_name: Option<String>,
45    /// Middle name of the customer
46    pub middle_name: Option<String>,
47    /// Indicates if the entity is a sparse object
48    #[serde(rename = "sparse")]
49    pub sparse: Option<bool>,
50    /// Suffix of the customer's name (e.g., Jr., Sr., III)
51    pub suffix: Option<String>,
52    /// Last name of the customer
53    pub family_name: Option<String>,
54    /// Primary email address of the customer
55    pub primary_email_addr: Option<Email>,
56    /// Resale number for the customer
57    pub resale_num: Option<String>,
58    /// Secondary tax identifier for the customer
59    pub secondary_tax_identifier: Option<String>,
60    /// Reference to the Accounts Receivable account for the customer
61    pub ar_account_ref: Option<NtRef>,
62    /// Reference to the default tax code for the customer
63    pub default_tax_code_ref: Option<NtRef>,
64    /// Preferred delivery method for the customer (None, Print, Email, or Trax)
65    pub preferred_delivery_method: Option<String>,
66    /// Reference to the sales term for the customer
67    pub sales_term_ref: Option<NtRef>,
68    /// Reference to the customer type for the customer
69    pub customer_type_ref: Option<String>,
70    /// Fax number of the customer
71    pub fax: Option<PhoneNumber>,
72    /// Indicates if the customer is billed with their parent
73    pub bill_with_parent: Option<bool>,
74    /// Reference to the currency for the customer
75    pub currency_ref: Option<NtRef>,
76    /// Mobile phone number of the customer
77    pub mobile: Option<PhoneNumber>,
78    /// Indicates if the customer is a job
79    pub job: Option<bool>,
80    /// Balance including all jobs related to the customer
81    pub balance_with_jobs: Option<f64>,
82    /// Primary phone number of the customer
83    pub primary_phone: Option<PhoneNumber>,
84    /// Date of the open balance in YYYY-MM-DD format
85    pub open_balance_date: Option<NaiveDate>,
86    /// Indicates if the customer is taxable
87    pub taxable: Option<bool>,
88    /// Alternative phone number of the customer
89    pub alternate_phone: Option<PhoneNumber>,
90    /// Reference to the parent of the customer
91    pub parent_ref: Option<NtRef>,
92    /// Notes about the customer
93    pub notes: Option<String>,
94    /// Web address (URL) of the customer
95    pub web_addr: Option<WebAddr>,
96    /// Indicates if the customer is active
97    pub active: Option<bool>,
98    /// Company name of the customer
99    pub company_name: Option<String>,
100    /// Current balance of the customer
101    pub balance: Option<f64>,
102    /// Shipping address of the customer
103    pub ship_addr: Option<Addr>,
104    /// Reference to the default payment method for the customer
105    pub payment_method_ref: Option<NtRef>,
106    /// Indicates if the customer is a project
107    pub is_project: Option<bool>,
108    /// Source of the customer record
109    ///
110    /// DEPRECATED: as of 9/15/2025
111    pub source: Option<String>,
112    /// Name to print on checks for the customer
113    pub print_check_on_name: Option<String>,
114    /// Billing address of the customer
115    pub bill_addr: Option<Addr>,
116    /// Fully qualified name of the customer
117    pub fully_qualified_name: Option<String>,
118    /// Level in the customer hierarchy
119    pub level: Option<String>,
120    /// Tax exemption reason identifier for the customer
121    pub tax_exemption_reason_id: Option<TaxExemptStatus>,
122}
123
124/// `TaxExemptStatus`
125///
126/// Enumerates QuickBooks-defined reason codes for tax exemption. Values may be returned
127/// either as their numeric code (1-15) or as strings that can be parsed to those codes.
128/// Unknown or unsupported values are coerced to `Other` during deserialization.
129#[skip_serializing_none]
130#[derive(Clone, Debug, PartialEq, Serialize, Default)]
131#[serde(into = "u8")]
132pub enum TaxExemptStatus {
133    FederalGovernment,
134    StateGovernment,
135    LocalGovernment,
136    TribalGovernment,
137    CharitableOrganization,
138    ReligiousOrganization,
139    EducationalOrganization,
140    Hospital,
141    Resale,
142    DirectPayPermit,
143    MultiplePointsOfUse,
144    DirectMail,
145    AgriculturalProduction,
146    IndustrialProductionOrManufacturing,
147    ForeignDiplomat,
148    #[default]
149    Other,
150}
151
152// TODO - Make sure this is necessary, as it might only be strings that are returned from
153// the API, and not numbers.
154impl<'de> Deserialize<'de> for TaxExemptStatus {
155    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
156    where
157        D: serde::Deserializer<'de>,
158    {
159        #[derive(Deserialize)]
160        #[serde(untagged)]
161        enum StringOrNumber {
162            String(String),
163            Number(u8),
164        }
165
166        match StringOrNumber::deserialize(deserializer)? {
167            StringOrNumber::String(s) => match s.parse::<u8>() {
168                Ok(num) => Ok(num.into()),
169                Err(_) => Ok(TaxExemptStatus::Other),
170            },
171            StringOrNumber::Number(num) => Ok(num.into()),
172        }
173    }
174}
175
176impl From<u8> for TaxExemptStatus {
177    fn from(value: u8) -> Self {
178        match value {
179            1 => TaxExemptStatus::FederalGovernment,
180            2 => TaxExemptStatus::StateGovernment,
181            3 => TaxExemptStatus::LocalGovernment,
182            4 => TaxExemptStatus::TribalGovernment,
183            5 => TaxExemptStatus::CharitableOrganization,
184            6 => TaxExemptStatus::ReligiousOrganization,
185            7 => TaxExemptStatus::EducationalOrganization,
186            8 => TaxExemptStatus::Hospital,
187            9 => TaxExemptStatus::Resale,
188            10 => TaxExemptStatus::DirectPayPermit,
189            11 => TaxExemptStatus::MultiplePointsOfUse,
190            12 => TaxExemptStatus::DirectMail,
191            13 => TaxExemptStatus::AgriculturalProduction,
192            14 => TaxExemptStatus::IndustrialProductionOrManufacturing,
193            15 => TaxExemptStatus::ForeignDiplomat,
194            _ => TaxExemptStatus::Other,
195        }
196    }
197}
198
199impl From<TaxExemptStatus> for u8 {
200    fn from(value: TaxExemptStatus) -> u8 {
201        match value {
202            TaxExemptStatus::Other => 0,
203            TaxExemptStatus::FederalGovernment => 1,
204            TaxExemptStatus::StateGovernment => 2,
205            TaxExemptStatus::LocalGovernment => 3,
206            TaxExemptStatus::TribalGovernment => 4,
207            TaxExemptStatus::CharitableOrganization => 5,
208            TaxExemptStatus::ReligiousOrganization => 6,
209            TaxExemptStatus::EducationalOrganization => 7,
210            TaxExemptStatus::Hospital => 8,
211            TaxExemptStatus::Resale => 9,
212            TaxExemptStatus::DirectPayPermit => 10,
213            TaxExemptStatus::MultiplePointsOfUse => 11,
214            TaxExemptStatus::DirectMail => 12,
215            TaxExemptStatus::AgriculturalProduction => 13,
216            TaxExemptStatus::IndustrialProductionOrManufacturing => 14,
217            TaxExemptStatus::ForeignDiplomat => 15,
218        }
219    }
220}
221
222impl QBCreatable for Customer {
223    fn can_create(&self) -> bool {
224        self.display_name.is_some()
225            || self.suffix.is_some()
226            || self.title.is_some()
227            || self.middle_name.is_some()
228            || self.family_name.is_some()
229            || self.given_name.is_some()
230    }
231}
232
233impl QBFullUpdatable for Customer {
234    fn can_full_update(&self) -> bool {
235        self.can_read() && self.can_create()
236    }
237}
238
239impl QBSparseUpdateable for Customer {
240    fn can_sparse_update(&self) -> bool {
241        self.can_full_update()
242    }
243}