world_tax/
types.rs

1//! Tax calculation types and structures.
2//! 
3//! This module contains the core types used for tax calculations across different
4//! tax systems including VAT, GST, HST, and other regional tax schemes. It provides
5//! the fundamental data structures and enums needed to represent tax scenarios,
6//! trade agreements, and calculation rules.
7
8use log::debug;
9use serde::{Deserialize, Deserializer, Serialize};
10use strum_macros::Display;
11use std::collections::HashMap;
12use crate::errors::InputValidationError;
13
14/// Represents different types of tax systems used globally.
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16#[serde(rename_all = "lowercase")]
17pub enum TaxSystemType {
18    /// Value Added Tax - Common in EU and many other countries
19    Vat,
20    /// Goods and Services Tax
21    Gst,
22    /// Provincial Sales Tax (Canadian)
23    Pst,
24    /// Harmonized Sales Tax (Canadian)
25    Hst,
26    /// Quebec Sales Tax
27    Qst,
28    /// No tax system applicable
29    None,
30}
31
32/// Defines the type of transaction between parties.
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
34#[serde(rename_all = "lowercase")]
35pub enum TransactionType {
36    /// Business to Business transaction
37    B2B,
38    /// Business to Consumer transaction
39    B2C,
40}
41
42/// Specifies how tax should be calculated for a given transaction.
43#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
44#[serde(rename_all = "snake_case")]
45pub enum TaxCalculationType {
46    /// Use origin tax rate; below threshold
47    Origin,
48    /// Use destination tax rate; above threshold
49    Destination,
50    /// Buyer pays tax in their country
51    ReverseCharge,
52    /// Tax applies but at zero rate
53    ZeroRated,
54    /// No tax applies
55    Exempt,
56    /// Tax status unknown
57    None,
58    /// Calculation depends on threshold
59    ThresholdBased
60}
61
62/// Represents different types of taxes that can be applied.
63#[derive(Debug, Clone, Serialize, Deserialize ,PartialEq)]
64#[serde(rename_all = "snake_case")]
65pub enum TaxType {
66    /// Value Added Tax with specific rate
67    VAT(VatRate),
68    /// Goods and Services Tax
69    GST,
70    /// Harmonized Sales Tax
71    HST,
72    /// Provincial Sales Tax
73    PST,
74    /// Quebec Sales Tax
75    QST,
76    /// US State Sales Tax
77    StateSalesTax,
78}
79
80/// Different rates that can be applied for Value Added Tax.
81#[derive(Debug, Clone, Display, Serialize, Deserialize, PartialEq)]
82#[serde(rename_all = "snake_case")]
83pub enum VatRate {
84    /// Standard VAT rate
85    Standard,    
86    /// Reduced VAT rate
87    Reduced,     
88    /// Alternative reduced VAT rate
89    ReducedAlt,  
90    /// Super-reduced VAT rate
91    SuperReduced,
92    /// Zero-rated goods/services
93    Zero,
94    /// VAT exempt
95    Exempt,
96    /// Reverse charge applies
97    ReverseCharge   
98}
99
100/// Defines the type of trade agreement between regions.
101#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
102#[serde(rename_all = "snake_case")]
103pub enum TradeAgreementType {
104    /// Agreement between multiple countries in a customs union (e.g., EU)
105    CustomsUnion,
106    /// Agreement between states within a federal system
107    FederalState
108}
109
110/// Override options for trade agreement application.
111#[derive(Debug, Clone)]
112pub enum TradeAgreementOverride {
113    /// Explicitly use a specific agreement (e.g., "EU", "USMCA")
114    UseAgreement(String),
115    /// Force no trade agreement to be applied
116    NoAgreement,
117}
118
119/// Specifies which types of goods/services an agreement applies to.
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct AppliesTo {
122    /// Whether the agreement applies to physical goods
123    pub physical_goods: bool,
124    /// Whether the agreement applies to digital goods
125    pub digital_goods: bool,
126    /// Whether the agreement applies to services
127    pub services: bool,
128}
129
130/// Represents a trade agreement between regions or states.
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct TradeAgreement {
133    /// Name of the trade agreement
134    pub name: String,
135    /// Type of the trade agreement
136    pub r#type: TradeAgreementType,
137    /// List of member regions/states
138    pub members: Vec<String>,
139    /// Whether agreement applies by default
140    pub default_applicable: bool,
141    /// Types of goods/services covered
142    pub applies_to: AppliesTo,
143    /// Tax rules under this agreement
144    pub tax_rules: TaxRules,
145}
146
147impl TradeAgreement {
148    /// Returns true if this is a federal-level agreement
149    pub fn is_federal(&self) -> bool {
150        self.r#type == TradeAgreementType::FederalState
151    }
152
153    /// Returns true if this is an international agreement
154    pub fn is_international(&self) -> bool {
155        self.r#type == TradeAgreementType::CustomsUnion
156    }
157}
158
159/// Configuration for tax calculation rules based on various thresholds and conditions.
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct TaxRuleConfig {
162    /// Default tax calculation type
163    pub r#type: TaxCalculationType,
164    /// Tax calculation type for amounts below threshold
165    pub below_threshold: Option<TaxCalculationType>,
166    /// Tax calculation type for amounts above threshold
167    pub above_threshold: Option<TaxCalculationType>,
168    /// Monetary threshold for standard goods
169    pub threshold: Option<u32>,
170    /// Tax calculation type for digital products below threshold
171    pub below_threshold_digital_products: Option<TaxCalculationType>,
172    /// Tax calculation type for digital products above threshold
173    pub above_threshold_digital_products: Option<TaxCalculationType>,
174    /// Monetary threshold for digital products
175    pub threshold_digital_products: Option<u32>,
176    /// Whether a resale certificate is required for special treatment
177    pub requires_resale_certificate: Option<bool>,
178}
179
180impl TaxRuleConfig {
181    /// Determines the tax calculation type based on the amount and threshold
182    /// 
183    /// # Arguments
184    /// * `amount` - The transaction amount
185    /// * `ignore_threshold` - Whether to ignore threshold-based calculations
186    pub fn by_threshold(&self, amount: u32, ignore_threshold: bool) -> &TaxCalculationType {
187        let has_threshold = self.below_threshold.is_some() && self.above_threshold.is_some() && self.threshold.is_some();
188        if has_threshold {
189            let rule_threshold: u32 = self.threshold.unwrap();
190            if amount < rule_threshold && !ignore_threshold {
191                return self.below_threshold.as_ref().unwrap();
192            } else {
193                return self.above_threshold.as_ref().unwrap();
194            }
195        }
196        &self.r#type
197    }
198
199    /// Determines the tax calculation type for digital products based on amount and threshold
200    pub fn by_digital_product_threshold(&self, amount: u32, ignore_threshold: bool) -> &TaxCalculationType {
201        let has_threshold = self.below_threshold_digital_products.is_some() 
202            && self.above_threshold_digital_products.is_some() 
203            && self.threshold_digital_products.is_some();
204        if has_threshold {
205            let rule_threshold: u32 = self.threshold_digital_products.unwrap();
206            if amount < rule_threshold && !ignore_threshold {
207                return self.below_threshold_digital_products.as_ref().unwrap();
208            } else {
209                return self.above_threshold_digital_products.as_ref().unwrap();
210            }
211        }
212        &self.r#type
213    }
214
215    /// Determines the appropriate tax calculation type based on product type and amount
216    pub fn by_threshold_or_digital_product_threshold(
217        &self,
218        amount: u32,
219        is_digital_product_or_service: bool,
220        ignore_threshold: bool
221    ) -> &TaxCalculationType {
222        if is_digital_product_or_service {
223            return self.by_digital_product_threshold(amount, ignore_threshold);
224        }
225        self.by_threshold(amount, ignore_threshold)
226    }
227
228    /// Determines if the transaction qualifies for reseller treatment
229    pub fn is_reseller(&self, has_resale_certificate: bool) -> bool {
230        if self.requires_resale_certificate.is_some() {
231            return self.requires_resale_certificate.unwrap() && has_resale_certificate;
232        }
233        false
234    }
235}
236
237/// Collection of tax rules for different transaction scenarios
238#[derive(Debug, Clone, Serialize, Deserialize)]
239pub struct TaxRules {
240    /// Rules for internal B2B transactions
241    pub internal_b2b: Option<TaxRuleConfig>,
242    /// Rules for internal B2C transactions
243    pub internal_b2c: Option<TaxRuleConfig>,
244    /// Rules for external exports
245    pub external_export: TaxRuleConfig,
246}
247
248/// Product-specific tax rules configuration
249#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct ProductRules {
251    /// Default tax rule to apply
252    pub default: String,
253    /// Tax rules for specific products
254    pub specific_products: HashMap<String, String>,
255}
256
257/// Represents tax information for a state/province
258#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct State {
260    /// Standard tax rate for the state
261    pub standard_rate: f64,
262    /// Type of tax system used in the state
263    #[serde(rename = "type")]
264    pub tax_type: TaxSystemType,
265}
266
267/// Custom deserializer for handling rate values that might be boolean or numeric
268fn deserialize_rate<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
269where
270    D: Deserializer<'de>,
271{
272    #[derive(Deserialize)]
273    #[serde(untagged)]
274    enum RateValue {
275        Number(f64),
276        Boolean(bool),
277    }
278
279    match Option::<RateValue>::deserialize(deserializer)? {
280        Some(RateValue::Number(rate)) => Ok(Some(rate)),
281        Some(RateValue::Boolean(false)) => Ok(None),
282        Some(RateValue::Boolean(true)) => Ok(None),
283        None => Ok(None),
284    }
285}
286
287/// Represents tax information for a country
288#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct Country {
290    /// Type of tax system used in the country
291    #[serde(rename = "type")]
292    pub tax_type: TaxSystemType,
293    /// Currency code for the country
294    pub currency: String,
295    /// Standard tax rate
296    pub standard_rate: f64,
297    /// Reduced tax rate if applicable
298    #[serde(default, deserialize_with = "deserialize_rate")]
299    pub reduced_rate: Option<f64>,
300    /// Alternative reduced tax rate if applicable
301    #[serde(default, deserialize_with = "deserialize_rate")]
302    pub reduced_rate_alt: Option<f64>,
303    /// Super-reduced tax rate if applicable
304    #[serde(default, deserialize_with = "deserialize_rate")]
305    pub super_reduced_rate: Option<f64>,
306    /// Parking rate if applicable
307    #[serde(default, deserialize_with = "deserialize_rate")]
308    pub parking_rate: Option<f64>,
309    /// Full name of the VAT system
310    pub vat_name: Option<String>,
311    /// Abbreviation of the VAT system name
312    pub vat_abbr: Option<String>,
313    /// Tax information for states/provinces if applicable
314    pub states: Option<HashMap<String, State>>,
315}
316
317/// Represents a geographical region for tax purposes
318/// 
319/// # Examples
320/// 
321/// ```
322/// # use world_tax::types::Region;
323/// // Create a region for France (no sub-region)
324/// let france = Region::new("FR".to_string(), None).unwrap();
325/// 
326/// // Create a region for California, USA
327/// let california = Region::new("US".to_string(), Some("US-CA".to_string())).unwrap();
328/// ```
329#[derive(Debug, Clone)]
330pub struct Region {
331    /// ISO 3166-1 alpha-2 country code
332    pub country: String,
333    /// Optional ISO 3166-2 region code
334    pub region: Option<String>,
335}
336
337impl Region {
338    /// Creates a new Region with validation
339    pub fn new(country: String, region: Option<String>) -> Result<Self, InputValidationError> {
340        Self::validate(&country, &region)?;
341        Ok(Self { country, region })
342    }
343
344    /// Validates country and region codes against ISO standards
345    fn validate(country: &str, region: &Option<String>) -> Result<(), InputValidationError> {
346        let country_info = rust_iso3166::from_alpha2(country)
347            .ok_or_else(|| InputValidationError::InvalidCountryCode(country.to_string()))?;
348            
349        debug!("Found country: {}", country_info.name);
350
351        if let Some(region_code) = region {
352            let _ = country_info.subdivisions()
353                .ok_or_else(|| InputValidationError::UnexpectedRegionCode(region_code.clone()))?;
354                
355            let region_info = rust_iso3166::iso3166_2::from_code(region_code)
356                .ok_or_else(|| InputValidationError::InvalidRegionCode(region_code.clone()))?;
357
358            debug!("Found region: {}", region_info.name);
359        }
360
361        Ok(())
362    }
363}
364
365/// Represents a complete tax calculation scenario
366#[derive(Debug, Clone)]
367pub struct TaxScenario {
368    /// Region where the seller is located
369    pub source_region: Region,
370    /// Region where the buyer is located
371    pub destination_region: Region,
372    /// Type of transaction (B2B or B2C)
373    pub transaction_type: TransactionType,
374    /// How the tax should be calculated
375    // pub calculation_type: TaxCalculationType,
376    /// Optional override for trade agreement application
377    pub trade_agreement_override: Option<TradeAgreementOverride>,
378    /// Whether the product/service is digital
379    pub is_digital_product_or_service: bool,
380    /// Whether the buyer has a resale certificate (relevant for B2B in US)
381    pub has_resale_certificate: bool,
382    /// Whether to ignore thresholds in calculations
383    pub ignore_threshold: bool,
384    /// Specific VAT rate to apply if applicable
385    pub vat_rate: Option<VatRate>,
386}
387
388/// Represents a specific tax rate and its characteristics.
389#[derive(Debug, Serialize, Deserialize)]
390pub struct TaxRate {
391    /// The numerical tax rate as a decimal (e.g., 0.20 for 20%)
392    pub rate: f64,
393    /// The type of tax (VAT, GST, etc.)
394    pub tax_type: TaxType,
395    /// Whether this tax compounds on top of other taxes
396    pub compound: bool,
397}