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)]
18pub struct Customer {
25 pub id: Option<String>,
27 pub sync_token: Option<String>,
29 #[serde(skip_serializing)]
31 pub meta_data: Option<MetaData>,
32 pub display_name: Option<String>,
34 pub title: Option<String>,
36 pub given_name: Option<String>,
38 pub middle_name: Option<String>,
40 #[serde(rename = "sparse")]
42 pub sparse: Option<bool>,
43 pub suffix: Option<String>,
45 pub family_name: Option<String>,
47 pub primary_email_addr: Option<Email>,
49 pub resale_num: Option<String>,
51 pub secondary_tax_identifier: Option<String>,
53 pub ar_account_ref: Option<NtRef>,
55 pub default_tax_code_ref: Option<NtRef>,
57 pub preferred_delivery_method: Option<String>,
59 pub sales_term_ref: Option<NtRef>,
61 pub customer_type_ref: Option<String>,
63 pub fax: Option<PhoneNumber>,
65 pub bill_with_parent: Option<bool>,
67 pub currency_ref: Option<NtRef>,
69 pub mobile: Option<PhoneNumber>,
71 pub job: Option<bool>,
73 pub balance_with_jobs: Option<f64>,
75 pub primary_phone: Option<PhoneNumber>,
77 pub open_balance_date: Option<NaiveDate>,
79 pub taxable: Option<bool>,
81 pub alternate_phone: Option<PhoneNumber>,
83 pub parent_ref: Option<NtRef>,
85 pub notes: Option<String>,
87 pub web_addr: Option<WebAddr>,
89 pub active: Option<bool>,
91 pub company_name: Option<String>,
93 pub balance: Option<f64>,
95 pub ship_addr: Option<Addr>,
97 pub payment_method_ref: Option<NtRef>,
99 pub is_project: Option<bool>,
101 pub source: Option<String>,
105 pub print_check_on_name: Option<String>,
107 pub bill_addr: Option<Addr>,
109 pub fully_qualified_name: Option<String>,
111 pub level: Option<String>,
113 pub tax_exemption_reason_id: Option<TaxExemptStatus>,
115}
116
117#[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
145impl<'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}