elsif ($$params{'hold_type'} eq 'P') { $target_field = 'partid'; }
else { return undef; }
+ my $formats_map = delete $$params{holdable_formats_map};
+
foreach (@$target_list) {
$$params{$target_field} = $_;
+
+ # copy the requested formats from the target->formats map
+ # into the top-level formats attr for each hold
+ $$params{holdable_formats} = $formats_map->{$_};
+
my $res;
($res) = $self->method_lookup(
'open-ils.circ.title_hold.is_possible')->run($auth, $params, $override ? $oargs : {});
[$blob->{hold}->{bre_id}], undef, {flesh => '{mra}'}
);
$blob->{marc_xml} = $data[0]->{marc_xml};
+ my $hold = $blob->{hold}->{hold};
+ $blob->{metarecord_ccvms} = $self->get_mr_ccvms($hold->target)
+ if $hold->hold_type eq 'M';
push(@holds, $blob);
}
}
};
my $type_dispatch = {
+ M => sub {
+
+ # target metarecords
+ my $mrecs = $e->batch_retrieve_metabib_metarecord([
+ \@targets,
+ {flesh => 1, flesh_fields => {mmr => ['master_record']}}],
+ {substream => 1}
+ );
+
+ for my $id (@targets) {
+ my ($mr) = grep {$_->id eq $id} @$mrecs;
+
+ my $avail_attrs = $self->get_mr_ccvms($mr->id);
+ my $format_attr = $avail_attrs->{formats}{attr};
+
+ # during hold placement submission, the user selects
+ # which of the available formats/langs are acceptiable.
+ # Capture those here as the holdable_formats for the MR hold.
+ my @selected_formats = $cgi->param('metarecord_formats_' . $mr->id);
+ my @selected_langs = $cgi->param('metarecord_langs_' . $mr->id);
+
+ # map the selected attrs into the JSON holdable_formats structure
+ @selected_formats = map {
+ {_attr => $format_attr, _val => $_} } @selected_formats;
+ @selected_langs = map {
+ {_attr => 'item_lang', _val => $_} } @selected_langs;
+
+ my $holdable_formats = OpenSRF::Utils::JSON->perl2JSON({
+ 0 => \@selected_formats,
+ 1 => \@selected_langs
+ });
+
+ push(@hold_data, $data_filler->({
+ target => $mr,
+ record => $mr->master_record,
+ metarecord_ccvms => $avail_attrs,
+ holdable_formats => $holdable_formats
+ }));
+ }
+ },
T => sub {
my $recs = $e->batch_retrieve_biblio_record_entry(\@targets, {substream => 1});
if(@create_targets) {
+ # holdable formats may be different for each MR hold.
+ # map each set to the ID of the target.
+ my $holdable_formats = {};
+ if ($hold_type eq 'M') {
+ $holdable_formats->{$_->{target_id}} =
+ $_->{holdable_formats} for @hold_data;
+ # TODO: cleanup
+ warn "MR Holdable formats " . Dumper($holdable_formats) . "\n";
+ }
+
my $bses = OpenSRF::AppSession->create('open-ils.circ');
my $breq = $bses->request(
$method,
$e->authtoken,
- $data_filler->({ patronid => $usr,
+ $data_filler->({
+ patronid => $usr,
pickup_lib => $pickup_lib,
- hold_type => $hold_type
+ hold_type => $hold_type,
+ holdable_formats_map => $holdable_formats
}),
\@create_targets
);
}
}
+# fetches the CCVMs IDs representing available languages and formats
+# for the constituent records of the selected metarecord.
+sub get_mr_ccvms {
+ my ($self, $mr_id) = @_;
+ my $e = $self->editor;
+
+ # crad.name which defines the set of metarecord formats
+ my $format_attr = $self->ctx->{search_cgf}->( # leverage caching
+ name => 'opac.metarecord.holds.format_attr')->[0]->value;
+
+ my $bre_ids =
+ $e->search_metabib_metarecord_source_map({metarecord => $mr_id});
+ $bre_ids = [map {$_->source} @$bre_ids];
+
+ my $query = {
+ select => {ccvm => [{column => 'id', transform => 'distinct'}]},
+ from => {mraf => {
+ ccvm => {
+ fkey => 'attr',
+ field => 'ctype',
+ filter => {code => {'=' => {'+mraf' => 'value'}}}
+ }
+ }},
+ where => {'+mraf' => {id => $bre_ids, attr => ''}}
+ };
+
+ $query->{where}{'+mraf'}{attr} = 'item_lang';
+ my $langs = $e->json_query($query);
+
+ $query->{where}{'+mraf'}{attr} = $format_attr;
+ my $formats = $e->json_query($query);
+
+ return {
+ formats => {
+ attr => $format_attr,
+ values => [map { $_->{id} } @$formats],
+ },
+ langs => {
+ attr => 'item_lang',
+ values => [map { $_->{id} } @$langs]
+ }
+ };
+}
+
+
+
sub fetch_user_circs {
my $self = shift;
my $flesh = shift; # flesh bib data, etc.
foreach ($cgi->param('modifier')) {
# The unless bit is to avoid stacking modifiers.
- $query = ('#' . $_ . ' ' . $query) unless $query =~ qr/\#\Q$_/;
+ $query = ('#' . $_ . ' ' . $query) unless
+ $query =~ qr/\#\Q$_/ or $_ eq 'metabib';
}
# filters
my $ctx = $self->ctx;
my $e = $self->editor;
+ # 1. param->metarecord : view constituent bib records for a metarecord
+ # 2. param->modifier=metabib : perform a metarecord search
+ my $metarecord = $ctx->{metarecord} = $cgi->param('metarecord');
+ my @mods = $cgi->param('modifier');
+ my $is_meta = (@mods and grep {$_ eq 'metabib'} @mods and !$metarecord);
+ my $id_key = $is_meta ? 'mmr_id' : 'bre_id';
+
# find the last record in the set, then redirect
my $find_last = $cgi->param('find_last');
return $bbag_err;
}
- $ctx->{page} = 'rresult' unless $internal;
+ if (!$internal) {
+ $ctx->{page} = $is_meta ? 'mresult' : 'rresult';
+ }
$ctx->{ids} = [];
$ctx->{records} = [];
$ctx->{search_facets} = {};
$ctx->{search_ou} = $self->_get_search_lib();
$ctx->{pref_ou} = $self->_get_pref_lib() || $ctx->{search_ou};
my $offset = $page * $limit;
- my $metarecord = $cgi->param('metarecord');
my $results;
my $tag_circs = $self->tag_circed_items;
$self->timelog("Got search parameters");
$query = "$_ $query" for @facets;
- $logger->activity("EGWeb: [search] $query");
+ my $ltag = $is_meta ? '[mmr search]' : '[bre search]';
+ $logger->activity("EGWeb: $ltag $query");
try {
my $method = 'open-ils.search.biblio.multiclass.query';
$method .= '.staff' if $ctx->{is_staff};
+ $method =~ s/biblio/metabib/ if $is_meta;
my $ses = OpenSRF::AppSession->create('open-ils.search');
my $rec_id = pop @$rec_ids;
$cgi->delete('find_last');
my $url = $cgi->url(-full => 1, -path => 1, -query => 1);
+ # TODO: metarecord => /rresults?metarecord=$mmr_id
$url =~ s|/results|/record/$rec_id|;
return $self->generic_redirect($url);
}
$self->load_rresults_bookbag_item_notes($rec_ids) if $ctx->{bookbag};
+ my $fetch_recs = $rec_ids;
+
+ my $metarecord_master;
+ if ($metarecord) {
+ # when listing the contents of a metarecord, be sure to fetch
+ # the lead record for summary display. Adding the ID to
+ # $fetch_recs lets us grab the record (if necessary) w/o it
+ # unintentially becoming a member of the result set.
+ my $mr = $e->retrieve_metabib_metarecord($metarecord);
+ push(@$fetch_recs, $mr->master_record)
+ unless grep {$_ eq $mr->master_record} @$fetch_recs;
+ $metarecord_master = $mr->master_record;
+ }
+
$self->timelog("Calling get_records_and_facets()");
my ($facets, @data) = $self->get_records_and_facets(
- $rec_ids, $results->{facet_key},
+ $fetch_recs, $results->{facet_key},
{
flesh => '{holdings_xml,mra,acp,acnp,acns,bmp}',
site => $site,
+ metarecord => $is_meta,
depth => $depth,
pref_lib => $ctx->{pref_ou},
}
$self->timelog("Returned from get_records_and_facets()");
if ($page == 0) {
+ # TODO: handle metarecords
my $stat = $self->check_1hit_redirect($rec_ids);
return $stat if $stat;
}
# shove recs into context in search results order
for my $rec_id (@$rec_ids) {
- push(
- @{$ctx->{records}},
- grep { $_->{id} == $rec_id } @data
- );
+ my ($rec) = grep { $_->{$id_key} == $rec_id } @data;
+ push(@{$ctx->{records}}, $rec);
+
+ $ctx->{metarecord_master} = $rec
+ if $metarecord_master and $metarecord_master eq $rec_id;
}
if ($tag_circs) {
for my $rec (@{$ctx->{records}}) {
- my ($res_rec) = grep { $_->[0] == $rec->{id} } @{$results->{ids}};
+ my ($res_rec) = grep { $_->[0] == $rec->{$id_key} } @{$results->{ids}};
# index 1 in the per-record result array is a boolean which
# indicates whether the record in question is in the users
# accessible circ history list
$unapi_args->{depth} ||= $self->ctx->{aou_tree}->()->ou_type->depth;
$unapi_args->{flesh_depth} ||= 5;
+ my $is_meta = delete $unapi_args->{metarecord};
+ my $unapi_type = $is_meta ? 'unapi.mmr' : 'unapi.bre';
+
$unapi_cache ||= OpenSRF::Utils::Cache->new('global');
my $unapi_cache_key_suffix = join(
'_',
+ $is_meta || 0,
$unapi_args->{site},
$unapi_args->{depth},
$unapi_args->{flesh_depth},
$outer_self->timelog("get_records_and_facets(): got response content");
# Protect against requests for non-existent records
- return unless $data->{'unapi.bre'};
+ return unless $data->{$unapi_type};
- my $xml = XML::LibXML->new->parse_string($data->{'unapi.bre'})->documentElement;
+ my $xml = XML::LibXML->new->parse_string($data->{$unapi_type})->documentElement;
$outer_self->timelog("get_records_and_facets(): parsed xml");
# Protect against legacy invalid MARCXML that might not have a 901c
my $bre_id;
+ my $mmr_id;
my $bre_id_nodes = $xml->find('*[@tag="901"]/*[@code="c"]');
if ($bre_id_nodes) {
$bre_id = $bre_id_nodes->[0]->textContent;
} else {
$logger->warn("Missing 901 subfield 'c' in " . $xml->toString());
}
- $tmp_data{$bre_id} = {id => $bre_id, marc_xml => $xml};
- if ($bre_id) {
+ if ($is_meta) {
+ # extract metarecord ID from mmr.unapi tag
+ for my $node ($xml->getElementsByTagName('abbr')) {
+ my $title = $node->getAttribute('title');
+ ($mmr_id = $title) =~
+ s/tag:open-ils.org:U2\@mmr\/(\d+)\/.*/$1/g;
+ last if $mmr_id;
+ }
+ }
+
+ my $rec_id = $mmr_id ? $mmr_id : $bre_id;
+ $tmp_data{$rec_id} = {
+ id => $rec_id,
+ bre_id => $bre_id,
+ mmr_id => $mmr_id,
+ marc_xml => $xml
+ };
+
+ if ($rec_id) {
# Let other backends grab our data now that we're done.
- my $key = 'TPAC_unapi_cache_'.$bre_id.'_'.$unapi_cache_key_suffix;
+ my $key = 'TPAC_unapi_cache_'.$rec_id.'_'.$unapi_cache_key_suffix;
my $cache_data = $unapi_cache->get_cache($key);
if ($$cache_data{running}) {
- $unapi_cache->put_cache($key, { id => $bre_id, marc_xml => $data->{'unapi.bre'} }, 10);
+ $unapi_cache->put_cache($key, {
+ bre_id => $bre_id,
+ mmr_id => $mmr_id,
+ id => $rec_id,
+ marc_xml => $data->{$unapi_type}
+ }, 10);
}
}
-
$outer_self->timelog("get_records_and_facets(): end of success handler");
}
);
- $self->timelog("get_records_and_facets(): about to call unapi.bre via json_query (rec_ids has " . scalar(@$rec_ids));
+ $self->timelog("get_records_and_facets(): about to call ".
+ "$unapi_type via json_query (rec_ids has " . scalar(@$rec_ids));
my @loop_recs = @$rec_ids;
my %rec_timeout;
$tmp_data{$unapi_data->{id}} = $unapi_data;
} else { # we're the first or we timed out. success_handler will populate the real value
$unapi_cache->put_cache($unapi_cache_key, { running => $$ }, 10);
+
+ my $sdepth = $unapi_args->{flesh_depth};
+ my $slimit = "acn=>$sdepth,acp=>$sdepth";
+ $slimit .= ",bre=>$sdepth" if $is_meta;
+ my $flesh = $unapi_args->{flesh} || '';
+
+ # tag the record with the MR id
+ $flesh =~ s/}$/,mmr.unapi}/g if $is_meta;
+
$ses->request(
'open-ils.cstore.json_query',
{from => [
- 'unapi.bre', $bid, 'marcxml','record',
- $unapi_args->{flesh},
+ $unapi_type, $bid, 'marcxml','record', $flesh,
$unapi_args->{site},
$unapi_args->{depth},
- 'acn=>' . $unapi_args->{flesh_depth} . ',acp=>' . $unapi_args->{flesh_depth},
+ $slimit,
undef, undef, $unapi_args->{pref_lib}
]}
);
}
-
}
PROCESS "opac/parts/misc_util.tt2";
PROCESS "opac/parts/hold_status.tt2";
PROCESS "opac/parts/org_selector.tt2";
+ PROCESS "opac/parts/metarecord_hold_filters.tt2";
WRAPPER "opac/parts/myopac/base.tt2";
myopac_page = "holds"; # in this case, just for tab coloring.
<strong>[% l('Format:') %]</strong>
<img src="[% attrs.format_icon %]" alt="[% attrs.format_label | html %]" title="[% attrs.format_label | html %]" />
</p>[% END %]
+ [% IF hold.metarecord_ccvms.formats.values.size OR # should this be size > 1
+ hold.metarecord_ccvms.langs.values.size > 1;
+ PROCESS metarecord_hold_filters_selector hold_data=hold;
+ END %]
+
<p>
<strong>[% l('Status') %]</strong>: [% hold.human_status %]
</p>
CASE "sort_selector";
INCLUDE "opac/parts/filtersort.tt2"
value=CGI.param('sort') class='results_header_sel';
+ %]
- CASE "copy_location" %]
+ <!-- applies metarecord search -->
+ <div class="adv_search_available">
+ <input type='checkbox' name="modifier" value="metabib"
+ [%- CGI.param('modifier').grep('metabib').size ?
+ ' checked="checked"' : '' %]
+ id='opac.result.ismetabib' />
+ <label for='opac.result.ismetabib'>
+ [% l("Group Formats and Editions") %]</label>
+ </div>
+
+ [% CASE "copy_location" %]
<select id="adv_copy_location_selector"
aria-label="[% l('Select Shelving Location') %]"
name="fi:locations" size="3" multiple="multiple">
--- /dev/null
+[%#
+Draws the format multi-select and the language multi-select for
+limiting the set of desired records for a given metarecord.
+%]
+
+<style>
+ /* TODO: MOVE ME */
+ .metarecord_filters {
+ padding: 5px;
+ margin-top: 5px;
+ border-bottom: 1px solid #333;
+ border-top: 1px solid #333;
+ }
+ .metarecord_filter_container {
+ float : left;
+ margin-right: 10px;
+ }
+ .metarecord_filter_container select {
+ padding: 2px;
+ width: 13em; /* consistent w/ adv search selectors */
+ }
+ .metarecord_filter_header {
+ padding-bottom: 5px;
+ }
+</style>
+
+[% BLOCK metarecord_hold_filters_selector;
+ format_ccvms = [];
+ lang_ccvms = [];
+ # some CCVMs have search_labels, some use the value as the label
+ # instead. Accommodate both, starting with search_label
+ FOR ccvm_id IN hold_data.metarecord_ccvms.formats.values;
+ ccvm = ctx.get_ccvm(ccvm_id);
+ CALL ccvm.search_label(ccvm.value) UNLESS ccvm.search_label;
+ format_ccvms.push(ccvm);
+ END;
+ FOR ccvm_id IN hold_data.metarecord_ccvms.langs.values;
+ ccvm = ctx.get_ccvm(ccvm_id);
+ CALL ccvm.search_label(ccvm.value) UNLESS ccvm.search_label;
+ lang_ccvms.push(ccvm);
+ END;
+%]
+
+<div class="metarecord_filters">
+ <div class="metarecord_filter_container">
+ <div class="metarecord_filter_header">
+ <div>[% l('Select your desired format(s).') %]</div>
+ </div>
+ <select multiple='multiple'
+ name="metarecord_formats_[% hold_data.target.id %]">
+ [% FOR ccvm IN format_ccvms.sort('search_label') %]
+ <option value="[% ccvm.code %]">
+ [% ccvm.search_label | html %]
+ </option>
+ [% END %]
+ </select>
+ <input type="hidden"
+ name="metarecord_format_attr" value="[% format_ccvms.0.ctype %]"/>
+ </div>
+ <div class="metarecord_filter_container">
+ <div class="metarecord_filter_header">
+ [% l('Select your desired language(s)') %]
+ </div>
+ <select multiple='multiple'
+ name="metarecord_langs_[% hold_data.target.id %]">
+ [% FOR lang_ccvm IN lang_ccvms.sort('search_label') %]
+ <option value="[% lang_ccvm.code %]">
+ [% lang_ccvm.search_label | html %]
+ </option>
+ [% END %]
+ </select>
+ </div>
+ <div class="clear-both"> </div>
+</div>
+[% END # metarecord_hold_filters_selector %]
[% PROCESS "opac/parts/misc_util.tt2";
PROCESS "opac/parts/hold_error_messages.tt2";
+ PROCESS "opac/parts/metarecord_hold_filters.tt2";
%]
<div id='holds_box' class='canvas' style='margin-top: 6px;'>
<input type='hidden' name='part' value=''/>
[% END %]
[% END %]
+ [% IF hdata.metarecord_ccvms.formats.values.size OR # should this be size > 1
+ hdata.metarecord_ccvms.langs.values.size > 1;
+ PROCESS metarecord_hold_filters_selector hold_data=hdata;
+ END %]
</td>
</tr>
[% END %]
IF CGI.param('detail_record_view');
attrs.title = attrs.title_extended;
END;
+ # note: rec.id refers to the record identifier, regardless
+ # of the type of record. i.e. rec.id = mmr_id ? mmr_id : bre_id
+ IF rec.mmr_id;
+ # metarecords link to record list page
+ record_url = mkurl(ctx.opac_root _ '/results',
+ {metarecord => rec.mmr_id}, ['page']);
+ hold_type = 'M';
+ ELSE;
+ record_url = mkurl(ctx.opac_root _ '/record/' _ rec.bre_id);
+ hold_type = 'T';
+ END;
-%]
<tr class="result_table_row">
<td class="results_row_count" name="results_row_count">[%
result_count; result_count = result_count + 1
%].</td>
<td class='result_table_pic_header'>
- <a href="[% mkurl(ctx.opac_root _ '/record/' _ rec.id) %]"><img alt="[% l('Image of item') %]"
+ <a href="[% record_url %]"><img alt="[% l('Image of item') %]"
name='item_jacket' class='result_table_pic' width="55"
- src='[% ctx.media_prefix %]/opac/extras/ac/jacket/small/r/[% rec.id | uri %]' /></a><br />
+ src='[% ctx.media_prefix %]/opac/extras/ac/jacket/small/r/[% rec.bre_id | uri %]' /></a><br />
</td>
<td class='result_table_title_cell' name='result_table_title_cell'>
<div class="result_metadata">
- <abbr class="unapi-id" title='tag:[% ctx.hostname %],[% date.format(date.now, '%Y') %]:biblio-record_entry/[% rec.id %]'></abbr>
+ [% IF rec.mmr_id %]
+ <abbr class="unapi-id"
+ title='tag:[% ctx.hostname %],[% date.format(date.now, '%Y') %]:metabib-metarecord/[% rec.mmr_id %]'>
+ </abbr>
+ [% ELSE %]
+ <abbr class="unapi-id"
+ title='tag:[% ctx.hostname %],[% date.format(date.now, '%Y') %]:biblio-record_entry/[% rec.bre_id %]'>
+ </abbr>
+ [% END %]
<a class='record_title' name='record_[% rec.id %]' name='item_title'
- href="[% mkurl(ctx.opac_root _ '/record/' _ rec.id) %]"
+ href="[% record_url %]"
[% HTML.attributes(title => l('Display record details for "[_1]"', attrs.title)) %]
class='search_link'>[% attrs.title | html %]</a>
[%-
%]
<div class="results_aux_utils place_hold"><a
href="[% mkurl(ctx.opac_root _ '/place_hold',
- {hold_target => rec.id, hold_type => 'T', hold_source_page => mkurl()}, ['query']) %]"
+ {hold_target => rec.id, hold_type => hold_type,
+ hold_source_page => mkurl()}, ['query']) %]"
name="place_hold_link" class="no-dec"><img
src="[% ctx.media_prefix %]/images/green_check.png"
alt=""/><span class="result_place_hold">[% l('Place Hold') %]</span></a>
<form action="[% ctx.opac_root %]/results" method="get">
[% INCLUDE "opac/parts/searchbar.tt2" took_care_of_form=1 %]
<div class="almost-content-wrapper">
- <div id="results_header_bar">
+
+ [%# hide the header bar when displaying metarecord constituents
+ instead of skipping it altogether to allow the search form
+ variables to propagate %]
+ [% IF ctx.metarecord;
+ mr_attrs = {marc_xml => ctx.metarecord_master.marc_xml};
+ PROCESS get_marc_attrs args=mr_attrs %]
+ <div class="results_header_lbl">
+ [% l('Viewing Results for Grouped Record: [_1]',
+ mr_attrs.title) | html %]
+ </div>
+ [% END %]
+ <div class="results_header_bar[%- IF ctx.metarecord %] hidden[% END -%]">
<div id="results_header_inner">
<div class="results_header_btns">
<a href="[% mkurl(ctx.opac_root _ '/home', {$loc_name => loc_value}, 1) %]">[% l('Another Search') %]</a>
<label class="results_header_lbl">
<input type="checkbox" id="limit_to_available" name="modifier" value="available"
- onchange="limit_to_avail_onchange(this, true)"
+ onchange="search_modifier_onchange('available', this, true)"
[% CGI.param('modifier').grep('available').size ? ' checked="checked"' : '' %] />
[% l('Limit to available items') %]
</label>
+ <label class="results_header_lbl">
+ <input type="checkbox" name="modifier" value="metabib"
+ onchange="search_modifier_onchange('metabib', this, true)"
+ [% CGI.param('modifier').grep('metabib').size ? ' checked="checked"' : '' %] />
+ [% l('Group Formats and Editions') %]
+ </label>
[% IF CGI.param('detail_record_view') %]
<input type="hidden" name="detail_record_view" value="1" />
[% END %]
}
}
-function limit_to_avail_onchange(checkbox, submitOnChange) {
+function search_modifier_onchange(type, checkbox, submitOnChange) {
if (checkbox.form._adv && !checkbox.checked) {
var search_box = $('search_box');
- search_box.value = search_box.value.replace(/#available ?/g, "");
+ var reg = new RegExp('#' + type + ' ?', 'g');
+ search_box.value = search_box.value.replace(reg, "");
}
if (submitOnChange) {