1use crate::{SwiftField, ValidationError, ValidationResult};
2use serde::{Deserialize, Serialize};
3
4pub const VALID_INSTRUCTION_CODES: &[&str] = &[
6 "CHQB", "HOLD", "INTC", "PHOB", "PHOI", "PHON", "REPA", "SDVA", "TELB", "TELE", "TELI",
7];
8
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
109pub struct Field23E {
110 pub instruction_code: String,
132
133 #[serde(skip_serializing_if = "Option::is_none")]
149 pub additional_info: Option<String>,
150}
151
152impl SwiftField for Field23E {
153 fn parse(value: &str) -> Result<Self, crate::ParseError> {
154 let content = if let Some(stripped) = value.strip_prefix(":23E:") {
155 stripped } else if let Some(stripped) = value.strip_prefix("23E:") {
157 stripped } else {
159 value
160 };
161
162 if content.is_empty() {
163 return Err(crate::ParseError::InvalidFieldFormat {
164 field_tag: "23E".to_string(),
165 message: "Field content cannot be empty after removing tag".to_string(),
166 });
167 }
168
169 if let Some(slash_pos) = content.find('/') {
171 let instruction_code = &content[..slash_pos];
172 let additional_info = &content[slash_pos + 1..];
173
174 Self::new(instruction_code, Some(additional_info.to_string()))
175 } else {
176 Self::new(content, None)
177 }
178 }
179
180 fn to_swift_string(&self) -> String {
181 match &self.additional_info {
182 Some(info) => format!(":23E:{}/{}", self.instruction_code, info),
183 None => format!(":23E:{}", self.instruction_code),
184 }
185 }
186
187 fn validate(&self) -> ValidationResult {
188 let mut errors = Vec::new();
189
190 if self.instruction_code.len() != 4 {
192 errors.push(ValidationError::LengthValidation {
193 field_tag: "23E".to_string(),
194 expected: "4 characters".to_string(),
195 actual: self.instruction_code.len(),
196 });
197 }
198
199 if !self
201 .instruction_code
202 .chars()
203 .all(|c| c.is_alphanumeric() && c.is_ascii())
204 {
205 errors.push(ValidationError::FormatValidation {
206 field_tag: "23E".to_string(),
207 message: "Instruction code must contain only alphanumeric characters".to_string(),
208 });
209 }
210
211 if !VALID_INSTRUCTION_CODES.contains(&self.instruction_code.as_str()) {
213 errors.push(ValidationError::ValueValidation {
214 field_tag: "23E".to_string(),
215 message: format!("Invalid instruction code: {}", self.instruction_code),
216 });
217 }
218
219 if let Some(ref info) = self.additional_info {
221 if info.len() > 30 {
222 errors.push(ValidationError::LengthValidation {
223 field_tag: "23E".to_string(),
224 expected: "max 30 characters".to_string(),
225 actual: info.len(),
226 });
227 }
228
229 if info.is_empty() {
230 errors.push(ValidationError::ValueValidation {
231 field_tag: "23E".to_string(),
232 message: "Additional information cannot be empty if specified".to_string(),
233 });
234 }
235
236 if !info.chars().all(|c| c.is_ascii() && !c.is_control()) {
238 errors.push(ValidationError::FormatValidation {
239 field_tag: "23E".to_string(),
240 message: "Additional information contains invalid characters".to_string(),
241 });
242 }
243 }
244
245 ValidationResult {
246 is_valid: errors.is_empty(),
247 errors,
248 warnings: Vec::new(),
249 }
250 }
251
252 fn format_spec() -> &'static str {
253 "4!c[/30x]"
254 }
255}
256
257impl Field23E {
258 pub fn new(
260 instruction_code: impl Into<String>,
261 additional_info: Option<String>,
262 ) -> crate::Result<Self> {
263 let code = instruction_code.into().trim().to_uppercase();
264
265 if code.is_empty() {
266 return Err(crate::ParseError::InvalidFieldFormat {
267 field_tag: "23E".to_string(),
268 message: "Instruction code cannot be empty".to_string(),
269 });
270 }
271
272 if code.len() != 4 {
273 return Err(crate::ParseError::InvalidFieldFormat {
274 field_tag: "23E".to_string(),
275 message: "Instruction code must be exactly 4 characters".to_string(),
276 });
277 }
278
279 if !code.chars().all(|c| c.is_alphanumeric() && c.is_ascii()) {
281 return Err(crate::ParseError::InvalidFieldFormat {
282 field_tag: "23E".to_string(),
283 message: "Instruction code must contain only alphanumeric characters".to_string(),
284 });
285 }
286
287 if !VALID_INSTRUCTION_CODES.contains(&code.as_str()) {
289 return Err(crate::ParseError::InvalidFieldFormat {
290 field_tag: "23E".to_string(),
291 message: format!("Invalid instruction code: {}", code),
292 });
293 }
294
295 if let Some(ref info) = additional_info {
297 if info.len() > 30 {
298 return Err(crate::ParseError::InvalidFieldFormat {
299 field_tag: "23E".to_string(),
300 message: "Additional information too long (max 30 characters)".to_string(),
301 });
302 }
303
304 if info.is_empty() {
305 return Err(crate::ParseError::InvalidFieldFormat {
306 field_tag: "23E".to_string(),
307 message: "Additional information cannot be empty if specified".to_string(),
308 });
309 }
310
311 if !info.chars().all(|c| c.is_ascii() && !c.is_control()) {
313 return Err(crate::ParseError::InvalidFieldFormat {
314 field_tag: "23E".to_string(),
315 message: "Additional information contains invalid characters".to_string(),
316 });
317 }
318 }
319
320 Ok(Field23E {
321 instruction_code: code,
322 additional_info,
323 })
324 }
325
326 pub fn code(&self) -> &str {
341 &self.instruction_code
342 }
343
344 pub fn additional_info(&self) -> Option<&str> {
359 self.additional_info.as_deref()
360 }
361
362 pub fn is_valid_code(&self) -> bool {
376 VALID_INSTRUCTION_CODES.contains(&self.instruction_code.as_str())
377 }
378
379 pub fn is_communication_instruction(&self) -> bool {
397 matches!(
398 self.instruction_code.as_str(),
399 "PHOB" | "PHOI" | "PHON" | "TELB" | "TELE" | "TELI"
400 )
401 }
402
403 pub fn is_timing_instruction(&self) -> bool {
421 matches!(self.instruction_code.as_str(), "SDVA" | "HOLD")
422 }
423
424 pub fn is_payment_method_instruction(&self) -> bool {
442 matches!(self.instruction_code.as_str(), "CHQB" | "REPA")
443 }
444
445 pub fn requires_manual_intervention(&self) -> bool {
463 matches!(
464 self.instruction_code.as_str(),
465 "HOLD" | "PHOB" | "PHOI" | "PHON" | "TELB" | "TELE" | "TELI" | "CHQB"
466 )
467 }
468
469 pub fn priority_impact(&self) -> i8 {
487 match self.instruction_code.as_str() {
488 "SDVA" => 2, "PHON" | "TELE" => 1, "INTC" => 0, "PHOB" | "PHOI" | "TELB" | "TELI" => -1, "CHQB" | "REPA" => -1, "HOLD" => -2, _ => 0, }
496 }
497
498 pub fn description(&self) -> &'static str {
513 match self.instruction_code.as_str() {
514 "CHQB" => "Pay by cheque/banker's draft - Physical payment instrument required",
515 "HOLD" => "Hold payment until further notice - Suspend processing pending instructions",
516 "INTC" => "Intracompany payment - Internal company transfer between related entities",
517 "PHOB" => {
518 "Phone ordering customer before payment - Contact beneficiary before processing"
519 }
520 "PHOI" => "Phone intermediary bank before payment - Contact intermediary institution",
521 "PHON" => {
522 "Phone all parties before payment - Contact all relevant parties for verification"
523 }
524 "REPA" => "Reimbursement payment - Payment for reimbursement or expense purposes",
525 "SDVA" => "Same day value - Ensure same-day value dating for the payment",
526 "TELB" => "Telex beneficiary before payment - Send telex notification to beneficiary",
527 "TELE" => "Telex all parties before payment - Send telex notifications to all parties",
528 "TELI" => {
529 "Telex intermediary bank before payment - Send telex to intermediary institution"
530 }
531 _ => "Unknown instruction code - Non-standard or institution-specific instruction",
532 }
533 }
534
535 pub fn instruction_category(&self) -> &'static str {
550 match self.instruction_code.as_str() {
551 "PHOB" | "PHOI" | "PHON" | "TELB" | "TELE" | "TELI" => "Communication",
552 "SDVA" | "HOLD" => "Timing",
553 "CHQB" | "REPA" => "Payment Method",
554 "INTC" => "Internal Transfer",
555 _ => "Other",
556 }
557 }
558
559 pub fn recommends_additional_info(&self) -> bool {
577 matches!(
578 self.instruction_code.as_str(),
579 "HOLD" | "PHOB" | "PHOI" | "PHON" | "TELB" | "TELE" | "TELI" | "REPA"
580 )
581 }
582
583 pub fn validate_with_field_23b(&self, field_23b_code: &str) -> crate::Result<()> {
585 match field_23b_code {
586 "SPRI" => {
587 if !["SDVA", "TELB", "PHOB", "INTC"].contains(&self.instruction_code.as_str()) {
589 return Err(crate::ParseError::InvalidFieldFormat {
590 field_tag: "23E".to_string(),
591 message: format!(
592 "When Field 23B is SPRI, Field 23E can only be SDVA, TELB, PHOB, or INTC. Got: {}",
593 self.instruction_code
594 ),
595 });
596 }
597 }
598 "SSTD" | "SPAY" => {
599 return Err(crate::ParseError::InvalidFieldFormat {
601 field_tag: "23E".to_string(),
602 message: "Field 23E must not be present when Field 23B is SSTD or SPAY"
603 .to_string(),
604 });
605 }
606 _ => {
607 }
609 }
610
611 Ok(())
612 }
613
614 pub fn comprehensive_description(&self) -> String {
629 let base = format!(
630 "{} ({}): {}",
631 self.instruction_code,
632 self.instruction_category(),
633 self.description()
634 );
635
636 if let Some(ref info) = self.additional_info {
637 format!("{} - Additional Info: {}", base, info)
638 } else {
639 base
640 }
641 }
642}
643
644impl std::fmt::Display for Field23E {
645 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
646 match &self.additional_info {
647 Some(info) => write!(f, "{}/{}", self.instruction_code, info),
648 None => write!(f, "{}", self.instruction_code),
649 }
650 }
651}
652
653#[cfg(test)]
654mod tests {
655 use super::*;
656
657 #[test]
658 fn test_field23e_creation_simple() {
659 let field = Field23E::new("CHQB", None).unwrap();
660 assert_eq!(field.instruction_code, "CHQB");
661 assert_eq!(field.additional_info, None);
662 assert_eq!(field.code(), "CHQB");
663 }
664
665 #[test]
666 fn test_field23e_creation_with_info() {
667 let field = Field23E::new("HOLD", Some("COMPLIANCE CHECK".to_string())).unwrap();
668 assert_eq!(field.instruction_code, "HOLD");
669 assert_eq!(field.additional_info, Some("COMPLIANCE CHECK".to_string()));
670 assert_eq!(field.additional_info(), Some("COMPLIANCE CHECK"));
671 }
672
673 #[test]
674 fn test_field23e_parse_simple() {
675 let field = Field23E::parse("INTC").unwrap();
676 assert_eq!(field.instruction_code, "INTC");
677 assert_eq!(field.additional_info, None);
678 }
679
680 #[test]
681 fn test_field23e_parse_with_info() {
682 let field = Field23E::parse("REPA/WEEKLY PAYMENT").unwrap();
683 assert_eq!(field.instruction_code, "REPA");
684 assert_eq!(field.additional_info, Some("WEEKLY PAYMENT".to_string()));
685 }
686
687 #[test]
688 fn test_field23e_parse_with_tag_prefix() {
689 let field = Field23E::parse(":23E:HOLD/INFO").unwrap();
690 assert_eq!(field.instruction_code, "HOLD");
691 assert_eq!(field.additional_info, Some("INFO".to_string()));
692
693 let field = Field23E::parse("23E:SDVA").unwrap();
694 assert_eq!(field.instruction_code, "SDVA");
695 assert_eq!(field.additional_info, None);
696 }
697
698 #[test]
699 fn test_field23e_case_normalization() {
700 let field = Field23E::new("phob", None).unwrap();
701 assert_eq!(field.instruction_code, "PHOB");
702 }
703
704 #[test]
705 fn test_field23e_invalid_code() {
706 let result = Field23E::new("INVL", None); assert!(result.is_err());
708
709 let result = Field23E::new("ABC", None); assert!(result.is_err());
711
712 let result = Field23E::new("ABCDE", None); assert!(result.is_err());
714 }
715
716 #[test]
717 fn test_field23e_invalid_additional_info() {
718 let result = Field23E::new("HOLD", Some("A".repeat(31))); assert!(result.is_err());
720
721 let result = Field23E::new("HOLD", Some("".to_string())); assert!(result.is_err());
723 }
724
725 #[test]
726 fn test_field23e_business_rules() {
727 let field = Field23E::new("SDVA", None).unwrap();
729 assert!(field.validate_with_field_23b("SPRI").is_ok());
730
731 let field = Field23E::new("TELB", None).unwrap();
732 assert!(field.validate_with_field_23b("SPRI").is_ok());
733
734 let field = Field23E::new("CHQB", None).unwrap();
736 assert!(field.validate_with_field_23b("SPRI").is_err());
737
738 let field = Field23E::new("HOLD", None).unwrap();
740 assert!(field.validate_with_field_23b("SSTD").is_err());
741 assert!(field.validate_with_field_23b("SPAY").is_err());
742
743 let field = Field23E::new("CHQB", None).unwrap();
745 assert!(field.validate_with_field_23b("CRED").is_ok());
746 }
747
748 #[test]
749 fn test_field23e_to_swift_string() {
750 let field = Field23E::new("TELI", None).unwrap();
751 assert_eq!(field.to_swift_string(), ":23E:TELI");
752
753 let field = Field23E::new("PHON", Some("CALL BEFORE".to_string())).unwrap();
754 assert_eq!(field.to_swift_string(), ":23E:PHON/CALL BEFORE");
755 }
756
757 #[test]
758 fn test_field23e_validation() {
759 let field = Field23E::new("TELE", None).unwrap();
760 let result = field.validate();
761 assert!(result.is_valid);
762
763 let invalid_field = Field23E {
764 instruction_code: "INVALID".to_string(),
765 additional_info: None,
766 };
767 let result = invalid_field.validate();
768 assert!(!result.is_valid);
769 }
770
771 #[test]
772 fn test_field23e_format_spec() {
773 assert_eq!(Field23E::format_spec(), "4!c[/30x]");
774 }
775
776 #[test]
777 fn test_field23e_display() {
778 let field = Field23E::new("PHOI", None).unwrap();
779 assert_eq!(format!("{}", field), "PHOI");
780
781 let field = Field23E::new("SDVA", Some("SAME DAY".to_string())).unwrap();
782 assert_eq!(format!("{}", field), "SDVA/SAME DAY");
783 }
784
785 #[test]
786 fn test_field23e_descriptions() {
787 let field = Field23E::new("CHQB", None).unwrap();
788 assert_eq!(
789 field.description(),
790 "Pay by cheque/banker's draft - Physical payment instrument required"
791 );
792
793 let field = Field23E::new("HOLD", None).unwrap();
794 assert_eq!(
795 field.description(),
796 "Hold payment until further notice - Suspend processing pending instructions"
797 );
798
799 let field = Field23E::new("SDVA", None).unwrap();
800 assert_eq!(
801 field.description(),
802 "Same day value - Ensure same-day value dating for the payment"
803 );
804 }
805
806 #[test]
807 fn test_field23e_is_valid_code() {
808 let field = Field23E::new("CHQB", None).unwrap();
809 assert!(field.is_valid_code());
810
811 let field = Field23E {
812 instruction_code: "XXXX".to_string(),
813 additional_info: None,
814 };
815 assert!(!field.is_valid_code());
816 }
817
818 #[test]
819 fn test_field23e_communication_instructions() {
820 let communication_codes = ["PHOB", "PHOI", "PHON", "TELB", "TELE", "TELI"];
821 for code in communication_codes {
822 let field = Field23E::new(code, None).unwrap();
823 assert!(
824 field.is_communication_instruction(),
825 "Code {} should be communication instruction",
826 code
827 );
828 assert!(
829 !field.is_timing_instruction(),
830 "Code {} should not be timing instruction",
831 code
832 );
833 assert!(
834 !field.is_payment_method_instruction(),
835 "Code {} should not be payment method instruction",
836 code
837 );
838 }
839
840 let non_communication_codes = ["CHQB", "HOLD", "INTC", "REPA", "SDVA"];
841 for code in non_communication_codes {
842 let field = Field23E::new(code, None).unwrap();
843 assert!(
844 !field.is_communication_instruction(),
845 "Code {} should not be communication instruction",
846 code
847 );
848 }
849 }
850
851 #[test]
852 fn test_field23e_timing_instructions() {
853 let timing_codes = ["SDVA", "HOLD"];
854 for code in timing_codes {
855 let field = Field23E::new(code, None).unwrap();
856 assert!(
857 field.is_timing_instruction(),
858 "Code {} should be timing instruction",
859 code
860 );
861 assert!(
862 !field.is_communication_instruction(),
863 "Code {} should not be communication instruction",
864 code
865 );
866 }
867
868 let non_timing_codes = ["CHQB", "PHOB", "INTC", "REPA"];
869 for code in non_timing_codes {
870 let field = Field23E::new(code, None).unwrap();
871 assert!(
872 !field.is_timing_instruction(),
873 "Code {} should not be timing instruction",
874 code
875 );
876 }
877 }
878
879 #[test]
880 fn test_field23e_payment_method_instructions() {
881 let payment_method_codes = ["CHQB", "REPA"];
882 for code in payment_method_codes {
883 let field = Field23E::new(code, None).unwrap();
884 assert!(
885 field.is_payment_method_instruction(),
886 "Code {} should be payment method instruction",
887 code
888 );
889 assert!(
890 !field.is_communication_instruction(),
891 "Code {} should not be communication instruction",
892 code
893 );
894 assert!(
895 !field.is_timing_instruction(),
896 "Code {} should not be timing instruction",
897 code
898 );
899 }
900
901 let non_payment_method_codes = ["HOLD", "PHOB", "INTC", "SDVA"];
902 for code in non_payment_method_codes {
903 let field = Field23E::new(code, None).unwrap();
904 assert!(
905 !field.is_payment_method_instruction(),
906 "Code {} should not be payment method instruction",
907 code
908 );
909 }
910 }
911
912 #[test]
913 fn test_field23e_manual_intervention_requirements() {
914 let manual_codes = [
915 "HOLD", "PHOB", "PHOI", "PHON", "TELB", "TELE", "TELI", "CHQB",
916 ];
917 for code in manual_codes {
918 let field = Field23E::new(code, None).unwrap();
919 assert!(
920 field.requires_manual_intervention(),
921 "Code {} should require manual intervention",
922 code
923 );
924 }
925
926 let automatic_codes = ["SDVA", "INTC", "REPA"];
927 for code in automatic_codes {
928 let field = Field23E::new(code, None).unwrap();
929 assert!(
930 !field.requires_manual_intervention(),
931 "Code {} should not require manual intervention",
932 code
933 );
934 }
935 }
936
937 #[test]
938 fn test_field23e_priority_impact() {
939 let test_cases = [
940 ("SDVA", 2), ("PHON", 1), ("TELE", 1), ("INTC", 0), ("PHOB", -1), ("PHOI", -1), ("TELB", -1), ("TELI", -1), ("CHQB", -1), ("REPA", -1), ("HOLD", -2), ];
952
953 for (code, expected_impact) in test_cases {
954 let field = Field23E::new(code, None).unwrap();
955 assert_eq!(
956 field.priority_impact(),
957 expected_impact,
958 "Priority impact mismatch for code {}",
959 code
960 );
961 }
962 }
963
964 #[test]
965 fn test_field23e_instruction_categories() {
966 let test_cases = [
967 ("PHOB", "Communication"),
968 ("PHOI", "Communication"),
969 ("PHON", "Communication"),
970 ("TELB", "Communication"),
971 ("TELE", "Communication"),
972 ("TELI", "Communication"),
973 ("SDVA", "Timing"),
974 ("HOLD", "Timing"),
975 ("CHQB", "Payment Method"),
976 ("REPA", "Payment Method"),
977 ("INTC", "Internal Transfer"),
978 ];
979
980 for (code, expected_category) in test_cases {
981 let field = Field23E::new(code, None).unwrap();
982 assert_eq!(
983 field.instruction_category(),
984 expected_category,
985 "Category mismatch for code {}",
986 code
987 );
988 }
989 }
990
991 #[test]
992 fn test_field23e_additional_info_recommendations() {
993 let recommends_info = [
994 "HOLD", "PHOB", "PHOI", "PHON", "TELB", "TELE", "TELI", "REPA",
995 ];
996 for code in recommends_info {
997 let field = Field23E::new(code, None).unwrap();
998 assert!(
999 field.recommends_additional_info(),
1000 "Code {} should recommend additional info",
1001 code
1002 );
1003 }
1004
1005 let no_info_needed = ["SDVA", "INTC", "CHQB"];
1006 for code in no_info_needed {
1007 let field = Field23E::new(code, None).unwrap();
1008 assert!(
1009 !field.recommends_additional_info(),
1010 "Code {} should not recommend additional info",
1011 code
1012 );
1013 }
1014 }
1015
1016 #[test]
1017 fn test_field23e_comprehensive_description() {
1018 let field = Field23E::new("CHQB", None).unwrap();
1020 let desc = field.comprehensive_description();
1021 assert!(desc.contains("CHQB"));
1022 assert!(desc.contains("Payment Method"));
1023 assert!(desc.contains("Physical payment instrument required"));
1024
1025 let field = Field23E::new("HOLD", Some("COMPLIANCE CHECK".to_string())).unwrap();
1027 let desc = field.comprehensive_description();
1028 assert!(desc.contains("HOLD"));
1029 assert!(desc.contains("Timing"));
1030 assert!(desc.contains("Suspend processing pending instructions"));
1031 assert!(desc.contains("Additional Info: COMPLIANCE CHECK"));
1032 }
1033
1034 #[test]
1035 fn test_field23e_enhanced_descriptions() {
1036 let test_cases = [
1037 (
1038 "CHQB",
1039 "Pay by cheque/banker's draft - Physical payment instrument required",
1040 ),
1041 (
1042 "HOLD",
1043 "Hold payment until further notice - Suspend processing pending instructions",
1044 ),
1045 (
1046 "INTC",
1047 "Intracompany payment - Internal company transfer between related entities",
1048 ),
1049 (
1050 "PHOB",
1051 "Phone ordering customer before payment - Contact beneficiary before processing",
1052 ),
1053 (
1054 "PHOI",
1055 "Phone intermediary bank before payment - Contact intermediary institution",
1056 ),
1057 (
1058 "PHON",
1059 "Phone all parties before payment - Contact all relevant parties for verification",
1060 ),
1061 (
1062 "REPA",
1063 "Reimbursement payment - Payment for reimbursement or expense purposes",
1064 ),
1065 (
1066 "SDVA",
1067 "Same day value - Ensure same-day value dating for the payment",
1068 ),
1069 (
1070 "TELB",
1071 "Telex beneficiary before payment - Send telex notification to beneficiary",
1072 ),
1073 (
1074 "TELE",
1075 "Telex all parties before payment - Send telex notifications to all parties",
1076 ),
1077 (
1078 "TELI",
1079 "Telex intermediary bank before payment - Send telex to intermediary institution",
1080 ),
1081 ];
1082
1083 for (code, expected_desc) in test_cases {
1084 let field = Field23E::new(code, None).unwrap();
1085 assert_eq!(
1086 field.description(),
1087 expected_desc,
1088 "Description mismatch for code {}",
1089 code
1090 );
1091 }
1092 }
1093
1094 #[test]
1095 fn test_field23e_business_logic_combinations() {
1096 let sdva = Field23E::new("SDVA", None).unwrap();
1098 assert!(sdva.is_timing_instruction());
1099 assert_eq!(sdva.priority_impact(), 2);
1100 assert!(!sdva.requires_manual_intervention());
1101 assert!(!sdva.recommends_additional_info());
1102 assert_eq!(sdva.instruction_category(), "Timing");
1103
1104 let hold = Field23E::new("HOLD", None).unwrap();
1106 assert!(hold.is_timing_instruction());
1107 assert_eq!(hold.priority_impact(), -2);
1108 assert!(hold.requires_manual_intervention());
1109 assert!(hold.recommends_additional_info());
1110 assert_eq!(hold.instruction_category(), "Timing");
1111
1112 let phon = Field23E::new("PHON", None).unwrap();
1114 assert!(phon.is_communication_instruction());
1115 assert_eq!(phon.priority_impact(), 1);
1116 assert!(phon.requires_manual_intervention());
1117 assert!(phon.recommends_additional_info());
1118 assert_eq!(phon.instruction_category(), "Communication");
1119
1120 let chqb = Field23E::new("CHQB", None).unwrap();
1122 assert!(chqb.is_payment_method_instruction());
1123 assert_eq!(chqb.priority_impact(), -1);
1124 assert!(chqb.requires_manual_intervention());
1125 assert!(!chqb.recommends_additional_info());
1126 assert_eq!(chqb.instruction_category(), "Payment Method");
1127 }
1128
1129 #[test]
1130 fn test_field23e_serialization_with_enhanced_fields() {
1131 let field = Field23E::new("HOLD", Some("COMPLIANCE CHECK".to_string())).unwrap();
1132
1133 let json = serde_json::to_string(&field).unwrap();
1135 let deserialized: Field23E = serde_json::from_str(&json).unwrap();
1136
1137 assert_eq!(field, deserialized);
1138 assert_eq!(field.code(), deserialized.code());
1139 assert_eq!(field.additional_info(), deserialized.additional_info());
1140 assert_eq!(
1141 field.instruction_category(),
1142 deserialized.instruction_category()
1143 );
1144 assert_eq!(field.priority_impact(), deserialized.priority_impact());
1145 }
1146
1147 #[test]
1148 fn test_field23e_real_world_scenarios() {
1149 let high_value = Field23E::new("PHON", Some("CALL ALL PARTIES".to_string())).unwrap();
1151 assert!(high_value.is_communication_instruction());
1152 assert!(high_value.requires_manual_intervention());
1153 assert_eq!(high_value.priority_impact(), 1);
1154
1155 let urgent = Field23E::new("SDVA", None).unwrap();
1157 assert!(urgent.is_timing_instruction());
1158 assert!(!urgent.requires_manual_intervention());
1159 assert_eq!(urgent.priority_impact(), 2);
1160
1161 let compliance = Field23E::new("HOLD", Some("AML REVIEW REQUIRED".to_string())).unwrap();
1163 assert!(compliance.is_timing_instruction());
1164 assert!(compliance.requires_manual_intervention());
1165 assert_eq!(compliance.priority_impact(), -2);
1166 assert!(compliance.recommends_additional_info());
1167
1168 let internal = Field23E::new("INTC", None).unwrap();
1170 assert_eq!(internal.instruction_category(), "Internal Transfer");
1171 assert!(!internal.requires_manual_intervention());
1172 assert_eq!(internal.priority_impact(), 0);
1173 }
1174
1175 #[test]
1176 fn test_field23e_edge_cases_enhanced() {
1177 let all_codes = [
1179 "CHQB", "HOLD", "INTC", "PHOB", "PHOI", "PHON", "REPA", "SDVA", "TELB", "TELE", "TELI",
1180 ];
1181 for code in all_codes {
1182 let field = Field23E::new(code, None).unwrap();
1183
1184 assert!(!field.instruction_category().is_empty());
1186
1187 assert!(!field.description().is_empty());
1189
1190 assert!(field.priority_impact() >= -2 && field.priority_impact() <= 2);
1192
1193 assert!(field.priority_impact() >= -2 && field.priority_impact() <= 2);
1195 }
1196 }
1197
1198 #[test]
1199 fn test_field23e_comprehensive_validation() {
1200 for &code in VALID_INSTRUCTION_CODES {
1202 let field = Field23E::new(code, None).unwrap();
1203 let validation = field.validate();
1204 assert!(validation.is_valid, "Code {} should be valid", code);
1205 assert!(field.is_valid_code());
1206
1207 let field_with_info = Field23E::new(code, Some("TEST INFO".to_string())).unwrap();
1209 let validation_with_info = field_with_info.validate();
1210 assert!(
1211 validation_with_info.is_valid,
1212 "Code {} with info should be valid",
1213 code
1214 );
1215 }
1216 }
1217}