vat_ph 0.1.0

A library for calculating VAT in the Philippines.
Documentation
#![doc = include_str!("../README.md")]
use rust_decimal::{Decimal, dec};

/// Default VAT rate in the Philippines set by the Bureau of Internal Revenue (BIR) stated in RA 12023.
const VAT_RATE: Decimal = dec!(12.0);

/// Represents the result of a VAT computation, containing net, VAT, and gross amounts.
pub struct Vat {
    /// The net amount before VAT.
    pub net: Decimal,
    /// The VAT amount calculated based on the net amount.
    pub vat: Decimal,
    /// The gross amount after adding VAT to the net amount.
    pub gross: Decimal,
}

/// Enum representing the different types of VAT input that can be provided for computation.
pub enum VatInput {
    /// Represents the net amount before VAT.
    Net(Decimal),
    /// Represents the gross amount after VAT has been added.
    Gross(Decimal),
    /// Represents the VAT amount calculated from the net amount.
    Vat(Decimal),
}

/// Error type for VAT computation errors.
#[derive(Debug, PartialEq)]
pub enum Error {
    /// Indicates that a negative value was provided where it is not allowed (e.g., net, gross, or VAT amounts).
    NegativeInput(String),

    /// Indicates that the VAT rate provided is outside the acceptable range (e.g., negative or greater than 100%).
    RateOutOfBound(String),
}

/// Computes the VAT based on the provided input and optional rate. If you were to pass a custom
/// rate, be sure its value is between 0.0 and 100.0 (inclusive).
///
/// # Example usage:
/// ```
/// use rust_decimal::dec;
/// use vat_ph::compute_vat;
/// use vat_ph::VatInput;
///
/// let result = compute_vat(VatInput::Net(dec!(1000.0)), None);
/// assert!(result.is_ok());
///
/// let vat = result.unwrap();
/// assert_eq!(vat.net, dec!(1000.0));
/// assert_eq!(vat.vat, dec!(120.0));
/// assert_eq!(vat.gross, dec!(1120.0));
/// ```
/// ## Arguments
/// * `input` - The input type that specifies the amount to compute VAT from. It can be either `Net`, `Gross`, or `Vat`.
/// * `rate` - An optional custom VAT rate. If `None`, the default rate of 12% is used. If provided, it must be between 0.0 and 100.0 (inclusive).
/// ## Returns
/// * [`Vat`] - Returns a `Vat` struct containing the net, VAT, and gross amounts if the computation is successful.
pub fn compute_vat(input: VatInput, rate: Option<Decimal>) -> Result<Vat, Error> {
    let vat_rate = rate.unwrap_or(VAT_RATE) / dec!(100.0);

    if vat_rate < dec!(0.0) {
        return Err(Error::RateOutOfBound(
            "VAT rate cannot be negative".to_string(),
        ));
    }

    if vat_rate > dec!(1.0) {
        return Err(Error::RateOutOfBound(
            "VAT rate cannot exceed 100%".to_string(),
        ));
    }

    match input {
        VatInput::Net(net) => {
            if net < dec!(0.0) {
                return Err(Error::NegativeInput(
                    "Net amount cannot be negative".to_string(),
                ));
            }

            let vat = net * vat_rate;
            Ok(Vat {
                net,
                vat,
                gross: net + vat,
            })
        }
        VatInput::Gross(gross) => {
            if gross < dec!(0.0) {
                return Err(Error::NegativeInput(
                    "Gross amount cannot be negative".to_string(),
                ));
            }

            let net = gross / (dec!(1.0) + vat_rate);
            Ok(Vat {
                net,
                vat: gross - net,
                gross,
            })
        }
        VatInput::Vat(vat) => {
            if vat < dec!(0.0) {
                return Err(Error::NegativeInput(
                    "VAT amount cannot be negative".to_string(),
                ));
            }

            let net = vat / vat_rate;
            Ok(Vat {
                net,
                vat,
                gross: net + vat,
            })
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use rust_decimal::{Decimal, dec};

    // Helper function to round to 2 decimal places for consistency
    fn round_to_2(value: Decimal) -> Decimal {
        value.round_dp(2)
    }

    // Test correct VAT calculations with default rate (12%)
    #[test]
    fn test_compute_vat_net_default_rate() {
        let result = compute_vat(VatInput::Net(dec!(1000.0)), None).unwrap();
        assert_eq!(round_to_2(result.net), dec!(1000.0));
        assert_eq!(round_to_2(result.vat), dec!(120.0)); // 1000 * 0.12
        assert_eq!(round_to_2(result.gross), dec!(1120.0)); // 1000 + 120
    }

    #[test]
    fn test_compute_vat_gross_default_rate() {
        let result = compute_vat(VatInput::Gross(dec!(1120.0)), None).unwrap();
        assert_eq!(round_to_2(result.net), dec!(1000.0)); // 1120 / 1.12
        assert_eq!(round_to_2(result.vat), dec!(120.0)); // 1120 - 1000
        assert_eq!(round_to_2(result.gross), dec!(1120.0));
    }

    #[test]
    fn test_compute_vat_vat_default_rate() {
        let result = compute_vat(VatInput::Vat(dec!(120.0)), None).unwrap();
        assert_eq!(round_to_2(result.net), dec!(1000.0)); // 120 / 0.12
        assert_eq!(round_to_2(result.vat), dec!(120.0));
        assert_eq!(round_to_2(result.gross), dec!(1120.0)); // 1000 + 120
    }

    // Test correct VAT calculations with custom rate (10%)
    #[test]
    fn test_compute_vat_net_custom_rate() {
        let result = compute_vat(VatInput::Net(dec!(1000.0)), Some(dec!(10.0))).unwrap();
        assert_eq!(round_to_2(result.net), dec!(1000.0));
        assert_eq!(round_to_2(result.vat), dec!(100.0)); // 1000 * 0.10
        assert_eq!(round_to_2(result.gross), dec!(1100.0)); // 1000 + 100
    }

    #[test]
    fn test_compute_vat_gross_custom_rate() {
        let result = compute_vat(VatInput::Gross(dec!(1100.0)), Some(dec!(10.0))).unwrap();
        assert_eq!(round_to_2(result.net), dec!(1000.0)); // 1100 / 1.10
        assert_eq!(round_to_2(result.vat), dec!(100.0)); // 1100 - 1000
        assert_eq!(round_to_2(result.gross), dec!(1100.0));
    }

    #[test]
    fn test_compute_vat_vat_custom_rate() {
        let result = compute_vat(VatInput::Vat(dec!(100.0)), Some(dec!(10.0))).unwrap();
        assert_eq!(round_to_2(result.net), dec!(1000.0)); // 100 / 0.10
        assert_eq!(round_to_2(result.vat), dec!(100.0));
        assert_eq!(round_to_2(result.gross), dec!(1100.0)); // 1000 + 100
    }

    // Test incorrect VAT calculations (ensure they don't match wrong values)
    #[test]
    fn test_compute_vat_net_incorrect() {
        let result = compute_vat(VatInput::Net(dec!(1000.0)), None).unwrap();
        assert_ne!(round_to_2(result.vat), dec!(100.0)); // Wrong VAT (should be 120)
        assert_ne!(round_to_2(result.gross), dec!(1000.0)); // Wrong gross (should be 1120)
    }

    #[test]
    fn test_compute_vat_gross_incorrect() {
        let result = compute_vat(VatInput::Gross(dec!(1120.0)), None).unwrap();
        assert_ne!(round_to_2(result.net), dec!(1120.0)); // Wrong net (should be 1000)
        assert_ne!(round_to_2(result.vat), dec!(100.0)); // Wrong VAT (should be 120)
    }

    #[test]
    fn test_compute_vat_vat_incorrect() {
        let result = compute_vat(VatInput::Vat(dec!(120.0)), None).unwrap();
        assert_ne!(round_to_2(result.net), dec!(120.0)); // Wrong net (should be 1000)
        assert_ne!(round_to_2(result.gross), dec!(100.0)); // Wrong gross (should be 1120)
    }

    // Test edge cases: zero input
    #[test]
    fn test_compute_vat_zero_net() {
        let result = compute_vat(VatInput::Net(dec!(0.0)), None).unwrap();
        assert_eq!(round_to_2(result.net), dec!(0.0));
        assert_eq!(round_to_2(result.vat), dec!(0.0)); // 0 * 0.12
        assert_eq!(round_to_2(result.gross), dec!(0.0)); // 0 + 0
    }

    #[test]
    fn test_compute_vat_zero_gross() {
        let result = compute_vat(VatInput::Gross(dec!(0.0)), None).unwrap();
        assert_eq!(round_to_2(result.net), dec!(0.0)); // 0 / 1.12
        assert_eq!(round_to_2(result.vat), dec!(0.0)); // 0 - 0
        assert_eq!(round_to_2(result.gross), dec!(0.0));
    }

    #[test]
    fn test_compute_vat_zero_vat() {
        let result = compute_vat(VatInput::Vat(dec!(0.0)), None).unwrap();
        assert_eq!(round_to_2(result.net), dec!(0.0)); // 0 / 0.12
        assert_eq!(round_to_2(result.vat), dec!(0.0));
        assert_eq!(round_to_2(result.gross), dec!(0.0)); // 0 + 0
    }

    // Test edge cases: negative input
    #[test]
    fn test_compute_vat_negative_net() {
        let result = compute_vat(VatInput::Net(dec!(-1000.0)), None);
        assert!(matches!(result, Err(Error::NegativeInput(_))));
    }

    #[test]
    fn test_compute_vat_negative_gross() {
        let result = compute_vat(VatInput::Gross(dec!(-1120.0)), None);
        assert!(matches!(result, Err(Error::NegativeInput(_))));
    }

    #[test]
    fn test_compute_vat_negative_vat() {
        let result = compute_vat(VatInput::Vat(dec!(-120.0)), None);
        assert!(matches!(result, Err(Error::NegativeInput(_))));
    }

    // Test edge cases: invalid rates
    #[test]
    fn test_compute_vat_negative_rate() {
        let result = compute_vat(VatInput::Net(dec!(1000.0)), Some(dec!(-12.0)));
        assert!(matches!(result, Err(Error::RateOutOfBound(_))));
    }

    #[test]
    fn test_compute_vat_excessive_rate() {
        let result = compute_vat(VatInput::Net(dec!(1000.0)), Some(dec!(150.0)));
        assert!(matches!(result, Err(Error::RateOutOfBound(_))));
    }

    // Test edge case: zero rate (e.g., zero-rated VAT in Philippines)
    #[test]
    fn test_compute_vat_zero_rate() {
        let result = compute_vat(VatInput::Net(dec!(1000.0)), Some(dec!(0.0))).unwrap();
        assert_eq!(round_to_2(result.net), dec!(1000.0));
        assert_eq!(round_to_2(result.vat), dec!(0.0)); // 1000 * 0.0
        assert_eq!(round_to_2(result.gross), dec!(1000.0)); // 1000 + 0
    }
}