1pub mod amount;
7pub mod invoice;
8pub mod profile;
9pub mod report;
10pub mod tax;
11pub mod validate;
12
13pub use amount::Amount;
14pub use invoice::{Invoice, Line, Party};
15pub use profile::Profile;
16pub use report::{Finding, Report, Severity};
17pub use tax::{TaxCategory, TaxSystem};
18pub use validate::validate;
19
20#[cfg(test)]
21mod tests {
22 use super::*;
23 use rust_decimal::Decimal;
24
25 fn sst_invoice(profile: Profile) -> Invoice {
26 Invoice {
27 profile,
28 number: "INV-1".into(),
29 currency: "MYR".into(),
30 seller: {
31 let mut p = Party::new("Kedai", "MY");
32 p.tax_id = Some("C12345678901".into());
33 p.id_scheme = Some("TIN".into());
34 p
35 },
36 buyer: Party::new("Pembeli", "MY"),
37 lines: vec![Line {
38 id: "1".into(),
39 name: "Goods".into(),
40 net: Amount::parse("100.00").unwrap(),
41 tax: TaxCategory::sst("SR", Decimal::new(10, 2)),
42 }],
43 tax_total: Amount::parse("10.00").unwrap(),
44 payable: Amount::parse("110.00").unwrap(),
45 }
46 }
47
48 #[test]
49 fn pint_my_accepts_sst() {
50 let report = validate(&sst_invoice(Profile::PintMy));
51 assert!(report.ok(), "{report}");
52 }
53
54 #[test]
55 fn peppol_bis_rejects_sst() {
56 let report = validate(&sst_invoice(Profile::PeppolBis3));
57 assert!(!report.ok());
58 assert!(
59 report.findings.iter().any(|f| f.id == "PINT-TAX"),
60 "{report}"
61 );
62 }
63
64 #[test]
65 fn payable_must_match_net_plus_tax() {
66 let mut inv = sst_invoice(Profile::Pint);
67 inv.payable = Amount::parse("999.00").unwrap();
68 let report = validate(&inv);
69 assert!(report.findings.iter().any(|f| f.id == "BR-CO-16"));
70 }
71}