evergreen/common/
renew.rs

1use crate as eg;
2use eg::common::circulator::{CircOp, Circulator};
3use eg::common::holds;
4use eg::date;
5use eg::EgEvent;
6use eg::EgResult;
7use eg::EgValue;
8
9/// Performs item checkins
10impl Circulator<'_> {
11    /// Renew a circulation.
12    ///
13    /// Returns Ok(()) if the active transaction should be committed and
14    /// Err(EgError) if the active transaction should be rolled backed.
15    pub fn renew(&mut self) -> EgResult<()> {
16        self.circ_op = CircOp::Renew;
17        self.init()?;
18
19        log::info!("{self} starting renew");
20
21        self.load_renewal_circ()?;
22        self.basic_renewal_checks()?;
23
24        // Do this after self.basic_renewal_checks which may change
25        // our circ lib.
26        if !self.editor.allowed_at("COPY_CHECKOUT", self.circ_lib)? {
27            return Err(self.editor().die_event());
28        }
29
30        self.checkin()?;
31        self.checkout()
32    }
33
34    /// Find the circ we're trying to renew and extra the patron info.
35    pub fn load_renewal_circ(&mut self) -> EgResult<()> {
36        let mut query = eg::hash! {
37            "target_copy": self.copy_id,
38            "xact_finish": EgValue::Null,
39            "checkin_time": EgValue::Null,
40        };
41
42        if self.patron_id > 0 {
43            // Renewal caller does not always pass patron data.
44            query["usr"] = EgValue::from(self.patron_id);
45        }
46
47        let flesh = eg::hash! {
48            "flesh": 2,
49            "flesh_fields": {
50                "circ": ["usr"],
51                "au": ["card"],
52            }
53        };
54
55        let mut circ = self
56            .editor()
57            .search_with_ops("circ", query, flesh)?
58            .pop()
59            .ok_or_else(|| self.editor().die_event())?;
60
61        let circ_id = circ.id()?;
62        let patron = circ["usr"].take(); // fleshed
63        self.patron_id = patron.id()?;
64        self.patron = Some(patron);
65
66        // Replace the usr value which was null-ified above w/ take()
67        circ["usr"] = EgValue::from(self.patron_id);
68
69        self.parent_circ = Some(circ_id);
70        self.circ = Some(circ);
71
72        Ok(())
73    }
74
75    /// Check various perms, policies, limits before proceeding with
76    /// checkin+checkout.
77    fn basic_renewal_checks(&mut self) -> EgResult<()> {
78        let circ = self.circ.as_ref().unwrap();
79        let patron = self.patron.as_ref().unwrap();
80
81        let orig_circ_lib = circ["circ_lib"].int()?;
82
83        let renewal_remaining = circ["renewal_remaining"].int()?;
84        // NULL-able
85        let auto_renewal_remaining = circ["auto_renewal_remaining"].int();
86
87        let expire_date = patron["expire_date"].as_str().unwrap(); // required
88        let expire_dt = date::parse_datetime(expire_date)?;
89
90        let circ_lib = self.set_renewal_circ_lib(orig_circ_lib)?;
91
92        if self.patron_id != self.requestor_id()?
93            && !self.editor().allowed_at("RENEW_CIRC", circ_lib)?
94        {
95            return Err(self.editor().die_event());
96        }
97
98        if renewal_remaining < 1 {
99            self.exit_err_on_event_code("MAX_RENEWALS_REACHED")?;
100        }
101
102        self.renewal_remaining = renewal_remaining - 1;
103
104        // NULL-able field.
105        if let Ok(n) = auto_renewal_remaining {
106            if n < 1 {
107                self.exit_err_on_event_code("MAX_RENEWALS_REACHED")?;
108            }
109            self.auto_renewal_remaining = Some(n - 1);
110        }
111
112        // See if it's OK to renew items for expired patron accounts.
113        if expire_dt < date::now() {
114            let allow = self.settings.get_value("circ.renew.expired_patron_allow")?;
115            if !allow.boolish() {
116                self.exit_err_on_event_code("PATRON_ACCOUNT_EXPIRED")?;
117            }
118        }
119
120        let copy_id = self.copy_id;
121        let block_for_holds = self
122            .settings
123            .get_value("circ.block_renews_for_holds")?
124            .boolish();
125
126        if block_for_holds {
127            let holds = holds::find_nearest_permitted_hold(self.editor(), copy_id, true)?;
128            if holds.is_some() {
129                self.add_event(EgEvent::new("COPY_NEEDED_FOR_HOLD"));
130            }
131        }
132
133        Ok(())
134    }
135
136    fn set_renewal_circ_lib(&mut self, orig_circ_lib: i64) -> EgResult<i64> {
137        let is_opac = self
138            .options
139            .get("opac_renewal")
140            .unwrap_or(&eg::NULL)
141            .boolish();
142        let is_auto = self
143            .options
144            .get("auto_renewal")
145            .unwrap_or(&eg::NULL)
146            .boolish();
147        let is_desk = self
148            .options
149            .get("desk_renewal")
150            .unwrap_or(&eg::NULL)
151            .boolish();
152
153        if is_opac || is_auto {
154            if let Some(setting) = self
155                .editor()
156                .retrieve("cgf", "circ.opac_renewal.use_original_circ_lib")?
157                .take()
158            {
159                if setting["enabled"].boolish() {
160                    self.circ_lib = orig_circ_lib;
161                    self.settings.set_org_id(orig_circ_lib);
162                    return Ok(orig_circ_lib);
163                }
164            }
165        }
166
167        if is_desk {
168            if let Some(setting) = self
169                .editor()
170                .retrieve("cgf", "circ.desk_renewal.use_original_circ_lib")?
171                .take()
172            {
173                if setting["enabled"].boolish() {
174                    self.circ_lib = orig_circ_lib;
175                    self.settings.set_org_id(orig_circ_lib);
176                    return Ok(orig_circ_lib);
177                }
178            }
179        }
180
181        // Shouldn't get here, but maybe possible.
182        Ok(self.circ_lib)
183    }
184}