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 {
32 pub id: Option<String>,
34 pub sync_token: Option<String>,
36 #[serde(skip_serializing)]
38 pub meta_data: Option<MetaData>,
39 pub display_name: Option<String>,
41 pub title: Option<String>,
43 pub given_name: Option<String>,
45 pub middle_name: Option<String>,
47 #[serde(rename = "sparse")]
49 pub sparse: Option<bool>,
50 pub suffix: Option<String>,
52 pub family_name: Option<String>,
54 pub primary_email_addr: Option<Email>,
56 pub resale_num: Option<String>,
58 pub secondary_tax_identifier: Option<String>,
60 pub ar_account_ref: Option<NtRef>,
62 pub default_tax_code_ref: Option<NtRef>,
64 pub preferred_delivery_method: Option<String>,
66 pub sales_term_ref: Option<NtRef>,
68 pub customer_type_ref: Option<String>,
70 pub fax: Option<PhoneNumber>,
72 pub bill_with_parent: Option<bool>,
74 pub currency_ref: Option<NtRef>,
76 pub mobile: Option<PhoneNumber>,
78 pub job: Option<bool>,
80 pub balance_with_jobs: Option<f64>,
82 pub primary_phone: Option<PhoneNumber>,
84 pub open_balance_date: Option<NaiveDate>,
86 pub taxable: Option<bool>,
88 pub alternate_phone: Option<PhoneNumber>,
90 pub parent_ref: Option<NtRef>,
92 pub notes: Option<String>,
94 pub web_addr: Option<WebAddr>,
96 pub active: Option<bool>,
98 pub company_name: Option<String>,
100 pub balance: Option<f64>,
102 pub ship_addr: Option<Addr>,
104 pub payment_method_ref: Option<NtRef>,
106 pub is_project: Option<bool>,
108 pub source: Option<String>,
112 pub print_check_on_name: Option<String>,
114 pub bill_addr: Option<Addr>,
116 pub fully_qualified_name: Option<String>,
118 pub level: Option<String>,
120 pub tax_exemption_reason_id: Option<TaxExemptStatus>,
122}
123
124#[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
152impl<'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}