]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/biblio.pm
LP#1549505: Provide a cron-able script to perform badge recalculation
[Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / Application / Storage / Publisher / biblio.pm
1 package OpenILS::Application::Storage::Publisher::biblio;
2 use base qw/OpenILS::Application::Storage/;
3 use vars qw/$VERSION/;
4 use OpenSRF::EX qw/:try/;
5 #use OpenILS::Application::Storage::CDBI::biblio;
6 #use OpenILS::Application::Storage::CDBI::asset;
7 use OpenILS::Utils::Fieldmapper;
8
9 $VERSION = 1;
10
11 sub record_copy_count {
12     my $self = shift;
13     my $client = shift;
14
15     my %args = @_;
16
17     my $cn_table = asset::call_number->table;
18     my $cp_table = asset::copy->table;
19     my $st_table = config::copy_status->table;
20     my $src_table = config::bib_source->table;
21     my $br_table = biblio::record_entry->table;
22     my $loc_table = asset::copy_location->table;
23     my $out_table = actor::org_unit_type->table;
24
25     my $descendants = "actor.org_unit_descendants(u.id)";
26     my $ancestors = "actor.org_unit_ancestors(?) u JOIN $out_table t ON (u.ou_type = t.id)";
27
28     if ($args{org_unit} < 0) {
29         $args{org_unit} *= -1;
30         $ancestors = "(select org_unit as id from actor.org_lasso_map where lasso = ?) u CROSS JOIN (SELECT -1 AS depth) t";
31     }
32
33     my $visible = 'AND a.opac_visible = TRUE AND st.opac_visible = TRUE AND loc.opac_visible = TRUE AND cp.opac_visible = TRUE';
34     if ($self->api_name =~ /staff/o) {
35         $visible = ''
36     }
37
38     my $sql = <<"    SQL";
39         SELECT  t.depth,
40             u.id AS org_unit,
41             sum(
42                 (SELECT count(cp.id)
43                   FROM  $cn_table cn
44                     JOIN $cp_table cp ON (cn.id = cp.call_number)
45                     JOIN $descendants a ON (cp.circ_lib = a.id)
46                     JOIN $st_table st ON (cp.status = st.id)
47                     JOIN $loc_table loc ON (cp.location = loc.id)
48                   WHERE cn.record = ?
49                     $visible
50                     AND cn.deleted IS FALSE
51                     AND cp.deleted IS FALSE
52                     AND loc.deleted IS FALSE)
53             ) AS count,
54             sum(
55                 (SELECT count(cp.id)
56                   FROM  $cn_table cn
57                     JOIN $cp_table cp ON (cn.id = cp.call_number)
58                     JOIN $descendants a ON (cp.circ_lib = a.id)
59                     JOIN $st_table st ON (cp.status = st.id)
60                     JOIN $loc_table loc ON (cp.location = loc.id)
61                   WHERE cn.record = ?
62                     $visible
63                     AND cn.deleted IS FALSE
64                     AND cp.deleted IS FALSE
65                     AND loc.deleted IS FALSE
66                     AND cp.status IN (0,7,12))
67             ) AS available,
68             sum(
69                 (SELECT count(cp.id)
70                   FROM  $cn_table cn
71                     JOIN $cp_table cp ON (cn.id = cp.call_number)
72                     JOIN $st_table st ON (cp.status = st.id)
73                     JOIN $loc_table loc ON (cp.location = loc.id)
74                   WHERE cn.record = ?
75                     AND st.opac_visible = TRUE
76                     AND loc.opac_visible = TRUE
77                     AND cp.opac_visible = TRUE
78                     AND cn.deleted IS FALSE
79                     AND cp.deleted IS FALSE
80                     AND loc.deleted IS FALSE)
81             ) AS unshadow,
82                         sum(    
83                                 (SELECT sum(1)
84                                   FROM  $br_table br
85                                         JOIN $src_table src ON (src.id = br.source)
86                                   WHERE br.id = ?
87                                         AND src.transcendant IS TRUE
88                                 )
89                         ) AS transcendant
90           FROM  $ancestors
91           GROUP BY 1,2
92     SQL
93
94     my $sth = biblio::record_entry->db_Main->prepare_cached($sql);
95     $sth->execute(''.$args{record}, ''.$args{record}, ''.$args{record}, ''.$args{record}, ''.$args{org_unit});
96     while ( my $row = $sth->fetchrow_hashref ) {
97         $client->respond( $row );
98     }
99     return undef;
100 }
101 __PACKAGE__->register_method(
102     api_name    => 'open-ils.storage.biblio.record_entry.copy_count',
103     method      => 'record_copy_count',
104     api_level   => 1,
105     stream      => 1,
106     cachable    => 1,
107 );
108 __PACKAGE__->register_method(
109     api_name    => 'open-ils.storage.biblio.record_entry.copy_count.staff',
110     method      => 'record_copy_count',
111     api_level   => 1,
112     stream      => 1,
113     cachable    => 1,
114 );
115
116 sub record_ranged_tree {
117     my $self = shift;
118     my $client = shift;
119     my $r = shift;
120     my $ou = shift;
121     my $depth = shift;
122     my $limit = shift || 0;
123     my $offset = shift || 0;
124
125     my $ou_sql = defined($depth) ?
126             "SELECT id FROM actor.org_unit_descendants(?,?)":
127             "SELECT id FROM actor.org_unit_descendants(?)";
128
129     my $ou_list =
130         actor::org_unit
131             ->db_Main
132             ->selectcol_arrayref(
133                 $ou_sql,
134                 {},
135                 $ou,
136                 (defined($depth) ? ($depth) : ()),
137             );
138
139     return undef unless ($ou_list and @$ou_list);
140
141     $r = biblio::record_entry->retrieve( $r );
142     return undef unless ($r);
143
144     my $rec = $r->to_fieldmapper;
145     $rec->call_numbers([]);
146
147     $rec->fixed_fields( $r->record_descriptor->next->to_fieldmapper );
148
149     my $offset_count = 0;
150     my $limit_count = 0;
151     for my $cn ( $r->call_numbers  ) {
152         next if ($cn->deleted);
153         my $call_number = $cn->to_fieldmapper;
154         $call_number->copies([]);
155
156
157         for my $cp ( $cn->copies(circ_lib => $ou_list) ) {
158             next if ($cp->deleted);
159             if ($offset > 0 && $offset_count < $offset) {
160                 $offset_count++;
161                 next;
162             }
163             
164             last if ($limit > 0 && $limit_count >= $limit);
165
166             my $copy = $cp->to_fieldmapper;
167             $copy->status( $cp->status->to_fieldmapper );
168             $copy->location( $cp->location->to_fieldmapper );
169             push @{ $call_number->copies }, $copy;
170
171             $limit_count++;
172         }
173
174         last if ($limit > 0 && $limit_count >= $limit);
175
176         push @{ $rec->call_numbers }, $call_number if (@{ $call_number->copies });
177     }
178
179     return $rec;
180 }
181 __PACKAGE__->register_method(
182     api_name    => 'open-ils.storage.biblio.record_entry.ranged_tree',
183     method      => 'record_ranged_tree',
184     argc        => 1,
185     api_level   => 1,
186 );
187
188
189 sub regenerate_badge_list {
190     my $self = shift;
191     my $client = shift;
192
193     my $sth = biblio::record_entry->db_Main->prepare_cached( <<"    SQL" );
194         SELECT  r.id AS badge
195           FROM  rating.badge r
196           WHERE r.last_calc < NOW() - r.recalc_interval
197                 OR r.last_calc IS NULL
198           ORDER BY r.last_calc ASC NULLS FIRST -- oldest first
199     SQL
200
201     $sth->execute;
202     while ( my $row = $sth->fetchrow_hashref ) {
203         $client->respond( $row->{badge} );
204     }
205     return undef;
206 }
207 __PACKAGE__->register_method(
208     api_name    => 'open-ils.storage.biblio.regenerate_badge_list',
209     method      => 'regenerate_badge_list',
210     api_level   => 1,
211     cachable    => 1,
212 );
213
214
215 sub record_by_barcode {
216     my $self = shift;
217     my $client = shift;
218
219     my $cn_table = asset::call_number->table;
220     my $cp_table = asset::copy->table;
221
222     my $id = ''.shift;
223     my ($r) = biblio::record_entry->db_Main->selectrow_array( <<"    SQL", {}, $id );
224         SELECT  cn.record
225           FROM  $cn_table cn
226             JOIN $cp_table cp ON (cp.call_number = cn.id)
227           WHERE cp.barcode = ?
228     SQL
229
230     my $rec = biblio::record_entry->retrieve( $r );
231
232     return $rec->to_fieldmapper if ($rec);
233     return undef;
234 }
235 __PACKAGE__->register_method(
236     api_name    => 'open-ils.storage.biblio.record_entry.retrieve_by_barcode',
237     method      => 'record_by_barcode',
238     api_level   => 1,
239     cachable    => 1,
240 );
241
242 sub record_by_copy {
243     my $self = shift;
244     my $client = shift;
245
246     my $cn_table = asset::call_number->table;
247     my $cp_table = asset::copy->table;
248
249     my $id = ''.shift;
250     my ($r) = biblio::record_entry->db_Main->selectrow_array( <<"    SQL", {}, $id );
251         SELECT  cn.record
252           FROM  $cn_table cn
253             JOIN $cp_table cp ON (cp.call_number = cn.id)
254           WHERE cp.id = ?
255     SQL
256
257     my $rec = biblio::record_entry->retrieve( $r );
258     return undef unless ($rec);
259
260     my $r_fm = $rec->to_fieldmapper;
261     my $ff = $rec->record_descriptor->next;
262     $r_fm->fixed_fields( $ff->to_fieldmapper ) if ($ff);
263
264     return $r_fm;
265 }
266 __PACKAGE__->register_method(
267     api_name    => 'open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy',
268     method      => 'record_by_copy',
269     api_level   => 1,
270     cachable    => 1,
271 );
272
273
274 =head1 comment Old version
275
276 my $org_unit_lookup;
277 sub record_copy_count {
278     my $self = shift;
279     my $client = shift;
280     my $oid = shift;
281     my @recs = @_;
282
283     if ($self->api_name !~ /batch/o) {
284         @recs = ($recs[0]);
285     }
286
287     throw OpenSRF::EX::InvalidArg ( "No org_unit id passed!" )
288         unless ($oid);
289
290     throw OpenSRF::EX::InvalidArg ( "No record id passed!" )
291         unless (@recs);
292
293     $org_unit_lookup ||= $self->method_lookup('open-ils.storage.direct.actor.org_unit.retrieve');
294     my ($org_unit) = $org_unit_lookup->run($oid);
295
296     # XXX Use descendancy tree here!!!
297     my $short_name_hack = $org_unit->shortname;
298     $short_name_hack = '' if (!$org_unit->parent_ou);
299     $short_name_hack .= '%';
300     # XXX Use descendancy tree here!!!
301
302     my $rec_list = join(',',@recs);
303
304     my $cp_table = asset::copy->table;
305     my $cn_table = asset::call_number->table;
306
307     my $select =<<"    SQL";
308         SELECT  count(cp.*) as copies
309           FROM  $cn_table cn
310             JOIN $cp_table cp ON (cp.call_number = cn.id)
311           WHERE cn.owning_lib LIKE ? AND
312             cn.record IN ($rec_list)
313     SQL
314
315     my $sth = asset::copy->db_Main->prepare_cached($select);
316     $sth->execute($short_name_hack);
317
318     my $results = $sth->fetchall_hashref('record');
319
320     $client->respond($$results{$_}{copies} || 0) for (@recs);
321
322     return undef;
323 }
324 __PACKAGE__->register_method(
325     method      => 'record_copy_count',
326     api_name    => 'open-ils.storage.direct.biblio.record_copy_count',
327     api_level   => 1,
328     argc        => 1,
329 );
330 __PACKAGE__->register_method(
331     method      => 'record_copy_count',
332     api_name    => 'open-ils.storage.direct.biblio.record_copy_count.batch',
333     api_level   => 1,
334     argc        => 1,
335     stream      => 1,
336 );
337
338 =cut
339
340 sub global_record_copy_count {
341     my $self = shift;
342     my $client = shift;
343
344     my $rec = shift;
345
346     my $cn_table = asset::call_number->table;
347     my $cp_table = asset::copy->table;
348     my $cl_table = asset::copy_location->table;
349     my $cs_table = config::copy_status->table;
350
351     my $copies_visible = 'AND cp.opac_visible IS TRUE AND cs.opac_visible IS TRUE AND cl.opac_visible IS TRUE';
352     $copies_visible = '' if ($self->api_name =~ /staff/o);
353
354     my $sql = <<"    SQL";
355
356         SELECT  owning_lib, sum(avail), sum(tot)
357           FROM  (
358                     SELECT  cn.owning_lib, count(cp.id) as avail, 0 as tot
359                   FROM  $cn_table cn
360                     JOIN $cp_table cp ON (cn.id = cp.call_number)
361                     JOIN $cs_table cs ON (cs.id = cp.status)
362                     JOIN $cl_table cl ON (cl.id = cp.location)
363                   WHERE cn.record = ?
364                     AND cp.status IN (0,7,12)
365                     $copies_visible
366                   GROUP BY 1
367                                     UNION
368                     SELECT  cn.owning_lib, 0 as avail, count(cp.id) as tot
369                   FROM  $cn_table cn
370                     JOIN $cp_table cp ON (cn.id = cp.call_number)
371                     JOIN $cs_table cs ON (cs.id = cp.status)
372                     JOIN $cl_table cl ON (cl.id = cp.location)
373                   WHERE cn.record = ?
374                     $copies_visible
375                   GROUP BY 1
376             ) x
377           GROUP BY 1
378     SQL
379
380     my $sth = biblio::record_entry->db_Main->prepare_cached($sql);
381     $sth->execute("$rec", "$rec");
382
383     $client->respond( $_ ) for (@{$sth->fetchall_arrayref});
384     return undef;
385 }
386 __PACKAGE__->register_method(
387     api_name    => 'open-ils.storage.biblio.record_entry.global_copy_count',
388     method      => 'global_record_copy_count',
389     api_level   => 1,
390     stream      => 1,
391     cachable    => 1,
392 );
393 __PACKAGE__->register_method(
394     api_name    => 'open-ils.storage.biblio.record_entry.global_copy_count.staff',
395     method      => 'global_record_copy_count',
396     api_level   => 1,
397     stream      => 1,
398     cachable    => 1,
399 );
400
401 sub record_copy_status_count {
402     my $self = shift;
403     my $client = shift;
404
405     my $rec = shift;
406     my $ou = shift || 1;
407     my $depth = shift || 0;
408
409
410     my $descendants = "actor.org_unit_descendants(?,?)";
411
412     my $cn_table = asset::call_number->table;
413     my $cnp_table = asset::call_number_prefix->table;
414     my $cns_table = asset::call_number_suffix->table;
415     my $cp_table = asset::copy->table;
416     my $cl_table = asset::copy_location->table;
417     my $cs_table = config::copy_status->table;
418
419     my $sql = <<"    SQL";
420
421         SELECT  cp.circ_lib,
422                 CASE WHEN cnp.id > -1 THEN cnp.label ELSE '' END,
423                 cn.label,
424                 CASE WHEN cns.id > -1 THEN cns.label ELSE '' END,
425                 cp.status,
426                 count(cp.id)
427           FROM  $cp_table cp,
428             $cn_table cn,
429             $cns_table cns,
430             $cnp_table cnp,
431             $cl_table cl,
432             $cs_table cs,
433             $descendants d
434           WHERE cn.record = ?
435             AND cnp.id = cn.prefix
436             AND cns.id = cn.suffix
437             AND cp.call_number = cn.id
438             AND cp.location = cl.id
439             AND cp.circ_lib = d.id
440             AND cp.status = cs.id
441             AND cl.opac_visible IS TRUE
442             AND cp.opac_visible IS TRUE
443             AND cp.deleted IS FALSE
444             AND cl.deleted IS FALSE
445             AND cs.opac_visible IS TRUE
446           GROUP BY 1,2,3,4,5;
447     SQL
448
449     my $sth = biblio::record_entry->db_Main->prepare_cached($sql);
450     $sth->execute($ou, $depth, "$rec" );
451
452     my %data = ();
453     for my $row (@{$sth->fetchall_arrayref}) {
454         $data{$$row[0]}{$$row[1]}{$$row[2]}{$$row[3]}{$$row[4]} += $$row[5];
455     }
456     
457     for my $ou (keys %data) {
458         for my $cn_prefix (keys %{$data{$ou}}) {
459             for my $cn (keys %{$data{$ou}{$cn_prefix}}) {
460                 for my $cn_suffix (keys %{$data{$ou}{$cn_prefix}{$cn}}) {
461                     $client->respond( [$ou, $cn_prefix, $cn, $cn_suffix, $data{$ou}{$cn}{$cn_prefix}{$cn}{$cn_suffix}] );
462                 }
463             }
464         }
465     }
466     return undef;
467 }
468 __PACKAGE__->register_method(
469     api_name    => 'open-ils.storage.biblio.record_entry.status_copy_count',
470     method      => 'record_copy_status_count',
471     api_level   => 1,
472     stream      => 1,
473     cachable    => 1,
474 );
475
476
477 sub record_copy_status_location_count {
478     my $self = shift;
479     my $client = shift;
480
481     my $rec = shift;
482     my $ou = shift || 1;
483     my $depth = shift || 0;
484
485
486     my $descendants = "actor.org_unit_descendants(?,?)";
487
488     my $cn_table = asset::call_number->table;
489     my $cnp_table = asset::call_number_prefix->table;
490     my $cns_table = asset::call_number_suffix->table;
491     my $cp_table = asset::copy->table;
492     my $cl_table = asset::copy_location->table;
493     my $cs_table = config::copy_status->table;
494
495     # FIXME using oils_i18n_xlate here is exposing a hitherto unexposed
496     # implementation detail of json_query; doing it this way because
497     # json_query currently doesn't grok joining a function to tables
498     my $sql = <<"    SQL";
499
500         SELECT  cp.circ_lib,
501                 CASE WHEN cnp.id > -1 THEN cnp.label ELSE '' END,
502                 cn.label,
503                 CASE WHEN cns.id > -1 THEN cns.label ELSE '' END,
504                 oils_i18n_xlate('asset.copy_location', 'acpl', 'name', 'id', cl.id::TEXT, ?),
505                 cp.status,
506                 count(cp.id)
507           FROM  $cp_table cp,
508             $cn_table cn,
509             $cns_table cns,
510             $cnp_table cnp,
511             $cl_table cl,
512             $cs_table cs,
513             $descendants d
514           WHERE cn.record = ?
515             AND cnp.id = cn.prefix
516             AND cns.id = cn.suffix
517             AND cp.call_number = cn.id
518             AND cp.location = cl.id
519             AND cp.circ_lib = d.id
520             AND cp.status = cs.id
521             AND cl.opac_visible IS TRUE
522             AND cp.opac_visible IS TRUE
523             AND cp.deleted IS FALSE
524             AND cl.deleted IS FALSE
525             AND cs.opac_visible IS TRUE
526           GROUP BY 1,2,3,4,5,6;
527     SQL
528
529     my $sth = biblio::record_entry->db_Main->prepare_cached($sql);
530     my $ses_locale = $client->session ? $client->session->session_locale : 'en-US';
531     $sth->execute($ses_locale, $ou, $depth, "$rec" );
532
533     my %data = ();
534     for my $row (@{$sth->fetchall_arrayref}) {
535         $data{$$row[0]}{$$row[1]}{$$row[2]}{$$row[3]}{$$row[4]}{$$row[5]} += $$row[6];
536     }
537     
538     for my $ou (keys %data) {
539         for my $cn_prefix (keys %{$data{$ou}}) {
540             for my $cn (keys %{$data{$ou}{$cn_prefix}}) {
541                 for my $cn_suffix (keys %{$data{$ou}{$cn_prefix}{$cn}}) {
542                     for my $cl (keys %{$data{$ou}{$cn_prefix}{$cn}{$cn_suffix}}) {
543                         $client->respond( [$ou, $cn_prefix, $cn, $cn_suffix, $cl, $data{$ou}{$cn_prefix}{$cn}{$cn_suffix}{$cl}] );
544                     }
545                 }
546             }
547         }
548     }
549     return undef;
550 }
551 __PACKAGE__->register_method(
552     api_name    => 'open-ils.storage.biblio.record_entry.status_copy_location_count',
553     method      => 'record_copy_status_location_count',
554     api_level   => 1,
555     stream      => 1,
556     cachable    => 1,
557 );
558
559 1;