]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/container.pm
LP#1832897: business logic for carousels
[working/Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / Application / Storage / Publisher / container.pm
1 package OpenILS::Application::Storage::Publisher::container;
2 use base qw/OpenILS::Application::Storage/;
3 use vars qw/$VERSION/;
4 use OpenSRF::EX qw/:try/;
5 use OpenSRF::Utils::Logger qw/:level :logger/;
6 use OpenILS::Utils::CStoreEditor;
7 #use OpenILS::Application::Storage::CDBI::config;
8
9 my $new_items_query = q(
10     WITH c_attr AS (SELECT c_attrs::query_int AS vis_test FROM asset.patron_default_visibility_mask() x)
11     SELECT acn.record AS bib
12     FROM asset.call_number acn
13     JOIN asset.copy acp ON (acp.call_number = acn.id)
14     JOIN asset.copy_location acpl ON (acp.location = acpl.id)
15     JOIN config.copy_status ccs ON (acp.status = ccs.id)
16     , c_attr
17     WHERE acn.owning_lib IN (ORG_LIST)
18     AND acp.circ_lib IN (ORG_LIST)
19     AND acp.holdable
20     AND acp.circulate
21     AND ccs.holdable
22     AND acpl.holdable
23     AND acpl.circulate
24     AND acp.active_date > NOW() - ?::INTERVAL
25     AND (EXISTS (SELECT 1 FROM asset.copy_vis_attr_cache WHERE record = acn.record AND vis_attr_vector @@ c_attr.vis_test))
26     AND (NOT EXISTS (SELECT 1 FROM metabib.record_attr_vector_list WHERE source = acn.record AND vlist @@ metabib.compile_composite_attr(' {"1":[{"_val":"s","_attr":"bib_level"}]}')::query_int))
27     GROUP BY acn.record
28     ORDER BY MIN(AGE(acp.active_date))
29     LIMIT ? 
30 );
31 my $recently_returned_query = q(
32 WITH c_attr AS (SELECT c_attrs::query_int AS vis_test FROM asset.patron_default_visibility_mask() x)
33     SELECT acn.record AS bib
34     FROM asset.call_number acn
35     JOIN asset.copy acp ON (acp.call_number = acn.id)
36     JOIN asset.copy_location acpl ON (acp.location = acpl.id)
37     JOIN config.copy_status ccs ON (acp.status = ccs.id)
38     JOIN action.circulation circ ON (circ.target_copy = acp.id)
39     , c_attr
40     WHERE acn.owning_lib IN (ORG_LIST)
41     AND acp.circ_lib IN (ORG_LIST)
42     AND acp.holdable
43     AND acp.circulate
44     AND ccs.holdable
45     AND acpl.holdable
46     AND acpl.circulate
47     AND circ.checkin_time > NOW() - ?::INTERVAL
48     AND circ.checkin_time IS NOT NULL
49     AND (EXISTS (SELECT 1 FROM asset.copy_vis_attr_cache WHERE record = acn.record AND vis_attr_vector @@ c_attr.vis_test))
50     AND (NOT EXISTS (SELECT 1 FROM metabib.record_attr_vector_list WHERE source = acn.record AND vlist @@ metabib.compile_composite_attr(' {"1":[{"_val":"s","_attr":"bib_level"}]}')::query_int))
51     GROUP BY acn.record
52     ORDER BY MIN(AGE(circ.checkin_time))
53     LIMIT ?
54 );
55 my $top_circs_query = q(
56     WITH c_attr AS (SELECT c_attrs::query_int AS vis_test FROM asset.patron_default_visibility_mask() x)
57     SELECT acn.record AS bib
58     FROM asset.call_number acn
59     JOIN asset.copy acp ON (acp.call_number = acn.id)
60     JOIN asset.copy_location acpl ON (acp.location = acpl.id)
61     JOIN config.copy_status ccs ON (acp.status = ccs.id)
62     JOIN action.circulation circ ON (circ.target_copy = acp.id)
63     , c_attr
64     WHERE acn.owning_lib IN (ORG_LIST)
65     AND acp.circ_lib IN (ORG_LIST)
66     AND acp.holdable
67     AND acp.circulate
68     AND ccs.holdable
69     AND acpl.holdable
70     AND acpl.circulate
71     AND circ.xact_start > NOW() - ?::INTERVAL
72     AND (EXISTS (SELECT 1 FROM asset.copy_vis_attr_cache WHERE record = acn.record AND vis_attr_vector @@ c_attr.vis_test))
73     AND (NOT EXISTS (SELECT 1 FROM metabib.record_attr_vector_list WHERE source = acn.record AND vlist @@ metabib.compile_composite_attr(' {"1":[{"_val":"s","_attr":"bib_level"}]}')::query_int))
74     GROUP BY acn.record
75     ORDER BY COUNT(circ.id) DESC
76     LIMIT ?
77 );
78 my $new_by_loc_query = q(
79     WITH c_attr AS (SELECT c_attrs::query_int AS vis_test FROM asset.patron_default_visibility_mask() x)
80     SELECT acn.record AS bib
81     FROM asset.call_number acn
82     JOIN asset.copy acp ON (acp.call_number = acn.id)
83     JOIN asset.copy_location acpl ON (acp.location = acpl.id)
84     JOIN config.copy_status ccs ON (acp.status = ccs.id)
85     , c_attr
86     WHERE acn.owning_lib IN (ORG_LIST)
87     AND acp.circ_lib IN (ORG_LIST)
88     AND acp.active_date > NOW() - ?::INTERVAL
89     AND acp.location IN (LOC_LIST)
90     AND acp.holdable
91     AND acp.circulate
92     AND ccs.holdable
93     AND acpl.holdable
94     AND acpl.circulate
95     AND (EXISTS (SELECT 1 FROM asset.copy_vis_attr_cache WHERE record = acn.record AND vis_attr_vector @@ c_attr.vis_test))
96     AND (NOT EXISTS (SELECT 1 FROM metabib.record_attr_vector_list WHERE source = acn.record AND vlist @@ metabib.compile_composite_attr(' {"1":[{"_val":"s","_attr":"bib_level"}]}')::query_int))
97     GROUP BY acn.record
98     ORDER BY MIN(AGE(acp.active_date))
99     LIMIT ?
100 );
101
102 my %TYPE_QUERY_MAP = (
103     2 => $new_items_query,
104     3 => $recently_returned_query,
105     4 => $top_circs_query,
106     5 => $new_by_loc_query,
107 );
108
109 sub refresh_container_from_carousel_definition {
110     my $self = shift;
111     my $client = shift;
112     my $bucket = shift;
113     my $carousel_type = shift;
114     my $age = shift // '15 days';
115     my $libs = shift // [];
116     my $locs = shift // [];
117     my $limit = shift // 50;
118
119     my $e = OpenILS::Utils::CStoreEditor->new;
120     my $ctype = $e->retrieve_config_carousel_type($carousel_type) or return $e->die_event;
121     $e->disconnect;
122
123     unless (exists($TYPE_QUERY_MAP{$carousel_type})) {
124         $logger->error("Carousel for bucket $bucket is misconfigured; type $carousel_type is not recognized");
125         return 0;
126     }
127
128     my $query = $TYPE_QUERY_MAP{$carousel_type};
129
130     if ($ctype->filter_by_copy_owning_lib eq 't') {
131         if (scalar(@$libs) < 1) {
132             $logger->error("Carousel for bucket $bucket is misconfigured; owning library filter expected but none specified");
133             return 0;
134         }
135         my $org_placeholders = join(',', map { '?' } @$libs);
136         $query =~ s/ORG_LIST/$org_placeholders/g;
137     } else {
138         $libs = []; # we'll ignore any superflous supplied values
139     }
140
141     if ($ctype->filter_by_copy_location eq 't') {
142         if (scalar(@$locs) < 1) {
143             $logger->error("Carousel for bucket $bucket is misconfigured; copy location filter expected but none specified");
144             return 0;
145         }
146         my $loc_placeholders = join(',', map { '?' } @$locs);
147         $query =~ s/LOC_LIST/$loc_placeholders/g;
148     } else {
149         $locs = []; # we'll ignore any superflous supplied values
150     }
151
152     my $sth = container::biblio_record_entry_bucket_item->db_Main->prepare_cached($query);
153
154     $sth->execute(@$libs, @$libs, $age, @$locs, $limit);
155     my @bibs = ();
156     while (my $row = $sth->fetchrow_hashref ) {
157         push @bibs, $row->{bib};
158     }
159     container::biblio_record_entry_bucket_item->search( bucket => $bucket )->delete_all;
160     my $i = 0;
161     foreach my $bib (@bibs) {
162         container::biblio_record_entry_bucket_item->create({ bucket => $bucket, target_biblio_record_entry => $bib, pos => $i++ });
163     }
164     return scalar(@bibs);
165 }
166
167 __PACKAGE__->register_method(
168     api_name    => 'open-ils.storage.container.refresh_from_carousel',
169     method      => 'refresh_container_from_carousel_definition',
170     api_level   => 1,
171     cachable    => 1,
172 );
173
174 sub refresh_all_carousels {
175     my $self = shift;
176     my $client = shift;
177
178     my $e = OpenILS::Utils::CStoreEditor->new;
179
180     my $automatic_types = $e->search_config_carousel_type({ automatic => 't' });
181     my $carousels = $e->search_container_carousel({ type => [ map { $_->id } @$automatic_types ], active => 't' });
182
183     my $meth = $self->method_lookup('open-ils.storage.container.refresh_from_carousel');
184
185     foreach my $carousel (@$carousels) {
186
187         my $orgs = [];
188         my $locs = [];
189         if (defined($carousel->owning_lib_filter)) {
190             my $ou_filter = $carousel->owning_lib_filter;
191             $ou_filter =~ s/[{}]//g;
192             @$orgs = split /,/, $ou_filter;
193         }
194         if (defined($carousel->copy_location_filter)) {
195             my $loc_filter = $carousel->copy_location_filter;
196             $loc_filter =~ s/[{}]//g;
197             @$locs = split /,/, $loc_filter;
198         }
199
200         my @res = $meth->run($carousel->bucket, $carousel->type, $carousel->age_filter, $orgs, $locs, $carousel->max_items);
201         my $ct = scalar(@res) ? $res[0] : 0;
202
203         $e->xact_begin;
204         $carousel->last_refresh_time('now');
205         $e->update_container_carousel($carousel);
206         $e->xact_commit;
207
208         $client->respond({
209             carousel => $carousel->id,
210             bucket   => $carousel->bucket,
211             updated  => $ct
212         });
213
214     }
215     $e->disconnect;
216     return undef;
217 }
218
219 __PACKAGE__->register_method(
220     api_name    => 'open-ils.storage.carousel.refresh_all',
221     method      => 'refresh_all_carousels',
222     api_level   => 1,
223     stream      => 1,
224     cachable    => 1,
225 );
226
227
228 1;