Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/fa_3/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ pub mod models;

#[cfg(test)]
mod tests;

pub mod rounding_tests;
225 changes: 150 additions & 75 deletions src/fa_3/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::shared::models::{
TRolaPodmiotuUpowaznionego, TStatusInfoPodatnika, TaxRate,
};
use rust_decimal::Decimal;
use rust_decimal::RoundingStrategy;
use rust_decimal::prelude::ToPrimitive;
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -87,6 +88,7 @@ impl Invoice {
let mut net_13_6_1 = Decimal::ZERO; // 0 domestic
let mut net_13_6_2 = Decimal::ZERO; // 0 intra
let mut net_13_6_3 = Decimal::ZERO; // 0 export
let mut net_13_9 = Decimal::ZERO; // oo

// Ensure invoice lines have sequential positive line numbers required by TNaturalny (MinExclusive > 0)
for (idx, line) in inv.invoice_body.lines.iter_mut().enumerate() {
Expand All @@ -100,7 +102,10 @@ impl Invoice {

let vat = if let Some(rate) = &line.tax_rate {
let net_cents = (net * Decimal::new(100, 0))
.round_dp(0)
.round_dp_with_strategy(
0,
rust_decimal::RoundingStrategy::MidpointAwayFromZero,
)
.to_i128()
.unwrap_or(0);
let vat_cents = (net_cents * rate.basis_points() as i128) / 10000;
Expand Down Expand Up @@ -143,7 +148,10 @@ impl Invoice {
TaxRate::ZeroEX => {
net_13_6_3 += net;
}
TaxRate::Zw | TaxRate::Oo | TaxRate::NpI | TaxRate::NpII => {
TaxRate::Oo => {
net_13_9 += net;
}
TaxRate::Zw | TaxRate::NpI | TaxRate::NpII => {
net_13_1 += net;
tax_14_1 += vat;
}
Expand All @@ -161,78 +169,140 @@ impl Invoice {
}
}

inv.invoice_body.net_total_basic_rate = if net_13_1 != Decimal::ZERO {
Some(net_13_1.round_dp(2))
} else {
None
};
inv.invoice_body.tax_total_basic_rate = if tax_14_1 != Decimal::ZERO {
Some(tax_14_1.round_dp(2))
} else {
None
};

inv.invoice_body.net_total_reduced_rate_1 = if net_13_2 != Decimal::ZERO {
Some(net_13_2.round_dp(2))
} else {
None
};
inv.invoice_body.tax_total_reduced_rate_1 = if tax_14_2 != Decimal::ZERO {
Some(tax_14_2.round_dp(2))
} else {
None
};

inv.invoice_body.net_total_reduced_rate_2 = if net_13_3 != Decimal::ZERO {
Some(net_13_3.round_dp(2))
} else {
None
};
inv.invoice_body.tax_total_reduced_rate_2 = if tax_14_3 != Decimal::ZERO {
Some(tax_14_3.round_dp(2))
} else {
None
};

inv.invoice_body.net_total_taxi = if net_13_4 != Decimal::ZERO {
Some(net_13_4.round_dp(2))
} else {
None
};
inv.invoice_body.tax_total_taxi = if tax_14_4 != Decimal::ZERO {
Some(tax_14_4.round_dp(2))
} else {
None
};

inv.invoice_body.net_total_special = if net_13_5 != Decimal::ZERO {
Some(net_13_5.round_dp(2))
} else {
None
};
inv.invoice_body.tax_total_special = if tax_14_5 != Decimal::ZERO {
Some(tax_14_5.round_dp(2))
} else {
None
};

inv.invoice_body.net_total_0_domestic = if net_13_6_1 != Decimal::ZERO {
Some(net_13_6_1.round_dp(2))
} else {
None
};
inv.invoice_body.net_total_0_intra = if net_13_6_2 != Decimal::ZERO {
Some(net_13_6_2.round_dp(2))
} else {
None
};
inv.invoice_body.net_total_0_export = if net_13_6_3 != Decimal::ZERO {
Some(net_13_6_3.round_dp(2))
} else {
None
};

inv.invoice_body.total_amount = total.round_dp(2);
inv.invoice_body.net_total_basic_rate =
if net_13_1 != Decimal::ZERO {
Some(net_13_1.round_dp_with_strategy(
2,
rust_decimal::RoundingStrategy::MidpointAwayFromZero,
))
} else {
None
};
inv.invoice_body.tax_total_basic_rate =
if tax_14_1 != Decimal::ZERO {
Some(tax_14_1.round_dp_with_strategy(
2,
rust_decimal::RoundingStrategy::MidpointAwayFromZero,
))
} else {
None
};

inv.invoice_body.net_total_reduced_rate_1 =
if net_13_2 != Decimal::ZERO {
Some(net_13_2.round_dp_with_strategy(
2,
rust_decimal::RoundingStrategy::MidpointAwayFromZero,
))
} else {
None
};
inv.invoice_body.tax_total_reduced_rate_1 =
if tax_14_2 != Decimal::ZERO {
Some(tax_14_2.round_dp_with_strategy(
2,
rust_decimal::RoundingStrategy::MidpointAwayFromZero,
))
} else {
None
};

inv.invoice_body.net_total_reduced_rate_2 =
if net_13_3 != Decimal::ZERO {
Some(net_13_3.round_dp_with_strategy(
2,
rust_decimal::RoundingStrategy::MidpointAwayFromZero,
))
} else {
None
};
inv.invoice_body.tax_total_reduced_rate_2 =
if tax_14_3 != Decimal::ZERO {
Some(tax_14_3.round_dp_with_strategy(
2,
rust_decimal::RoundingStrategy::MidpointAwayFromZero,
))
} else {
None
};

inv.invoice_body.net_total_taxi =
if net_13_4 != Decimal::ZERO {
Some(net_13_4.round_dp_with_strategy(
2,
rust_decimal::RoundingStrategy::MidpointAwayFromZero,
))
} else {
None
};
inv.invoice_body.tax_total_taxi =
if tax_14_4 != Decimal::ZERO {
Some(tax_14_4.round_dp_with_strategy(
2,
rust_decimal::RoundingStrategy::MidpointAwayFromZero,
))
} else {
None
};

inv.invoice_body.net_total_special =
if net_13_5 != Decimal::ZERO {
Some(net_13_5.round_dp_with_strategy(
2,
rust_decimal::RoundingStrategy::MidpointAwayFromZero,
))
} else {
None
};
inv.invoice_body.tax_total_special =
if tax_14_5 != Decimal::ZERO {
Some(tax_14_5.round_dp_with_strategy(
2,
rust_decimal::RoundingStrategy::MidpointAwayFromZero,
))
} else {
None
};

inv.invoice_body.net_total_0_domestic =
if net_13_6_1 != Decimal::ZERO {
Some(net_13_6_1.round_dp_with_strategy(
2,
rust_decimal::RoundingStrategy::MidpointAwayFromZero,
))
} else {
None
};
inv.invoice_body.net_total_0_intra =
if net_13_6_2 != Decimal::ZERO {
Some(net_13_6_2.round_dp_with_strategy(
2,
rust_decimal::RoundingStrategy::MidpointAwayFromZero,
))
} else {
None
};
inv.invoice_body.net_total_0_export =
if net_13_6_3 != Decimal::ZERO {
Some(net_13_6_3.round_dp_with_strategy(
2,
rust_decimal::RoundingStrategy::MidpointAwayFromZero,
))
} else {
None
};

inv.invoice_body.net_total_oo =
if net_13_9 != Decimal::ZERO {
Some(net_13_9.round_dp_with_strategy(
2,
rust_decimal::RoundingStrategy::MidpointAwayFromZero,
))
} else {
None
};
inv.invoice_body.total_amount =
total.round_dp_with_strategy(2, rust_decimal::RoundingStrategy::MidpointAwayFromZero);

if inv.invoice_body.invoice_type.is_none() {
inv.invoice_body.invoice_type = Some("VAT".to_string());
Expand Down Expand Up @@ -293,7 +363,8 @@ impl Invoice {

let mut buffer = String::new();
let serializer = quick_xml::se::Serializer::new(&mut buffer);
inv.serialize(serializer).map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
inv.serialize(serializer)
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
let xml_decl = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
let result = format!("{}{}", xml_decl, buffer);
Ok(result)
Expand Down Expand Up @@ -557,6 +628,9 @@ pub struct InvoiceBody {
#[serde(rename = "P_13_6_3")]
#[serde(skip_serializing_if = "Option::is_none")]
pub net_total_0_export: Option<Decimal>,
#[serde(rename = "P_13_9")]
#[serde(skip_serializing_if = "Option::is_none")]
pub net_total_oo: Option<Decimal>,
#[serde(rename = "P_15")]
pub total_amount: Decimal,
#[serde(rename = "Adnotacje")]
Expand Down Expand Up @@ -608,6 +682,7 @@ impl Default for InvoiceBody {
net_total_0_domestic: None,
net_total_0_intra: None,
net_total_0_export: None,
net_total_oo: None,
total_amount: Decimal::ZERO,
invoice_type: None,
annotations: Annotations::default(),
Expand Down
26 changes: 26 additions & 0 deletions src/fa_3/rounding_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#[cfg(test)]
mod rounding_tests {
use rust_decimal::Decimal;
use rust_decimal::RoundingStrategy;
use std::str::FromStr;

#[test]
fn test_midpoint_away_from_zero_2dp() {
let a = Decimal::from_str("1.005").unwrap();
assert_eq!(
a.round_dp_with_strategy(2, RoundingStrategy::MidpointAwayFromZero)
.to_string(),
"1.01"
);
}

#[test]
fn test_midpoint_away_from_zero_zero_dp() {
let a = Decimal::from_str("2.5").unwrap();
assert_eq!(
a.round_dp_with_strategy(0, RoundingStrategy::MidpointAwayFromZero)
.to_string(),
"3"
);
}
}
27 changes: 19 additions & 8 deletions src/fa_rr/builder.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::models::{InvoiceRR, Header, Subject1, Subject2, Footer};
use rust_decimal::Decimal;
use chrono::NaiveDate;
use super::models::{Footer, Header, InvoiceRR, Subject1, Subject2};
use crate::shared::models::{Address, ContactData, CurrencyCode, IdentificationData};
use chrono::NaiveDate;
use rust_decimal::Decimal;

pub struct InvoiceRRBuilder {
invoice: InvoiceRR,
Expand Down Expand Up @@ -123,7 +123,9 @@ impl Subject1Builder {

pub fn build(self) -> Subject1 {
Subject1 {
identification_data: self.identification_data.expect("IdentificationData is required for Subject1"),
identification_data: self
.identification_data
.expect("IdentificationData is required for Subject1"),
address: self.address.expect("Address is required for Subject1"),
correspondence_address: self.correspondence_address,
contact_data: self.contact_data,
Expand Down Expand Up @@ -165,7 +167,9 @@ impl Subject2Builder {

pub fn build(self) -> Subject2 {
Subject2 {
identification_data: self.identification_data.expect("IdentificationData is required for Subject2"),
identification_data: self
.identification_data
.expect("IdentificationData is required for Subject2"),
address: self.address.expect("Address is required for Subject2"),
correspondence_address: self.correspondence_address,
contact_data: self.contact_data,
Expand Down Expand Up @@ -212,10 +216,13 @@ impl AddressBuilder {

pub fn build(self) -> Address {
Address {
country_code: self.country_code
country_code: self
.country_code
.map(crate::shared::models::CountryCode::new)
.expect("Country code is required for Address"),
address_line_1: self.address_line_1.expect("Address line 1 is required for Address"),
address_line_1: self
.address_line_1
.expect("Address line 1 is required for Address"),
address_line_2: self.address_line_2,
gln: self.gln,
}
Expand All @@ -239,7 +246,11 @@ impl FooterBuilder {
pub fn build(self) -> Footer {
use super::models::{Footer, FooterInfo};
Footer {
informacje: self.footer_text.map(|txt| vec![FooterInfo { footer_text: Some(txt) }]),
informacje: self.footer_text.map(|txt| {
vec![FooterInfo {
footer_text: Some(txt),
}]
}),
}
}
}
2 changes: 1 addition & 1 deletion src/fa_rr/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
pub mod models;
pub mod builder;
pub mod models;
pub mod tests;
4 changes: 2 additions & 2 deletions src/fa_rr/models.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::shared::models::{Address, ContactData, CurrencyCode, IdentificationData};
use chrono::Local;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use chrono::Local;
use crate::shared::models::{Address, ContactData, CurrencyCode, IdentificationData};

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename = "Faktura")]
Expand Down
Loading