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