1use crate as eg;
2use eg::common::holds;
3use eg::common::settings::Settings;
4use eg::idl;
5use eg::Editor;
6use eg::EgResult;
7use eg::EgValue;
8use marctk as marc;
9use std::collections::HashMap;
10
11#[derive(Debug, PartialEq)]
17pub enum DisplayAttrValue {
18 Value(Option<String>),
19 List(Vec<String>),
20}
21
22impl DisplayAttrValue {
23 pub fn first(&self) -> &str {
28 match self {
29 Self::Value(op) => match op {
30 Some(s) => s.as_str(),
31 None => "",
32 },
33 Self::List(v) => v.first().map(|v| v.as_str()).unwrap_or(""),
34 }
35 }
36
37 pub fn into_value(mut self) -> EgValue {
38 match self {
39 Self::Value(ref mut op) => op.take().map(EgValue::from).unwrap_or(EgValue::Null),
40 Self::List(ref mut l) => EgValue::from(std::mem::take(l)),
41 }
42 }
43}
44
45pub struct DisplayAttr {
46 name: String,
47 label: String,
48 value: DisplayAttrValue,
49}
50
51impl DisplayAttr {
52 pub fn add_value(&mut self, value: String) {
53 match self.value {
54 DisplayAttrValue::Value(ref mut op) => {
55 if let Some(s) = op.take() {
56 self.value = DisplayAttrValue::List(vec![s, value]);
57 } else {
58 self.value = DisplayAttrValue::Value(Some(value));
59 }
60 }
61 DisplayAttrValue::List(ref mut l) => {
62 l.push(value);
63 }
64 }
65 }
66 pub fn label(&self) -> &str {
67 &self.label
68 }
69 pub fn name(&self) -> &str {
70 &self.name
71 }
72 pub fn value(&self) -> &DisplayAttrValue {
73 &self.value
74 }
75}
76
77pub struct DisplayAttrSet {
79 attrs: Vec<DisplayAttr>,
80}
81
82impl DisplayAttrSet {
83 pub fn into_value(mut self) -> EgValue {
84 let mut hash = eg::hash! {};
85 for attr in self.attrs.drain(..) {
86 let name = attr.name().to_string(); hash[&name] = attr.value.into_value();
88 }
89 hash
90 }
91}
92
93impl DisplayAttrSet {
94 pub fn attrs(&self) -> &Vec<DisplayAttr> {
95 &self.attrs
96 }
97
98 pub fn attr(&self, name: &str) -> Option<&DisplayAttr> {
99 self.attrs.iter().find(|a| a.name.as_str() == name)
100 }
101
102 pub fn attr_mut(&mut self, name: &str) -> Option<&mut DisplayAttr> {
103 self.attrs.iter_mut().find(|a| a.name.as_str() == name)
104 }
105
106 pub fn first_value(&self, name: &str) -> &str {
110 if let Some(attr) = self.attr(name) {
111 attr.value.first()
112 } else {
113 ""
114 }
115 }
116}
117
118pub fn map_to_mvr(editor: &mut Editor, bib_id: i64) -> EgResult<EgValue> {
120 let mut maps = get_display_attrs(editor, &[bib_id])?;
121
122 let mut attr_set = match maps.remove(&bib_id) {
123 Some(m) => m,
124 None => return Err(format!("Bib {bib_id} has no display attributes").into()),
125 };
126
127 let mut mvr = eg::hash! {"doc_id": bib_id};
128
129 let idl_class = idl::get_class("mvr")?;
130
131 let field_names = idl_class.field_names();
134
135 for attr in attr_set.attrs.iter_mut() {
136 if field_names.contains(&attr.name.as_str()) {
137 let value = std::mem::replace(&mut attr.value, DisplayAttrValue::Value(None));
138 mvr[&attr.name] = value.into_value();
139 }
140 }
141
142 EgValue::create("mvr", mvr)
143}
144
145pub fn get_display_attrs(
147 editor: &mut Editor,
148 bib_ids: &[i64],
149) -> EgResult<HashMap<i64, DisplayAttrSet>> {
150 let mut map = HashMap::new();
151 let attrs = editor.search("mfde", eg::hash! {"source": bib_ids})?;
152
153 for attr in attrs {
154 let bib_id = attr["source"].int()?;
155
156 let attr_set = map
158 .entry(bib_id)
159 .or_insert_with(|| DisplayAttrSet { attrs: Vec::new() });
160
161 let attr_name = attr["name"].to_string().expect("Required");
162 let attr_label = attr["label"].to_string().expect("Required");
163 let attr_value = attr["value"].to_string().expect("Required");
164
165 if let Some(attr) = attr_set.attr_mut(&attr_name) {
166 attr.add_value(attr_value);
167 } else {
168 let attr = DisplayAttr {
169 name: attr_name,
170 label: attr_label,
171 value: DisplayAttrValue::Value(Some(attr_value)),
172 };
173 attr_set.attrs.push(attr);
174 }
175 }
176
177 Ok(map)
178}
179
180pub struct RecordSummary {
181 id: i64,
182 record: EgValue,
183 display: DisplayAttrSet,
184 attributes: EgValue,
185 urls: Option<Vec<RecordUrl>>,
186 record_note_count: usize,
187 copy_counts: Vec<EgValue>,
188 hold_count: i64,
189 has_holdable_copy: bool,
190}
191
192impl RecordSummary {
193 pub fn into_value(mut self) -> EgValue {
194 let mut urls = EgValue::new_array();
195
196 if let Some(mut list) = self.urls.take() {
197 for v in list.drain(..) {
198 urls.push(v.into_value()).expect("Is Array");
199 }
200 }
201
202 let copy_counts = std::mem::take(&mut self.copy_counts);
203
204 eg::hash! {
205 id: self.id,
206 record: self.record.take(),
207 display: self.display.into_value(),
208 record_note_count: self.record_note_count,
209 attributes: self.attributes.take(),
210 copy_counts: EgValue::from(copy_counts),
211 hold_count: self.hold_count,
212 urls: urls,
213 has_holdable_copy: self.has_holdable_copy,
214 staff_view_metabib_attributes: eg::hash!{},
216 staff_view_metabib_records: eg::array! [],
218 }
219 }
220}
221
222pub fn catalog_record_summary(
223 editor: &mut Editor,
224 org_id: i64,
225 rec_id: i64,
226 is_staff: bool,
227 is_meta: bool,
228) -> EgResult<RecordSummary> {
229 let flesh = eg::hash! {
230 "flesh": 1,
231 "flesh_fields": {
232 "bre": ["mattrs", "creator", "editor", "notes"]
233 }
234 };
235
236 let mut record = editor
237 .retrieve_with_ops("bre", rec_id, flesh)?
238 .ok_or_else(|| editor.die_event())?;
239
240 let mut display_map = get_display_attrs(editor, &[rec_id])?;
241
242 let display = display_map
243 .remove(&rec_id)
244 .ok_or_else(|| format!("Cannot load attrs for bib {rec_id}"))?;
245
246 let mut attrs = EgValue::new_object();
250 for attr in record["mattrs"].members_mut() {
251 let name = attr["attr"].take();
252 let val = attr["value"].take();
253
254 if let EgValue::Array(ref mut list) = attrs[name.str()?] {
255 list.push(val);
256 } else {
257 attrs[name.str()?] = vec![val].into();
258 }
259 }
260
261 let urls = record_urls(editor, None, Some(record["marc"].str()?))?;
262
263 let note_count = record["notes"].len();
264 let copy_counts = record_copy_counts(editor, org_id, rec_id, is_staff, is_meta)?;
265 let hold_count = holds::record_hold_counts(editor, rec_id, None)?;
266 let has_holdable_copy = holds::record_has_holdable_copy(editor, rec_id, is_meta)?;
267
268 record["notes"].take();
270
271 record["marc"].take();
273 record["mattrs"].take();
274
275 Ok(RecordSummary {
276 id: rec_id,
277 record,
278 display,
279 urls,
280 copy_counts,
281 hold_count,
282 attributes: attrs,
283 has_holdable_copy,
284 record_note_count: note_count,
285 })
286}
287
288pub struct RecordUrl {
289 href: String,
290 label: Option<String>,
291 notes: Option<String>,
292 ind2: String,
293}
294
295impl RecordUrl {
296 pub fn into_value(self) -> EgValue {
297 eg::hash! {
298 href: self.href,
299 label: self.label,
300 notes: self.notes,
301 ind2: self.ind2
302 }
303 }
304}
305
306pub fn record_urls(
308 editor: &mut Editor,
309 bib_id: Option<i64>,
310 xml: Option<&str>,
311) -> EgResult<Option<Vec<RecordUrl>>> {
312 let rec_binding;
313
314 let xml = match xml.as_ref() {
315 Some(x) => x,
316 None => {
317 if let Some(id) = bib_id {
318 rec_binding = editor
319 .retrieve("bre", id)?
320 .ok_or_else(|| editor.die_event())?;
321 rec_binding["marc"].str()?
322 } else {
323 return Err("bib::record_urls requires params".into());
324 }
325 }
326 };
327
328 let record = match marc::Record::from_xml(xml).next() {
329 Some(result) => result?,
330 None => return Err("MARC XML parsing returned no result".into()),
331 };
332
333 let mut urls_maybe = None;
334
335 for field in record.get_fields("856").iter() {
336 if field.ind1() != "4" {
337 continue;
338 }
339
340 if field.has_subfield("9") || field.has_subfield("w") || field.has_subfield("n") {
342 continue;
343 }
344
345 let label_sf = field.first_subfield("y");
346 let notes_sf = field.first_subfield("z").or(field.first_subfield("3"));
347
348 for href in field.get_subfields("u").iter() {
349 if href.content().trim().is_empty() {
350 continue;
351 }
352
353 let label = label_sf.map(|l| l.content().to_string());
359 let notes = notes_sf.map(|v| v.content().to_string());
360
361 let url = RecordUrl {
362 label,
363 notes,
364 href: href.content().to_string(),
365 ind2: field.ind2().to_string(),
366 };
367
368 let urls = match urls_maybe.as_mut() {
369 Some(u) => u,
370 None => {
371 urls_maybe = Some(Vec::new());
372 urls_maybe.as_mut().unwrap()
373 }
374 };
375
376 urls.push(url);
377 }
378 }
379
380 Ok(urls_maybe)
381}
382
383pub fn record_copy_counts(
384 editor: &mut Editor,
385 org_id: i64,
386 rec_id: i64,
387 is_staff: bool,
388 is_meta: bool,
389) -> EgResult<Vec<EgValue>> {
390 let key = if is_meta { "metarecord" } else { "record" };
391 let func = format!("asset.{key}_copy_count");
392 let query = eg::hash! {"from": [func, org_id, rec_id, is_staff]};
393 let mut data = editor.json_query(query)?;
394
395 for count in data.iter_mut() {
396 count["count"] = count["visible"].take();
398 count.remove("visible");
399 }
400
401 data.sort_by(|a, b| {
402 let da = a["depth"].int_required();
403 let db = b["depth"].int_required();
404 da.cmp(&db)
405 });
406
407 Ok(data)
408}
409
410#[derive(Debug, Clone)]
411pub struct BibSubfield {
412 pub tag: String,
413 pub subfield: String,
414}
415
416pub fn bib_call_number_fields(
419 editor: &mut Editor,
420 org_id: i64,
421) -> EgResult<Option<Vec<BibSubfield>>> {
422 let mut settings = Settings::new(editor);
424
425 let class_id = settings.get_value_at_org("cat.default_classification_scheme", org_id)?;
426
427 let Some(class_id) = class_id.as_int() else {
428 log::debug!("No value for cat.default_classification_scheme at org {org_id}");
429 return Ok(None);
430 };
431
432 let classification = editor
433 .retrieve("acnc", class_id)?
434 .ok_or_else(|| editor.die_event())?;
435
436 let field_str = classification["field"].string()?;
439
440 let mut subfields = Vec::new();
441
442 for field in field_str.split(',') {
443 if field.len() < 4 {
444 continue;
445 }
446
447 let tag = &field[..3];
448
449 for sf in field[3..].split("") {
450 subfields.push(BibSubfield {
451 tag: tag.to_string(),
452 subfield: sf.to_string(),
453 });
454 }
455 }
456
457 Ok(Some(subfields))
458}