1use crate as eg;
2use chrono::prelude::Datelike;
3use chrono::Duration;
4use eg::date;
5use eg::Editor;
6use eg::EgResult;
7use eg::EgValue;
8
9fn org_relations_query(
12 editor: &mut Editor,
13 org_id: i64,
14 transform: &str,
15 depth: Option<i64>,
16) -> EgResult<Vec<i64>> {
17 let mut query = eg::hash! {
18 "select": {
19 "aou": [{
20 "transform": transform,
21 "column": "id",
22 "result_field": "id",
23 "params": []
24 }]
25 },
26 "from": "aou",
27 "where": {"id": org_id}
28 };
29
30 if let Some(d) = depth {
31 query["select"][0]["params"] = EgValue::from(vec![d]);
32 }
33
34 let list = editor.json_query(query)?;
35
36 let mut ids = Vec::new();
37 for h in list {
38 ids.push(h.id()?);
39 }
40 Ok(ids)
41}
42
43pub fn by_shortname(editor: &mut Editor, sn: &str) -> EgResult<EgValue> {
44 if let Some(o) = editor.search("aou", eg::hash! {"shortname": sn})?.pop() {
45 Ok(o)
46 } else {
47 Err(editor.die_event())
48 }
49}
50
51pub fn ancestors(editor: &mut Editor, org_id: i64) -> EgResult<Vec<i64>> {
52 org_relations_query(editor, org_id, "actor.org_unit_ancestors", None)
53}
54
55pub fn descendants(editor: &mut Editor, org_id: i64) -> EgResult<Vec<i64>> {
56 org_relations_query(editor, org_id, "actor.org_unit_descendants", None)
57}
58
59pub fn full_path(editor: &mut Editor, org_id: i64, depth: Option<i64>) -> EgResult<Vec<i64>> {
60 org_relations_query(editor, org_id, "actor.org_unit_full_path", depth)
61}
62
63pub fn root_org_unit(editor: &mut Editor) -> EgResult<EgValue> {
70 Ok(editor
71 .search_with_ops(
72 "aou",
73 eg::hash! {"parent_ou": EgValue::Null},
74 eg::hash! {"limit": 1},
75 )?
76 .pop()
77 .expect("we require a root org unit")
78 )
79}
80
81#[derive(Clone, PartialEq)]
83pub enum OrgOpenState {
84 Open,
86 Never,
88 OpensOnDate(date::EgDate),
91}
92
93pub fn next_open_date(
103 editor: &mut Editor,
104 org_id: i64,
105 date: &date::EgDate,
106) -> EgResult<OrgOpenState> {
107 let start_date = *date; let mut date = *date; let mut closed_days: Vec<i64> = Vec::new();
111 if let Some(h) = editor.retrieve("aouhoo", org_id)? {
112 for day in 0..7 {
113 let open = h[&format!("dow_{day}_open")].as_str().unwrap();
114 let close = h[&format!("dow_{day}_close")].as_str().unwrap();
115 if open == "00:00:00" && close == open {
116 closed_days.push(day);
117 }
118 }
119
120 if closed_days.len() == 7 {
122 return Ok(OrgOpenState::Never);
123 }
124 }
125
126 let mut counter = 0;
127 while counter < 366 {
128 counter += 1;
130
131 let weekday = date.date_naive().weekday().num_days_from_sunday();
133
134 if closed_days.contains(&(weekday as i64)) {
135 date += Duration::try_days(1).expect("In Bounds");
138 continue;
139 }
140
141 let timestamp = date::to_iso(&date);
145 let query = eg::hash! {
146 "org_unit": org_id,
147 "close_start": {"<=": EgValue::from(timestamp.clone())},
148 "close_end": {">=": EgValue::from(timestamp)},
149 };
150
151 let org_closed = editor.search("aoucd", query)?;
152
153 if org_closed.is_empty() {
154 if start_date == date {
156 return Ok(OrgOpenState::Open);
158 } else {
159 return Ok(OrgOpenState::OpensOnDate(date));
162 }
163 }
164
165 let mut range_end = org_closed[0]["close_end"].as_str().unwrap();
167 for day in org_closed.iter() {
168 let end = day["close_end"].as_str().unwrap();
169 if end > range_end {
170 range_end = end;
171 }
172 }
173
174 date = date::parse_datetime(range_end)?;
175 date += Duration::try_days(1).expect("In Bounds");
176 }
177
178 Ok(OrgOpenState::Never)
180}
181
182pub fn proximity(editor: &mut Editor, from_org: i64, to_org: i64) -> EgResult<Option<i64>> {
184 let query = eg::hash! {
185 "select": {"aoup": ["prox"]},
186 "from": "aoup",
187 "where": {
188 "from_org": from_org,
189 "to_org": to_org
190 }
191 };
192
193 if let Some(prox) = editor.json_query(query)?.pop() {
194 Ok(prox["prox"].as_int())
195 } else {
196 Ok(None)
197 }
198}