evergreen/common/trigger/reactor/
circ.rs

1//! Base module for A/T Reactors
2use crate as eg;
3use eg::common::auth;
4use eg::common::{trigger, trigger::Event, trigger::Processor};
5use eg::EgEvent;
6use eg::EgResult;
7use eg::EgValue;
8
9impl Processor<'_> {
10    pub fn autorenew(&mut self, events: &mut [&mut Event]) -> EgResult<()> {
11        let usr = &events[0].target()["usr"];
12        // "usr" is either the id itself or a user object with an ID.
13        let patron_id = usr.as_int().unwrap_or(usr.id()?);
14
15        let home_ou = if usr.is_object() {
16            usr["home_ou"].as_int().unwrap_or(usr["home_ou"].id()?)
17        } else {
18            // Fetch the patron so we can determine the home or unit
19            let patron = self
20                .editor
21                .retrieve("au", patron_id)?
22                .ok_or_else(|| self.editor.die_event())?;
23
24            patron["home_ou"].int()?
25        };
26
27        let mut auth_args = auth::InternalLoginArgs::new(patron_id, auth::LoginType::Opac);
28        auth_args.set_org_unit(home_ou);
29
30        // TODO move to internal_session() / add Trigger worker cache.
31        let auth_ses = auth::Session::internal_session_api(self.editor.client_mut(), &auth_args)?
32            .ok_or_else(|| "Cannot create internal auth session".to_string())?;
33
34        for event in events {
35            self.renew_one_circ(auth_ses.token(), patron_id, event)?;
36        }
37
38        Ok(())
39    }
40
41    fn renew_one_circ(&mut self, authtoken: &str, patron_id: i64, event: &Event) -> EgResult<()> {
42        let tc = &event.target()["target_copy"];
43        let copy_id = tc.as_int().unwrap_or(tc.id()?);
44
45        log::info!(
46            "Auto-Renewing Circ id={} copy={copy_id}",
47            event.target()["id"]
48        );
49
50        let params = vec![
51            EgValue::from(authtoken),
52            eg::hash! {
53                "patron_id": patron_id,
54                "copy_id": copy_id,
55                "auto_renewal": true
56            },
57        ];
58
59        log::info!("{self} renewing with params: {params:?}");
60
61        let mut response = self
62            .editor
63            .client_mut()
64            .send_recv_one("open-ils.circ", "open-ils.circ.renew", params)?
65            .ok_or_else(|| "Renewal returned no response".to_string())?;
66
67        // API may return an EgEvent or a list of them.  We're only
68        // interested in the first event.
69        let evt = if response.is_array() {
70            response.array_remove(0)
71        } else {
72            response
73        };
74
75        let eg_evt = EgEvent::parse(&evt)
76            .ok_or_else(|| format!("Renew returned unexpected data: {}", evt.dump()))?;
77
78        log::info!("{self} autorenewal returned {eg_evt}");
79
80        let source_circ = event.target();
81        let new_circ = &eg_evt.payload()["circ"];
82
83        let mut new_due_date = "";
84        let mut old_due_date = "";
85        let mut fail_reason = "";
86        let mut total_remaining;
87        let mut auto_remaining;
88
89        let success = eg_evt.is_success();
90        if success && new_circ.is_object() {
91            new_due_date = new_circ["due_date"].as_str().unwrap(); // required
92            total_remaining = new_circ["renewal_remaining"].int()?;
93
94            // nullable / maybe a string
95            auto_remaining = new_circ["auto_renewal_remaining"]
96                .as_int()
97                .unwrap_or_default();
98        } else {
99            old_due_date = source_circ["due_date"].as_str().unwrap(); // required
100            total_remaining = source_circ["renewal_remaining"].int()?;
101            fail_reason = eg_evt.desc().unwrap_or("");
102
103            // nullable / maybe a string
104            auto_remaining = source_circ["auto_renewal_remaining"]
105                .as_int()
106                .unwrap_or_default();
107        }
108
109        if total_remaining < 0 {
110            total_remaining = 0;
111        }
112        if auto_remaining < 0 {
113            auto_remaining = 0;
114        }
115        if auto_remaining < total_remaining {
116            auto_remaining = total_remaining;
117        }
118
119        let user_data = eg::hash! {
120            "copy": copy_id,
121            "is_renewed": success,
122            "reason": fail_reason,
123            "new_due_date": new_due_date,
124            "old_due_date": old_due_date,
125            "textcode": eg_evt.textcode(),
126            "total_renewal_remaining": total_remaining,
127            "auto_renewal_remaining": auto_remaining,
128        };
129
130        let target = &event.target()["circ_lib"];
131        let circ_lib = target.as_int().unwrap_or(target.id()?);
132
133        // Create the event from the source circ instead of the new
134        // circ, since the renewal may have failed.  Fire and do not
135        // forget so we don't flood A/T.
136        trigger::create_events_for_object(
137            self.editor,
138            "autorenewal",
139            event.target(),
140            circ_lib,
141            None,
142            Some(&user_data),
143            false,
144        )
145    }
146}