automapper-validation 0.28.2

AHB condition expression parsing, evaluation, and EDIFACT validation
Documentation
// <auto-generated>
// Generated by automapper-generator generate-conditions
// AHB: xml-migs-and-ahbs/FV2504/ORDCHG_AHB_1_0a_20241001.xml
// Generated: 2026-03-12T10:14:16Z
// </auto-generated>

// LLM-produced bodies aren't idiomatic Rust — silence stylistic lints at
// file scope so `cargo clippy -D warnings` stays green. Correctness,
// suspicious, and perf categories stay on so real bugs still surface.
#![allow(clippy::style, clippy::complexity)]

#[allow(unused_imports)]
use crate::eval::format_validators::*;
use crate::eval::{ConditionEvaluator, ConditionResult, EvaluationContext};

/// Generated condition evaluator for ORDCHG FV2504.
pub struct OrdchgConditionEvaluatorFV2504 {
    // External condition IDs that require runtime context.
    external_conditions: std::collections::HashSet<u32>,
}

impl Default for OrdchgConditionEvaluatorFV2504 {
    fn default() -> Self {
        let mut external_conditions = std::collections::HashSet::new();
        external_conditions.insert(1);
        Self {
            external_conditions,
        }
    }
}

impl ConditionEvaluator for OrdchgConditionEvaluatorFV2504 {
    fn message_type(&self) -> &str {
        "ORDCHG"
    }

    fn format_version(&self) -> &str {
        "FV2504"
    }

    fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
        match condition {
            1 => self.evaluate_1(ctx),
            2 => self.evaluate_2(ctx),
            3 => self.evaluate_3(ctx),
            4 => self.evaluate_4(ctx),
            5 => self.evaluate_5(ctx),
            494 => self.evaluate_494(ctx),
            500 => self.evaluate_500(ctx),
            501 => self.evaluate_501(ctx),
            502 => self.evaluate_502(ctx),
            503 => self.evaluate_503(ctx),
            931 => self.evaluate_931(ctx),
            939 => self.evaluate_939(ctx),
            940 => self.evaluate_940(ctx),
            _ => ConditionResult::Unknown,
        }
    }

    fn is_external(&self, condition: u32) -> bool {
        self.external_conditions.contains(&condition)
    }
    fn is_known(&self, condition: u32) -> bool {
        matches!(
            condition,
            1 | 2 | 3 | 4 | 5 | 494 | 500 | 501 | 502 | 503 | 931 | 939 | 940
        )
    }
}

impl OrdchgConditionEvaluatorFV2504 {
    /// [1] MP-ID nur aus Sparte Strom
    /// EXTERNAL: Requires context from outside the message.
    fn evaluate_1(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.external.evaluate("mp_id_only_strom")
    }

    /// [2] Wenn BGM+Z51 (Sperrung) vorhanden
    fn evaluate_2(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.has_qualifier("BGM", 0, "Z51")
    }

    /// [3] Wenn BGM+Z52 (Entsperrung) vorhanden
    fn evaluate_3(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.has_qualifier("BGM", 0, "Z52")
    }

    /// [4] wenn im DE3155 in demselben COM der Code EM vorhanden ist
    fn evaluate_4(&self, ctx: &EvaluationContext) -> ConditionResult {
        let coms = ctx.find_segments("COM");
        ConditionResult::from(coms.iter().any(|com| {
            com.elements
                .first()
                .and_then(|e| e.get(1))
                .is_some_and(|v| v == "EM")
        }))
    }

    /// [5] wenn im DE3155 in demselben COM der Code TE / FX / AJ / AL vorhanden ist
    fn evaluate_5(&self, ctx: &EvaluationContext) -> ConditionResult {
        let coms = ctx.find_segments("COM");
        ConditionResult::from(coms.iter().any(|com| {
            com.elements
                .first()
                .and_then(|e| e.get(1))
                .is_some_and(|v| matches!(v.as_str(), "TE" | "FX" | "AJ" | "AL"))
        }))
    }

    /// [494] Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument erstellt wurde, oder ein Zeitpunkt, der davor liegt.
    fn evaluate_494(&self, ctx: &EvaluationContext) -> ConditionResult {
        // HAND-EDITED: subject is the current DTM's DE2380 (via ctx.resolved_value),
        // compared with the message date (DTM+137). Lexicographic compare is valid
        // for fixed-width CCYYMMDDHHMM+ZZ (format 303).
        let current = match ctx.resolved_value {
            Some(v) if !v.is_empty() => v,
            _ => return ConditionResult::Unknown,
        };
        let threshold = match ctx
            .find_segments_with_qualifier("DTM", 0, "137")
            .first()
            .and_then(|s| s.elements.first())
            .and_then(|e| e.get(1))
        {
            Some(v) if !v.is_empty() => v.clone(),
            _ => return ConditionResult::Unknown,
        };
        ConditionResult::from(current <= threshold.as_str())
    }

    /// [500] Hinweis: Dokumentennummer aus BGM DE1004 der ORDERS
    fn evaluate_500(&self, _ctx: &EvaluationContext) -> ConditionResult {
        // Hinweis: Dokumentennummer aus BGM DE1004 der ORDERS — informational note, always applies
        ConditionResult::True
    }

    /// [501] Hinweis: Wert aus BGM+Z33 DE1004 der IFTSTA mit der die Information über den Entsperrauftrag übermittelt wurde
    fn evaluate_501(&self, _ctx: &EvaluationContext) -> ConditionResult {
        // Hinweis: Wert aus BGM+Z33 DE1004 der IFTSTA mit der die Information über den Entsperrauftrag übermittelt wurde — informational note, always applies
        ConditionResult::True
    }

    /// [502] Hinweis: Vorgangsnummer aus CNI DE1490 der IFTSTA mit BGM+Z33 mit der die Information über den Entsperrauftrag übermittelt wurde
    fn evaluate_502(&self, _ctx: &EvaluationContext) -> ConditionResult {
        // Hinweis: Vorgangsnummer aus CNI DE1490 der IFTSTA mit BGM+Z33 mit der die Information über den Entsperrauftrag übermittelt wurde — informational note, always applies
        ConditionResult::True
    }

    /// [503] Hinweis: Es darf nur eine Information im DE3148 übermittelt werden
    fn evaluate_503(&self, _ctx: &EvaluationContext) -> ConditionResult {
        // Hinweis: Es darf nur eine Information im DE3148 übermittelt werden — informational note, always applies
        ConditionResult::True
    }

    /// [931] Format: ZZZ = +00
    // REVIEW: Format constraint: ZZZ (timezone) must be '+00' (UTC). DTM elements[0][1] contains the datetime value including timezone suffix. Checks all DTM segments to ensure their datetime values end with '+00'. Returns Unknown when no DTM segments present, False if any DTM violates the constraint. (medium confidence)
    fn evaluate_931(&self, ctx: &EvaluationContext) -> ConditionResult {
        let dtms = ctx.find_segments("DTM");
        if dtms.is_empty() {
            return ConditionResult::Unknown;
        }
        for dtm in &dtms {
            if let Some(value) = dtm.elements.first().and_then(|e| e.get(1)) {
                if !value.is_empty() && !value.ends_with("+00") {
                    return ConditionResult::False;
                }
            }
        }
        ConditionResult::True
    }

    /// [939] Format: Die Zeichenkette muss die Zeichen @ und . enthalten
    // REVIEW: Format check: the email address string in COM DE3148 (elements[0][0]) when DE3155 (elements[0][1]) is 'EM' must contain both '@' and '.'. Returns Unknown if no EM-typed COM segment is found. (medium confidence)
    fn evaluate_939(&self, ctx: &EvaluationContext) -> ConditionResult {
        let coms = ctx.find_segments("COM");
        for com in &coms {
            let is_email = com
                .elements
                .first()
                .and_then(|e| e.get(1))
                .is_some_and(|v| v == "EM");
            if is_email {
                let value = com
                    .elements
                    .first()
                    .and_then(|e| e.first())
                    .cloned()
                    .unwrap_or_default();
                return ConditionResult::from(value.contains('@') && value.contains('.'));
            }
        }
        ConditionResult::Unknown
    }

    /// [940] Format: Die Zeichenkette muss mit dem Zeichen + beginnen und danach dürfen nur noch Ziffern folgen
    // REVIEW: Format check: the phone/fax number string in COM DE3148 (elements[0][0]) when DE3155 (elements[0][1]) is TE/FX/AJ/AL must start with '+' followed by digits only. Returns Unknown if no phone-typed COM segment is found. (medium confidence)
    fn evaluate_940(&self, ctx: &EvaluationContext) -> ConditionResult {
        let coms = ctx.find_segments("COM");
        for com in &coms {
            let is_phone = com
                .elements
                .first()
                .and_then(|e| e.get(1))
                .is_some_and(|v| matches!(v.as_str(), "TE" | "FX" | "AJ" | "AL"));
            if is_phone {
                let value = com
                    .elements
                    .first()
                    .and_then(|e| e.first())
                    .cloned()
                    .unwrap_or_default();
                let mut chars = value.chars();
                let starts_with_plus = chars.next() == Some('+');
                let rest_all_digits = chars.all(|c| c.is_ascii_digit());
                return ConditionResult::from(starts_with_plus && rest_all_digits);
            }
        }
        ConditionResult::Unknown
    }
}