1use crate::tax::TaxSystem;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub enum Edition {
8 En2017A1,
10 En2026,
12}
13
14impl Edition {
15 pub fn is_implemented(self) -> bool {
20 matches!(self, Self::En2017A1)
21 }
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum ProfileLookup {
27 Profile(Profile),
29 WrongProcess,
31 Unknown,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37pub enum Profile {
38 En16931,
40 PeppolBis3,
42 Pint,
44 PintMy,
46 Unknown,
48}
49
50impl Profile {
51 pub fn slug(self) -> &'static str {
53 match self {
54 Self::En16931 => "en16931",
55 Self::PeppolBis3 => "peppol",
56 Self::Pint => "pint",
57 Self::PintMy => "pint-my",
58 Self::Unknown => "unknown",
59 }
60 }
61
62 pub fn parse(s: &str) -> Option<Self> {
64 match s.trim().to_ascii_lowercase().as_str() {
65 "en16931" | "en-16931" | "core" => Some(Self::En16931),
66 "peppol" | "bis3" | "peppol-bis-3" => Some(Self::PeppolBis3),
67 "pint" => Some(Self::Pint),
68 "pint-my" | "pintmy" | "my" => Some(Self::PintMy),
69 _ => None,
70 }
71 }
72
73 pub fn specification_id(self) -> &'static str {
75 match self {
76 Self::En16931 => "urn:cen.eu:en16931:2017",
77 Self::PeppolBis3 => {
78 "urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0"
79 }
80 Self::Pint => "urn:peppol:pint:billing-1",
81 Self::PintMy => "urn:peppol:pint:billing-1@my-1",
82 Self::Unknown => "",
84 }
85 }
86
87 pub fn process_id(self) -> Option<&'static str> {
89 match self {
90 Self::PeppolBis3 => Some("urn:fdc:peppol.eu:2017:poacc:billing:01:1.0"),
91 Self::Pint | Self::PintMy => Some("urn:peppol:bis:billing"),
92 Self::En16931 | Self::Unknown => None,
93 }
94 }
95
96 pub fn tax_systems(self) -> &'static [TaxSystem] {
98 match self {
99 Self::En16931 | Self::PeppolBis3 => &[TaxSystem::Vat],
100 Self::Pint => &[
101 TaxSystem::Vat,
102 TaxSystem::Gst,
103 TaxSystem::Sst,
104 TaxSystem::Consumption,
105 ],
106 Self::PintMy => &[TaxSystem::Sst],
108 Self::Unknown => &[],
109 }
110 }
111
112 pub fn allows(self, system: TaxSystem) -> bool {
114 self.tax_systems().contains(&system)
115 }
116
117 pub fn known_slugs() -> &'static str {
119 "en16931, peppol, pint, pint-my"
120 }
121
122 pub fn edition(self) -> Edition {
124 Edition::En2017A1
125 }
126
127 pub fn is_conformant_cius(self) -> bool {
129 matches!(self, Self::PeppolBis3)
130 }
131
132 pub fn extra_rules(self) -> &'static [crate::rules::Rule] {
134 match self {
135 Self::PeppolBis3 => crate::peppol::RULES,
136 _ => &[],
137 }
138 }
139
140 pub const PEPPOL_BIS3_PREFIX: &'static str =
142 "urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0";
143
144 pub fn for_specification_id(id: &str) -> ProfileLookup {
146 let id = id.trim();
147 if id.contains('*') {
148 return ProfileLookup::Unknown;
149 }
150 if id.starts_with("urn:peppol:pint:selfbilling-1@my-1")
151 || id.starts_with(
152 "urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:selfbilling:3.0",
153 )
154 {
155 return ProfileLookup::WrongProcess;
156 }
157 if id.starts_with("urn:peppol:pint:billing-1@my-1") {
158 return ProfileLookup::Profile(Self::PintMy);
159 }
160 if id.starts_with("urn:peppol:pint:billing-1@") {
161 return ProfileLookup::Profile(Self::Pint);
162 }
163 if id.starts_with("urn:peppol:pint:billing-1") {
164 return ProfileLookup::Profile(Self::Pint);
165 }
166 if id.starts_with(Self::PEPPOL_BIS3_PREFIX) {
167 return ProfileLookup::Profile(Self::PeppolBis3);
168 }
169 if id.starts_with("urn:cen.eu:en16931:2017#compliant#") || id == "urn:cen.eu:en16931:2017" {
170 return ProfileLookup::Profile(Self::En16931);
171 }
172 ProfileLookup::Unknown
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn lookup_is_prefix_not_contains() {
182 assert_eq!(
183 Profile::for_specification_id("urn:peppol:pint:billing-1@my-1"),
184 ProfileLookup::Profile(Profile::PintMy)
185 );
186 assert_eq!(
187 Profile::for_specification_id("urn:peppol:pint:billing-1"),
188 ProfileLookup::Profile(Profile::Pint)
189 );
190 assert_eq!(
191 Profile::for_specification_id(Profile::PEPPOL_BIS3_PREFIX),
192 ProfileLookup::Profile(Profile::PeppolBis3)
193 );
194 assert_eq!(
195 Profile::for_specification_id("urn:cen.eu:en16931:2017"),
196 ProfileLookup::Profile(Profile::En16931)
197 );
198 assert_eq!(
199 Profile::for_specification_id(
200 "urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0"
201 ),
202 ProfileLookup::Profile(Profile::En16931)
203 );
204 assert_eq!(
205 Profile::for_specification_id("urn:peppol:pint:selfbilling-1@my-1"),
206 ProfileLookup::WrongProcess
207 );
208 assert_eq!(
209 Profile::for_specification_id(
210 "urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:selfbilling:3.0"
211 ),
212 ProfileLookup::WrongProcess
213 );
214 assert_eq!(
215 Profile::for_specification_id("urn:example:painting"),
216 ProfileLookup::Unknown
217 );
218 assert!(!Profile::PintMy.is_conformant_cius());
219 assert!(Profile::PeppolBis3.is_conformant_cius());
220 assert_eq!(
221 Profile::PeppolBis3.process_id(),
222 Some("urn:fdc:peppol.eu:2017:poacc:billing:01:1.0")
223 );
224 assert_eq!(Profile::En16931.process_id(), None);
225 assert!(Profile::En16931.edition().is_implemented());
226 assert!(!Edition::En2026.is_implemented());
227 for p in [
228 Profile::En16931,
229 Profile::PeppolBis3,
230 Profile::Pint,
231 Profile::PintMy,
232 Profile::Unknown,
233 ] {
234 assert_eq!(p.edition(), Edition::En2017A1);
235 assert!(p.edition().is_implemented());
236 }
237 }
238
239 #[test]
240 fn siblings_sst() {
241 assert!(!Profile::PeppolBis3.allows(TaxSystem::Sst));
242 assert!(Profile::PintMy.allows(TaxSystem::Sst));
243 }
244}