evergreen/common/trigger/validator/
mod.rs1use crate as eg;
3use eg::common::holdings;
4use eg::common::trigger::{Event, EventState, Processor};
5use eg::constants as C;
6use eg::date;
7use eg::EgResult;
8
9impl Processor<'_> {
11 pub fn validate(&mut self, event: &mut Event) -> EgResult<bool> {
19 log::info!("{self} validating {event}");
20
21 self.set_event_state(event, EventState::Validating)?;
22
23 let validator = self.validator();
24
25 let validate_result = match validator {
26 "NOOP_True" => Ok(true),
27 "NOOP_False" => Ok(false),
28 "CircIsOpen" => self.circ_is_open(event),
29 "CircIsOverdue" => self.circ_is_overdue(event),
30 "HoldIsAvailable" => self.hold_is_available(event),
31 "HoldIsCancelled" => self.hold_is_canceled(event),
32 "HoldNotifyCheck" => self.hold_notify_check(event),
33 "MinPassiveTargetAge" => self.min_passive_target_age(event),
34 "PatronBarred" => self.patron_is_barred(event),
35 "PatronNotBarred" => self.patron_is_barred(event).map(|val| !val),
36 "ReservationIsAvailable" => self.reservation_is_available(event),
37 _ => Err(format!("No such validator: {validator}").into()),
38 };
39
40 if let Ok(valid) = validate_result {
41 if valid {
42 self.set_event_state(event, EventState::Validating)?;
43 } else {
44 self.set_event_state(event, EventState::Invalid)?;
45 }
46 }
47
48 validate_result
49 }
50
51 fn circ_is_open(&mut self, event: &Event) -> EgResult<bool> {
53 if event.target()["checkin_time"].is_string() {
54 return Ok(false);
55 }
56
57 if event.target()["xact_finish"].is_string() {
58 return Ok(false);
59 }
60
61 if self.param_value("min_target_age").is_some() {
62 if let Some(fname) = self.param_value_as_str("target_age_field") {
63 if fname == "xact_start" {
64 return self.min_passive_target_age(event);
65 }
66 }
67 }
68
69 Ok(true)
70 }
71
72 fn min_passive_target_age(&mut self, event: &Event) -> EgResult<bool> {
73 let min_target_age = self
74 .param_value_as_str("min_target_age")
75 .ok_or_else(|| {
76 "'min_target_age' parameter required for MinPassiveTargetAge".to_string()
77 })?
78 .to_string();
79
80 let age_field = self.param_value_as_str("target_age_field").ok_or_else(|| {
81 "'target_age_field' parameter or delay_field required for MinPassiveTargetAge"
82 .to_string()
83 })?;
84
85 let age_field_val = &event.target()[age_field];
86 let age_date_str = age_field_val.as_str().ok_or_else(|| {
87 format!(
88 "MinPassiveTargetAge age field {age_field} has unexpected value: {}",
89 age_field_val.dump()
90 )
91 })?;
92
93 let age_field_ts =
94 date::add_interval(date::parse_datetime(age_date_str)?, &min_target_age)?;
95
96 Ok(age_field_ts <= date::now())
97 }
98
99 fn circ_is_overdue(&mut self, event: &Event) -> EgResult<bool> {
100 if event.target()["checkin_time"].is_string() {
101 return Ok(false);
102 }
103
104 if let Some(stop_fines) = event.target()["stop_fines"].as_str() {
105 if stop_fines == "MAXFINES" || stop_fines == "LONGOVERDUE" {
106 return Ok(false);
107 }
108 }
109
110 if self.param_value("min_target_age").is_some() {
111 if let Some(fname) = self.param_value_as_str("target_age_field") {
112 if fname == "xact_start" {
113 return self.min_passive_target_age(event);
114 }
115 }
116 }
117
118 let due_date = event.target()["due_date"].as_str().unwrap();
120 let due_date_ts = date::parse_datetime(due_date)?;
121
122 Ok(due_date_ts < date::now())
123 }
124
125 fn hold_is_available(&mut self, event: &Event) -> EgResult<bool> {
127 if !self.hold_notify_check(event)? {
128 return Ok(false);
129 }
130
131 let hold = event.target();
132
133 let canceled = hold["cancel_time"].is_string();
135 let fulfilled = hold["fulfillment_time"].is_string();
136 let captured = hold["capture_time"].is_string();
137 let shelved = hold["shelf_time"].is_string();
138
139 if canceled || fulfilled || !captured || !shelved {
140 return Ok(false);
141 }
142
143 let shelf_lib = match hold["current_shelf_lib"].as_i64() {
148 Some(id) => id,
149 None => match hold["current_shelf_lib"]["id"].as_i64() {
150 Some(id) => id,
151 None => return Ok(false),
152 },
153 };
154
155 let pickup_lib = hold["pickup_lib"]
156 .as_int()
157 .unwrap_or(hold["pickup_lib"].id()?);
158
159 if shelf_lib != pickup_lib {
160 return Ok(false);
161 }
162
163 let copy_status = if let Some(copy_id) = hold["current_copy"].as_i64() {
165 holdings::copy_status(self.editor, Some(copy_id), None)?
166 } else if hold["current_copy"].is_object() {
167 holdings::copy_status(self.editor, None, Some(&hold["current_copy"]))?
168 } else {
169 -1
170 };
171
172 Ok(copy_status == C::COPY_STATUS_ON_HOLDS_SHELF)
173 }
174
175 fn hold_is_canceled(&mut self, event: &Event) -> EgResult<bool> {
176 if self.hold_notify_check(event)? {
177 Ok(event.target()["cancel_time"].is_string())
178 } else {
179 Ok(false)
180 }
181 }
182
183 fn hold_notify_check(&mut self, event: &Event) -> EgResult<bool> {
191 let hold = event.target();
192
193 if self.param_value_as_bool("check_email_notify") && !hold["email_notify"].boolish() {
194 return Ok(false);
195 }
196
197 if self.param_value_as_bool("check_sms_notify") && !hold["sms_notify"].boolish() {
198 return Ok(false);
199 }
200
201 if self.param_value_as_bool("check_phone_notify") && !hold["phone_notify"].boolish() {
202 return Ok(false);
203 }
204
205 Ok(true)
206 }
207
208 fn reservation_is_available(&mut self, event: &Event) -> EgResult<bool> {
209 let res = event.target();
210 Ok(res["cancel_time"].is_null()
211 && !res["capture_time"].is_null()
212 && !res["current_resource"].is_null())
213 }
214
215 fn patron_is_barred(&mut self, event: &Event) -> EgResult<bool> {
216 Ok(event.target()["barred"].boolish())
217 }
218
219 }