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