use crate::model::ViolationDetails;
fn get_i64(value: &serde_json::Value, key: &str) -> i64 {
value.get(key).and_then(|v| v.as_i64()).unwrap_or(0)
}
fn sum_taxes(taxes: &serde_json::Value) -> i64 {
if let Some(taxes_array) = taxes.as_array() {
taxes_array.iter().map(|tax| get_i64(tax, "amount")).sum()
} else {
0
}
}
fn sum_adjustments(adjustments: &serde_json::Value) -> i64 {
if let Some(adjustments_array) = adjustments.as_array() {
adjustments_array
.iter()
.map(|adjustment| get_i64(adjustment, "amount"))
.sum()
} else {
0
}
}
pub fn header_total_should_be_sum_of_all_line_items_taxes_and_adjustments(
data: &serde_json::Value,
) -> Option<ViolationDetails> {
let mut line_items_total = 0i64;
let mut taxes_total = 0i64;
let mut adjustments_total = 0i64;
let header_total = data
.get("header")
.and_then(|header| header.get("total"))
.and_then(|total| total.as_i64())
.unwrap_or(0);
let itemization = data.get("itemization")?;
if let Some(general) = itemization.get("general") {
if let Some(items) = general.get("items").and_then(|i| i.as_array()) {
for item in items {
line_items_total += get_i64(item, "amount");
if let Some(taxes) = item.get("taxes") {
taxes_total += sum_taxes(taxes);
}
if let Some(adjustments) = item.get("adjustments") {
adjustments_total += sum_adjustments(adjustments);
}
}
}
if let Some(invoice_adjustments) = general.get("invoice_level_adjustments") {
adjustments_total += sum_adjustments(invoice_adjustments);
}
}
if let Some(ecommerce) = itemization.get("ecommerce") {
if let Some(shipments) = ecommerce.get("shipments").and_then(|s| s.as_array()) {
for shipment in shipments {
if let Some(items) = shipment.get("items").and_then(|i| i.as_array()) {
for item in items {
line_items_total += get_i64(item, "amount");
if let Some(taxes) = item.get("taxes") {
taxes_total += sum_taxes(taxes);
}
if let Some(adjustments) = item.get("adjustments") {
adjustments_total += sum_adjustments(adjustments);
}
}
}
}
}
if let Some(invoice_items) = ecommerce
.get("invoice_level_line_items")
.and_then(|i| i.as_array())
{
for item in invoice_items {
line_items_total += get_i64(item, "amount");
if let Some(taxes) = item.get("taxes") {
taxes_total += sum_taxes(taxes);
}
if let Some(adjustments) = item.get("adjustments") {
adjustments_total += sum_adjustments(adjustments);
}
}
}
if let Some(invoice_adjustments) = ecommerce.get("invoice_level_adjustments") {
adjustments_total += sum_adjustments(invoice_adjustments);
}
}
if let Some(lodging) = itemization.get("lodging") {
if let Some(items) = lodging.get("items").and_then(|i| i.as_array()) {
for item in items {
line_items_total += get_i64(item, "amount");
if let Some(taxes) = item.get("taxes") {
taxes_total += sum_taxes(taxes);
}
if let Some(adjustments) = item.get("adjustments") {
adjustments_total += sum_adjustments(adjustments);
}
}
}
if let Some(invoice_adjustments) = lodging.get("invoice_level_adjustments") {
adjustments_total += sum_adjustments(invoice_adjustments);
}
}
if let Some(car_rental) = itemization.get("car_rental") {
if let Some(items) = car_rental.get("items").and_then(|i| i.as_array()) {
for item in items {
line_items_total += get_i64(item, "amount");
if let Some(taxes) = item.get("taxes") {
taxes_total += sum_taxes(taxes);
}
if let Some(adjustments) = item.get("adjustments") {
adjustments_total += sum_adjustments(adjustments);
}
}
}
if let Some(invoice_adjustments) = car_rental.get("invoice_level_adjustments") {
adjustments_total += sum_adjustments(invoice_adjustments);
}
}
if let Some(subscription) = itemization.get("subscription") {
if let Some(items) = subscription
.get("subscription_items")
.and_then(|i| i.as_array())
{
for item in items {
line_items_total += get_i64(item, "amount");
if let Some(taxes) = item.get("taxes") {
taxes_total += sum_taxes(taxes);
}
if let Some(adjustments) = item.get("adjustments") {
adjustments_total += sum_adjustments(adjustments);
}
}
}
if let Some(invoice_adjustments) = subscription.get("invoice_level_adjustments") {
adjustments_total += sum_adjustments(invoice_adjustments);
}
}
if let Some(flight) = itemization.get("flight") {
if let Some(tickets) = flight.get("tickets").and_then(|t| t.as_array()) {
for ticket in tickets {
if let Some(fare) = ticket.get("fare").and_then(|f| f.as_i64()) {
line_items_total += fare;
} else {
if let Some(segments) = ticket.get("segments").and_then(|s| s.as_array()) {
for segment in segments {
if let Some(segment_fare) = segment.get("fare").and_then(|f| f.as_i64()) {
line_items_total += segment_fare;
}
if let Some(taxes) = segment.get("taxes") {
taxes_total += sum_taxes(taxes);
}
if let Some(adjustments) = segment.get("adjustments") {
adjustments_total += sum_adjustments(adjustments);
}
}
}
}
if let Some(taxes) = ticket.get("taxes") {
taxes_total += sum_taxes(taxes);
}
}
}
if let Some(invoice_adjustments) = flight.get("invoice_level_adjustments") {
adjustments_total += sum_adjustments(invoice_adjustments);
}
}
if let Some(transit_route) = itemization.get("transit_route") {
if let Some(items) = transit_route
.get("transit_route_items")
.and_then(|i| i.as_array())
{
for item in items {
line_items_total += get_i64(item, "fare");
if let Some(taxes) = item.get("taxes") {
taxes_total += sum_taxes(taxes);
}
if let Some(adjustments) = item.get("adjustments") {
adjustments_total += sum_adjustments(adjustments);
}
}
}
if let Some(invoice_adjustments) = transit_route.get("invoice_level_adjustments") {
adjustments_total += sum_adjustments(invoice_adjustments);
}
}
let calculated_total = line_items_total + taxes_total + adjustments_total;
if calculated_total == header_total {
None
} else {
Some(ViolationDetails {
details: Some(format!(
"line_items_total={}; taxes_total={}; adjustments_total={}; calculated_total={}; header_total={}",
line_items_total, taxes_total, adjustments_total, calculated_total, header_total
)),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_header_total_valid_general() {
let data = json!({
"header": {
"total": 1300
},
"itemization": {
"general": {
"items": [
{
"amount": 1000,
"taxes": [
{"amount": 100, "rate": 0.1, "name": "Sales Tax"}
],
"adjustments": [
{"amount": 50, "adjustment_type": "Discount"}
]
},
{
"amount": 100,
"taxes": [
{"amount": 50, "rate": 0.05, "name": "Service Tax"}
]
}
]
}
}
});
let result = header_total_should_be_sum_of_all_line_items_taxes_and_adjustments(&data);
assert!(result.is_none());
}
#[test]
fn test_header_total_invalid_general() {
let data = json!({
"header": {
"total": 1400
},
"itemization": {
"general": {
"items": [
{
"amount": 1000,
"taxes": [
{"amount": 100, "rate": 0.1, "name": "Sales Tax"}
],
"adjustments": [
{"amount": 50, "adjustment_type": "Discount"}
]
}
]
}
}
});
let result = header_total_should_be_sum_of_all_line_items_taxes_and_adjustments(&data);
assert!(result.is_some());
let violation = result.unwrap();
assert!(violation
.details
.unwrap()
.contains("calculated_total=1150; header_total=1400"));
}
#[test]
fn test_header_total_valid_ecommerce_shipments() {
let data = json!({
"header": {
"total": 1150
},
"itemization": {
"ecommerce": {
"shipments": [
{
"items": [
{
"amount": 1000,
"taxes": [
{"amount": 100, "rate": 0.1, "name": "Sales Tax"}
],
"adjustments": [
{"amount": 50, "adjustment_type": "Shipping"}
]
}
]
}
]
}
}
});
let result = header_total_should_be_sum_of_all_line_items_taxes_and_adjustments(&data);
assert!(result.is_none());
}
#[test]
fn test_header_total_valid_flight_with_ticket_fare() {
let data = json!({
"header": {
"total": 650
},
"itemization": {
"flight": {
"tickets": [
{
"fare": 500,
"taxes": [
{"amount": 150, "rate": null, "name": "Airport Tax"}
]
}
]
}
}
});
let result = header_total_should_be_sum_of_all_line_items_taxes_and_adjustments(&data);
assert!(result.is_none());
}
#[test]
fn test_header_total_valid_flight_with_segment_fares() {
let data = json!({
"header": {
"total": 800
},
"itemization": {
"flight": {
"tickets": [
{
"segments": [
{
"fare": 400,
"taxes": [
{"amount": 100, "rate": null, "name": "Airport Tax"}
]
},
{
"fare": 200,
"adjustments": [
{"amount": 100, "adjustment_type": "Fuel Surcharge"}
]
}
]
}
]
}
}
});
let result = header_total_should_be_sum_of_all_line_items_taxes_and_adjustments(&data);
assert!(result.is_none());
}
#[test]
fn test_header_total_valid_subscription() {
let data = json!({
"header": {
"total": 1150
},
"itemization": {
"subscription": {
"subscription_items": [
{
"amount": 1000,
"taxes": [
{"amount": 100, "rate": 0.1, "name": "VAT"}
],
"adjustments": [
{"amount": 50, "adjustment_type": "Discount"}
]
}
]
}
}
});
let result = header_total_should_be_sum_of_all_line_items_taxes_and_adjustments(&data);
assert!(result.is_none());
}
#[test]
fn test_header_total_valid_transit_route() {
let data = json!({
"header": {
"total": 250
},
"itemization": {
"transit_route": {
"transit_route_items": [
{
"fare": 200,
"taxes": [
{"amount": 30, "rate": 0.15, "name": "Transit Tax"}
],
"adjustments": [
{"amount": 20, "adjustment_type": "Service Fee"}
]
}
]
}
}
});
let result = header_total_should_be_sum_of_all_line_items_taxes_and_adjustments(&data);
assert!(result.is_none());
}
#[test]
fn test_header_total_with_invoice_level_adjustments() {
let data = json!({
"header": {
"total": 1050
},
"itemization": {
"general": {
"items": [
{
"amount": 1000,
"taxes": [
{"amount": 100, "rate": 0.1, "name": "Sales Tax"}
]
}
],
"invoice_level_adjustments": [
{"amount": -50, "adjustment_type": "Discount"}
]
}
}
});
let result = header_total_should_be_sum_of_all_line_items_taxes_and_adjustments(&data);
assert!(result.is_none());
}
#[test]
fn test_header_total_missing_header() {
let data = json!({
"itemization": {
"general": {
"items": [
{"amount": 1000}
]
}
}
});
let result = header_total_should_be_sum_of_all_line_items_taxes_and_adjustments(&data);
assert!(result.is_some());
}
#[test]
fn test_header_total_missing_itemization() {
let data = json!({
"header": {
"total": 1000
}
});
let result = header_total_should_be_sum_of_all_line_items_taxes_and_adjustments(&data);
assert!(result.is_none()); }
}