use crate::validation::profile::{Profile, ProfileMarker, Restriction, Underlies, terms as t};
use crate::validation::rules::peppol;
pub static EN16931: Profile = Profile {
id: "EN 16931",
edition: crate::Edition::En2017A1,
specification_id: "urn:cen.eu:en16931:2017",
underlying: &[],
restrictions: &[],
extra_rules: &[],
extensions: &[],
suppressed: &[],
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct En16931;
impl ProfileMarker for En16931 {
const PROFILE: &'static Profile = &EN16931;
}
static XR_TYPE_CODES: &[&str] = &["326", "380", "384", "389", "381", "875", "876", "877"];
static XR_SPEC_IDS: &[&str] = &[
"urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0",
"urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0\
#conformant#urn:xeinkauf.de:kosit:extension:xrechnung_3.0",
"urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0\
#compliant#urn:xeinkauf.de:kosit:xrechnung:cvd_0.9",
];
pub static XRECHNUNG: Profile = Profile {
id: "XRechnung 3.0",
edition: crate::Edition::En2017A1,
specification_id: XR_SPEC_IDS[0],
underlying: &["EN 16931", "Peppol BIS Billing 3.0"],
restrictions: &[
Restriction::Mandatory {
id: "BR-DE-1",
term: &t::PAYMENT_INSTRUCTIONS,
},
Restriction::Mandatory {
id: "BR-DE-3",
term: &t::SELLER_CITY,
},
Restriction::Mandatory {
id: "BR-DE-4",
term: &t::SELLER_POST_CODE,
},
Restriction::Mandatory {
id: "BR-DE-5",
term: &t::SELLER_CONTACT_POINT,
},
Restriction::Mandatory {
id: "BR-DE-6",
term: &t::SELLER_CONTACT_PHONE,
},
Restriction::Mandatory {
id: "BR-DE-7",
term: &t::SELLER_CONTACT_EMAIL,
},
Restriction::Mandatory {
id: "BR-DE-8",
term: &t::BUYER_CITY,
},
Restriction::Mandatory {
id: "BR-DE-9",
term: &t::BUYER_POST_CODE,
},
Restriction::Mandatory {
id: "BR-DE-14",
term: &t::VAT_RATE,
},
Restriction::Mandatory {
id: "BR-DE-15",
term: &t::BUYER_REFERENCE,
},
Restriction::CodeValues {
id: "BR-DE-17",
term: &t::TYPE_CODE,
allowed: XR_TYPE_CODES,
},
Restriction::CodeValues {
id: "BR-DE-21",
term: &t::SPECIFICATION_ID,
allowed: XR_SPEC_IDS,
},
],
extra_rules: XR_EXTRA,
extensions: &[],
suppressed: &[],
};
static XR_EXTRA: &[&crate::validation::Rule] = &const {
let x = crate::validation::rules::xrechnung::ALL;
let p = peppol::FOR_XRECHNUNG;
let mut out = [x[0]; 43];
let mut i = 0;
while i < x.len() {
out[i] = x[i];
i += 1;
}
let mut j = 0;
while j < p.len() {
out[x.len() + j] = p[j];
j += 1;
}
assert!(x.len() + p.len() == 43);
out
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct XRechnung;
impl ProfileMarker for XRechnung {
const PROFILE: &'static Profile = &XRECHNUNG;
}
impl Underlies<XRechnung> for En16931 {}
pub(crate) const PEPPOL_SPEC_ID: &str =
"urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0";
pub static PEPPOL_BIS_3: Profile = Profile {
id: "Peppol BIS Billing 3.0",
edition: crate::Edition::En2017A1,
specification_id: PEPPOL_SPEC_ID,
underlying: &["EN 16931"],
restrictions: &[],
extra_rules: crate::validation::rules::peppol::ALL,
extensions: &[],
suppressed: &[],
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PeppolBis3;
impl ProfileMarker for PeppolBis3 {
const PROFILE: &'static Profile = &PEPPOL_BIS_3;
}
impl Underlies<PeppolBis3> for En16931 {}
static XR_CVD_EXTRA: &[&crate::validation::Rule] = &const {
let x = XR_EXTRA;
let c = crate::validation::rules::xrechnung::cvd::ALL;
let mut out = [x[0]; 51];
let mut i = 0;
while i < x.len() {
out[i] = x[i];
i += 1;
}
let mut j = 0;
while j < c.len() {
out[x.len() + j] = c[j];
j += 1;
}
assert!(x.len() + c.len() == 51);
out
};
pub static XRECHNUNG_CVD: Profile = Profile {
id: "XRechnung 3.0 CVD",
edition: crate::Edition::En2017A1,
specification_id: XR_SPEC_IDS[2],
underlying: &["EN 16931", "XRechnung 3.0"],
restrictions: XRECHNUNG.restrictions,
extra_rules: XR_CVD_EXTRA,
extensions: &[],
suppressed: &["BR-CL-13"],
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct XRechnungCvd;
impl ProfileMarker for XRechnungCvd {
const PROFILE: &'static Profile = &XRECHNUNG_CVD;
}
static XR_EXT_EXTRA: &[&crate::validation::Rule] = &const {
let x = XR_EXTRA;
let e = crate::validation::rules::xrechnung::extension::ALL;
let mut out = [x[0]; 57];
let mut i = 0;
while i < x.len() {
out[i] = x[i];
i += 1;
}
let mut j = 0;
while j < e.len() {
out[x.len() + j] = e[j];
j += 1;
}
assert!(x.len() + e.len() == 57);
out
};
pub static XRECHNUNG_EXTENSION: Profile = Profile {
id: "XRechnung 3.0 Extension",
edition: crate::Edition::En2017A1,
specification_id: XR_SPEC_IDS[1],
underlying: &["XRechnung 3.0"],
restrictions: XRECHNUNG.restrictions,
extra_rules: XR_EXT_EXTRA,
extensions: &[
crate::extensions::SUB_INVOICE_LINES,
crate::extensions::THIRD_PARTY_PAYMENTS,
],
suppressed: &[
"BR-CO-16",
"PEPPOL-EN16931-CL001",
"BR-CL-10",
"BR-CL-11",
"BR-CL-25",
"BR-CL-26",
],
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct XRechnungExtension;
impl ProfileMarker for XRechnungExtension {
const PROFILE: &'static Profile = &XRECHNUNG_EXTENSION;
}
pub static ALL: &[&Profile] = &[
&EN16931,
&XRECHNUNG,
&XRECHNUNG_CVD,
&XRECHNUNG_EXTENSION,
&PEPPOL_BIS_3,
];
#[must_use]
pub fn for_specification_id(id: &str) -> Option<&'static Profile> {
ALL.iter()
.copied()
.find(|p| p.specification_id == id)
.or_else(|| {
id.starts_with(XR_SPEC_IDS[0]).then_some(&XRECHNUNG)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn profiles_are_siblings_not_a_scale() {
let peppol_invoice_codes = |c: &str| {
let inv = crate::Invoice {
business_process: Some("urn:fdc:peppol.eu:2017:poacc:billing:01:1.0".to_owned()),
type_code: Some(crate::invoice::Code::new(c)),
..Default::default()
};
PEPPOL_BIS_3.validate(&inv).has("PEPPOL-EN16931-P0100")
};
assert!(XR_TYPE_CODES.contains(&"389"));
assert!(peppol_invoice_codes("389"), "Peppol rejects 389");
assert!(!peppol_invoice_codes("386"), "Peppol accepts 386");
assert!(!XR_TYPE_CODES.contains(&"386"));
}
#[test]
fn a_document_selects_its_own_profile_through_bt_24() {
assert_eq!(
for_specification_id(XRECHNUNG.specification_id).map(|p| p.id),
Some("XRechnung 3.0")
);
assert_eq!(
for_specification_id(XR_SPEC_IDS[1]).map(|p| p.id),
Some("XRechnung 3.0 Extension")
);
assert_eq!(
for_specification_id(XR_SPEC_IDS[2]).map(|p| p.id),
Some("XRechnung 3.0 CVD")
);
assert_eq!(
for_specification_id(&format!("{}#conformant#urn:nope", XR_SPEC_IDS[0])).map(|p| p.id),
Some("XRechnung 3.0")
);
assert_eq!(
for_specification_id(EN16931.specification_id).map(|p| p.id),
Some("EN 16931")
);
assert!(for_specification_id("urn:nonsense").is_none());
}
#[test]
fn every_profile_states_what_4_4_2_requires() {
for p in ALL {
assert!(!p.id.is_empty());
assert!(!p.specification_id.is_empty());
assert_eq!(p.underlying.is_empty(), p.id == "EN 16931", "{}", p.id);
assert_eq!(
p.is_conformant_cius(),
p.suppressed.is_empty(),
"{} — conformance must follow suppression",
p.id
);
}
}
#[test]
fn exactly_two_profiles_are_not_conformant_ciuses() {
let not: Vec<&str> = ALL
.iter()
.filter(|p| !p.is_conformant_cius())
.map(|p| p.id)
.collect();
assert_eq!(not, ["XRechnung 3.0 CVD", "XRechnung 3.0 Extension"]);
}
}