1use serde::{Deserialize, Serialize};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(rename_all = "lowercase")]
18pub enum Jurisdiction {
19 Sg,
20 Uk,
21 Us,
22 Eu,
23 Custom,
24}
25
26impl Jurisdiction {
27 pub fn from_str(s: &str) -> Option<Self> {
28 match s.to_lowercase().as_str() {
29 "sg" | "singapore" => Some(Self::Sg),
30 "uk" | "gb" | "gbr" | "united-kingdom" => Some(Self::Uk),
31 "us" | "usa" | "united-states" => Some(Self::Us),
32 "eu" | "de" | "fr" | "nl" | "at" | "ie" => Some(Self::Eu),
33 "custom" | "intl" | "international" => Some(Self::Custom),
34 _ => None,
35 }
36 }
37
38 pub fn profile(&self) -> TaxProfile {
39 match self {
40 Self::Sg => TaxProfile {
41 code: "sg",
42 country: "Singapore",
43 tax_label: "GST",
44 default_rate: 9.0,
45 currency: "SGD",
46 symbol: "S$",
47 tax_invoice_title: "Tax Invoice",
48 non_registered_title: "Invoice",
49 tax_id_label: "GST Reg. No.",
50 company_no_label: "UEN",
51 date_format: "%-d %B %Y",
52 supports_reverse_charge: false,
53 zero_rate_label: "Zero-rated",
54 },
55 Self::Uk => TaxProfile {
56 code: "uk",
57 country: "United Kingdom",
58 tax_label: "VAT",
59 default_rate: 20.0,
60 currency: "GBP",
61 symbol: "£",
62 tax_invoice_title: "VAT Invoice",
63 non_registered_title: "Invoice",
64 tax_id_label: "VAT No.",
65 company_no_label: "Company No.",
66 date_format: "%-d %B %Y",
67 supports_reverse_charge: true,
68 zero_rate_label: "Zero-rated",
69 },
70 Self::Us => TaxProfile {
71 code: "us",
72 country: "United States",
73 tax_label: "Sales tax",
74 default_rate: 0.0,
75 currency: "USD",
76 symbol: "$",
77 tax_invoice_title: "Invoice",
78 non_registered_title: "Invoice",
79 tax_id_label: "EIN",
80 company_no_label: "State ID",
81 date_format: "%B %-d, %Y",
82 supports_reverse_charge: false,
83 zero_rate_label: "Exempt",
84 },
85 Self::Eu => TaxProfile {
86 code: "eu",
87 country: "European Union",
88 tax_label: "VAT",
89 default_rate: 19.0, currency: "EUR",
91 symbol: "€",
92 tax_invoice_title: "Rechnung / Invoice",
93 non_registered_title: "Invoice",
94 tax_id_label: "VAT ID",
95 company_no_label: "Reg. No.",
96 date_format: "%-d %B %Y",
97 supports_reverse_charge: true,
98 zero_rate_label: "Reverse charge",
99 },
100 Self::Custom => TaxProfile {
101 code: "custom",
102 country: "International",
103 tax_label: "Tax",
104 default_rate: 0.0,
105 currency: "USD",
106 symbol: "$",
107 tax_invoice_title: "Invoice",
108 non_registered_title: "Invoice",
109 tax_id_label: "Tax ID",
110 company_no_label: "Reg. No.",
111 date_format: "%Y-%m-%d",
112 supports_reverse_charge: false,
113 zero_rate_label: "Zero-rated",
114 },
115 }
116 }
117
118 pub fn as_str(&self) -> &'static str {
119 match self {
120 Self::Sg => "sg",
121 Self::Uk => "uk",
122 Self::Us => "us",
123 Self::Eu => "eu",
124 Self::Custom => "custom",
125 }
126 }
127}
128
129#[derive(Debug, Clone, Serialize)]
130pub struct TaxProfile {
131 pub code: &'static str,
132 pub country: &'static str,
133 pub tax_label: &'static str,
134 pub default_rate: f64,
135 pub currency: &'static str,
136 pub symbol: &'static str,
137 pub tax_invoice_title: &'static str,
138 pub non_registered_title: &'static str,
139 pub tax_id_label: &'static str,
140 pub company_no_label: &'static str,
141 pub date_format: &'static str,
142 pub supports_reverse_charge: bool,
143 pub zero_rate_label: &'static str,
144}
145
146impl TaxProfile {
147 pub fn title(&self, tax_registered: bool) -> &'static str {
148 if tax_registered {
149 self.tax_invoice_title
150 } else {
151 self.non_registered_title
152 }
153 }
154}
155
156pub fn all_profiles() -> Vec<TaxProfile> {
157 vec![
158 Jurisdiction::Sg.profile(),
159 Jurisdiction::Uk.profile(),
160 Jurisdiction::Us.profile(),
161 Jurisdiction::Eu.profile(),
162 Jurisdiction::Custom.profile(),
163 ]
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn sg_gst_defaults() {
172 let p = Jurisdiction::Sg.profile();
173 assert_eq!(p.tax_label, "GST");
174 assert_eq!(p.default_rate, 9.0);
175 assert_eq!(p.currency, "SGD");
176 assert_eq!(p.title(true), "Tax Invoice");
177 assert_eq!(p.title(false), "Invoice");
178 }
179
180 #[test]
181 fn uk_vat_defaults() {
182 let p = Jurisdiction::Uk.profile();
183 assert_eq!(p.tax_label, "VAT");
184 assert_eq!(p.default_rate, 20.0);
185 assert_eq!(p.currency, "GBP");
186 assert_eq!(p.title(true), "VAT Invoice");
187 }
188
189 #[test]
190 fn parses_aliases() {
191 assert_eq!(Jurisdiction::from_str("SG"), Some(Jurisdiction::Sg));
192 assert_eq!(Jurisdiction::from_str("united-kingdom"), Some(Jurisdiction::Uk));
193 assert_eq!(Jurisdiction::from_str("gb"), Some(Jurisdiction::Uk));
194 assert_eq!(Jurisdiction::from_str("unknown"), None);
195 }
196}