my $U = "OpenILS::Application::AppUtils";
-my %MFHD_SUMMARIZED_SUBFIELDS = (
- enum => [ split //, "abcdef" ], # $g and $h intentionally omitted for now
- chron => [ split //, "ijklm" ]
-);
+use OpenILS::Const qw/:const/;
+#OILS_MFHD_SUMMARIZED_SUBFIELDS is defined in OpenILS/Const.pm
+my $mfhd_summarized_subfields_ref = OILS_MFHD_SUMMARIZED_SUBFIELDS;
+my %MFHD_SUMMARIZED_SUBFIELDS = %{$mfhd_summarized_subfields_ref};
# This is a helper for scoped_holding_summary_tree_for_bib() a little further down
econst OILS_STOP_FINES_CLAIMS_NEVERCHECKEDOUT => 'CLAIMSNEVERCHECKEDOUT';
econst OILS_UNLIMITED_CIRC_DURATION => 'unlimited';
+# _____________________________________________________________________
+# Serial's Constants
+# _____________________________________________________________________
+econst OILS_MFHD_SUMMARIZED_SUBFIELDS => {
+ enum => [ split //, "abcdef" ], # $g and $h intentionally omitted for now
+ chron => [ split //, "ijklm" ]
+ };
+
# ---------------------------------------------------------------------
# Settings
# ---------------------------------------------------------------------
econst OILS_SETTING_LOST_IMMEDIATELY_AVAILABLE => 'circ.lost_immediately_available';
econst OILS_SETTING_BLOCK_HOLD_FOR_EXPIRED_PATRON => 'circ.holds.expired_patron_block';
econst OILS_SETTING_GENERATE_OVERDUE_ON_LOST_RETURN => 'circ.lost.generate_overdue_on_checkin';
-
+econst OILS_SETTING_MAX_LIMIT => '5000';
$ctx->{authtoken} = $e->authtoken;
$ctx->{authtime} = $e->authtime;
$ctx->{user} = $e->requestor;
+ #In the Staff Client, the org unit registered to the workstation is not always the same as the locg
+ $ctx->{ws_ou} = $ctx->{user}->ws_ou if $ctx->{is_staff};
$ctx->{place_unfillable} = 1 if $e->requestor->wsid && $e->allowed('PLACE_UNFILLABLE_HOLD', $e->requestor->ws_ou);
# The browser client does not set an OILS-Wrapper header (above).
use OpenILS::Utils::CStoreEditor qw/:funcs/;
use OpenILS::Utils::Fieldmapper;
use OpenILS::Application::AppUtils;
+use OpenILS::Const qw/:const/;
use Net::HTTP::NB;
use IO::Select;
my $U = 'OpenILS::Application::AppUtils';
my $copy_offset = shift;
my $pref_ou = shift;
+ #add one to the copy limit, so we can peak ahead in copy_table.tt2 to see if we
+ #should display a Next link
+ $copy_limit = $copy_limit + 1;
+
my $query = $U->basic_opac_copy_query(
$rec_id, undef, undef, $copy_limit, $copy_offset, $self->ctx->{is_staff}
);
+ my $lse_org = $org;
+ if ($self->ctx->{is_staff}) {
+ $lse_org = $self->ctx->{ws_ou};
+ }
+
+ #used to determine the paramters for the ORDER BY clauses
+ my $sort_type = $self->ctx->{get_org_setting}->($lse_org, 'opac.holding_sort_type');
+
+ #return the number of non-delted items in the serial.item
+ #table for this record
+ my $sitem_count_query = {
+ select => {
+ sitem => [{
+ column => 'id',
+ transform => 'count',
+ alias => 'id_count'
+ }]
+ },
+ from => {
+ sitem => {
+ siss => {
+ join => {
+ ssub => {}
+ }
+ },
+ sunit => {}
+ }
+ },
+ where => {
+ '+ssub' => {
+ record_entry => $rec_id
+ },
+ '-not' => {
+ '+sunit' => 'deleted'
+ }
+ }
+ };
+
+ #return the number of non-deleted items
+ #in the asset.copy table for the current record
+ my $acp_count_query = {
+ select => {
+ acp => [{
+ column => 'id',
+ transform => 'count',
+ alias => 'id_count'
+ }]
+ },
+ from => {
+ acp => {
+ acn => {}
+ }
+ },
+ where => {
+ '+acn' => {
+ record => $rec_id
+ },
+ '-not' => {
+ '+acp' => 'deleted'
+ }
+ }
+ };
+
+ my $sitem_count = $self->{editor}->json_query($sitem_count_query);
+ my $acp_count = $self->{editor}->json_query($acp_count_query);
+ my $is_serial = 0;
+
+ #if the number of items in the serial.items table == the number of items in the
+ #asset.copy table for this record then we have a pure serial record containing
+ #only items added through the serials module, so we can use the
+ #rank_serial_copy_by_issuance function below to sort the items in ascending or
+ #descending order based on the enumeration or chronology data in the subscription.
+ #Otherwise, if sorting by date the code will use active_date.
+ if ($sitem_count->[0]->{'id_count'} == $acp_count->[0]->{'id_count'}) {
+ $is_serial = 1;
+ }
+
+ if ($is_serial) {
+ #this addition to the query adds the necessary fields for the
+ #rank_serial_copy_by_issuance plpgsql function to work properly
+
+ $query->{select}->{aou} = [ {column => 'id', alias => 'ou_id'} ];
+ $query->{select}->{smhc} = [ {column => 'issuance'} ];
+ $query->{from}->{acp}->{sitem} = {
+ fkey => 'id',
+ field => 'unit',
+ join => {
+ smhc => {
+ fkey => 'issuance',
+ field => 'issuance',
+ join => {
+ siss => {
+ fkey => 'issuance',
+ join => {
+ ssub => {
+ fkey => 'subscription',
+ join => {
+ sdist => {
+ fkey => 'id',
+ field => 'subscription',
+ filter => {
+ holding_lib => {
+ in => {
+ select => {aou => [{
+ column => 'id',
+ transform => 'actor.org_unit_descendants',
+ result_field => 'id',
+ params => [$depth]
+ }]},
+ from => 'aou',
+ where => {id => $org}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ };
+ $query->{distinct} = 1;
+ }
+
if($org != $self->ctx->{aou_tree}->()->id) {
# no need to add the org join filter if we're not actually filtering
$query->{from}->{acp}->{aou} = {
}
}
};
- };
+ }
# Unsure if we want these in the shared function, leaving here for now
- unshift(@{$query->{order_by}},
- { class => "aou", field => 'id',
- transform => 'evergreen.rank_ou', params => [$org, $pref_ou]
+
+ if ($is_serial) {
+ #create a string to represent the OILS_MFHD_SUMMARIZED_SUBFIELDS constant
+ #defined in OpenILS/Const.pm
+ my $rank_serial_copy_by_issuance_subfields_string = '';
+
+ my $mfhd_summarized_subfields_ref = OILS_MFHD_SUMMARIZED_SUBFIELDS;
+ my %MFHD_SUMMARIZED_SUBFIELDS = %{$mfhd_summarized_subfields_ref};
+
+ #generate a string to be passed in as a parameter to rank_serial_copy_by_issuance
+ #this is to be used by plpgsql to create an HSTORE. its values are deliminated by a carrot ^.
+ foreach my $key (keys %MFHD_SUMMARIZED_SUBFIELDS) {
+ if ($rank_serial_copy_by_issuance_subfields_string ne '') {
+ $rank_serial_copy_by_issuance_subfields_string .= '^';
+ }
+
+ $rank_serial_copy_by_issuance_subfields_string .= $key;
+ $rank_serial_copy_by_issuance_subfields_string .= '^' . join(',',@{$MFHD_SUMMARIZED_SUBFIELDS{$key}});
}
- );
- push(@{$query->{order_by}},
- { class => "acp", field => 'status',
- transform => 'evergreen.rank_cp_status'
+
+ #this code orders pure serials records' items according
+ #to the enumeration or chronology values provided by
+ #the serials module. rank_serial_copy_by_issuance
+ #calculates the ordering.
+ if ($sort_type eq 'asc' or $sort_type eq 'desc') {
+ unshift(@{$query->{order_by}}, {
+ class => "smhc", field => "issuance",
+ transform => 'evergreen.rank_serial_copy_by_issuance',
+ params => [$rank_serial_copy_by_issuance_subfields_string],
+ ($sort_type eq 'desc' ? (direction => 'desc') : ())
+ }
+ );
+
+ #at this point we only have ORDER BY rank_serial_copy_by_issuance
+ #and aou.name and acn.label. We need aou.name to come before
+ #rank_serial_copy_by_issuance, so the org_units are ordered
+ #alphabetically, within their ranking (the code for the rankings
+ #gets unshifted on furhter down.. We remove aou.name and
+ #unshift it back on so it comes before rank_serial_copy_by_issuance.
+ #We do this rather than a simple removal of the second item in the
+ #array because the original array is defined elsewhere and could be
+ #modified independetly of this file.
+ my $index = 0;
+ my @order_by_array = @{$query->{order_by}};
+ my @new_order_by_array = ();
+ foreach my $order_by_clause (@order_by_array) {
+ foreach my $key (keys %{$order_by_clause}) {
+ if ($key eq 'class' and $order_by_clause->{$key} eq 'aou') {
+ my $aou_name_order_by = $order_by_clause;
+ my @end_part_of_query_order_by = @order_by_array[($index + 1)..$#order_by_array];
+
+ push(@new_order_by_array, @end_part_of_query_order_by);
+ unshift(@new_order_by_array, $aou_name_order_by);
+
+ #return memory used by current order_by to Perl
+ @{$query->{order_by}} = undef;
+ @order_by_array = undef;
+
+ $query->{order_by} = \@new_order_by_array;
+ last;
+ }
+ }
+
+ if (@order_by_array != undef) {
+ push(@new_order_by_array, $order_by_clause);
+ }
+
+ $index++;
+ }
+ }
+ }
+
+ my $search_lib_parent = $self->ctx->{get_aou}->($org)->parent_ou;
+
+ #If the search_lib_parent is defined then we are not at the top org_unit.
+ #Otherwise, if we are at the top org unit but the pref_ou is not the
+ #top org unit or we are not searching the top org_unit, then we will rank
+ #the OUs by distance.
+ #However, in the case that we are searching the top OU with a pref_lib
+ #we will only order the org_units within the pref_lib by calling
+ #rank_ou_root_search_with_pref_lib instead of rank_ou.
+ #Otherwise, if we are searching the top OU and the pref_lib is the top OU
+ #then we rank on name only, which is done by skiping this code leaving
+ #aou.name in the ORDER BY without a corresponding OU rank.
+ if (defined $search_lib_parent || $pref_ou != $org) {
+ unshift(@{$query->{order_by}}, ({
+ class => "aou", field => 'id',
+ (!defined $search_lib_parent ?
+ (transform => 'evergreen.rank_ou_root_search_with_pref_lib', params => [$org, $pref_ou])
+ :
+ (transform => 'evergreen.rank_ou', params => [$org, $pref_ou])
+ )
+ })
+ );
+ }
+
+ push(@{$query->{order_by}}, {
+ class => "acp", field => 'status',
+ transform => 'evergreen.rank_cp_status'
}
);
+ #IF the LSE sort order is asc or desc then
+ #add the apporpriate active_date ORDER BY.
+ #We add active_date even when sorting pure
+ #serial records via rank_serial_copy_by_issuance,
+ #so that records with multiple copies of the same item
+ #are ordered according to their date.
+ if ($sort_type eq 'asc' or $sort_type eq 'desc') {
+ my $index = 0;
+ foreach my $order_by_clause (@{$query->{order_by}}) {
+ foreach my $key (keys %{$order_by_clause}) {
+ if ($key eq 'class' and $order_by_clause->{$key} eq 'acn') {
+ my $new_order_by = {
+ class => 'acp', field => 'active_date',
+ ($sort_type eq 'desc' ? (direction => 'desc') : ())
+ };
+ #this replaces the current order by with a new reference
+ $order_by_clause = undef;
+ $order_by_clause = $new_order_by;
+
+ #add a order by acp.id after active date, so that items
+ #with identical active_dates (e.g. items added via the serials module)
+ #are ordered in the way they were added to the database
+ my $acp_id_order_by = {
+ class => 'acp', field => 'id',
+ ($sort_type eq 'desc' ? (direction => 'desc') : ())
+ };
+
+ my @order_by_array = @{$query->{order_by}};
+ my @begining_part_of_query_order_by = @order_by_array[0..$index];
+ my @end_part_of_query_order_by = @order_by_array[($index + 1)..$#order_by_array];
+
+ @order_by_array = @begining_part_of_query_order_by;
+ push(@order_by_array, $acp_id_order_by);
+ push(@order_by_array, @end_part_of_query_order_by);
+
+ #return memory used by current order_by to Perl
+ @{$query->{order_by}} = undef;
+
+ $query->{order_by} = \@order_by_array;
+ }
+ }
+ $index++;
+ }
+ }
+
return $query;
}
);
$self->timelog("Returned from get_records_and_facets()");
+ #try and get holdings
+ #should these values be passed in to this subroutine?
+ my $copy_offset = 0;
+ my $copy_limit = 5;
+ my $copy_depth = $depth;
+ my $cstore = OpenSRF::AppSession->create('open-ils.cstore');
+ $cstore->session_locale($OpenILS::Utils::CStoreEditor::default_locale);
+ for my $rec_id (@$rec_ids) {
+ my $copy_rec = $cstore->request(
+ 'open-ils.cstore.json_query.atomic',
+ $self->mk_copy_query($rec_id, $ctx->{search_ou}, $copy_depth, $copy_limit, $copy_offset, $ctx->{pref_ou})
+ );
+ my $copies = $copy_rec->gather(1);
+ $ctx->{holdings}->{$rec_id} = $copies;
+ }
+
if ($page == 0 and @$rec_ids == 1) {
my $stat = 0;
if ($is_meta) {
'coust', 'description'),
'bool');
+INSERT INTO config.org_unit_setting_type
+ (name, label, datatype, description, grp, update_perm, view_perm)
+VALUES (
+ 'opac.holding_sort_type',
+ oils_i18n_gettext(
+ 'opac.holding_sort_type',
+ 'Specify how items are ordered',
+ 'coust',
+ 'label'
+ ),
+ 'string',
+ oils_i18n_gettext(
+ 'opac.holding_sort_type',
+ 'This value specifies how items are ordered in search results and record views within the org unit. To sort from newest to oldest by active date use ''desc''. To sort from oldest to newest by active date use ''asc''. To sort by call number use ''call''.',
+ 'coust',
+ 'description'
+ ),
+ 'opac',
+ 93,
+ 192);
+
);
$$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION evergreen.rank_ou_root_search_with_pref_lib(lib INT, search_lib INT, pref_lib INT DEFAULT NULL)
+RETURNS INTEGER AS $$
+
+ -- In the case of ranking OUs in holdings displays
+ -- If the top level OU is the search lib,
+ -- meaning the search_lib's parent_ou is NULL, then
+ -- we only want to give special rankings to items
+ -- related to the pref_lib.
+ SELECT COALESCE(
+
+ -- lib matches pref_lib
+ (SELECT CASE WHEN $1 = $3 THEN -10000 END),
+
+
+ -- pref_lib is a child of search_lib and lib is a child of pref lib.
+ (SELECT distance - 5000
+ FROM actor.org_unit_descendants_distance($3)
+ WHERE id = $1 AND $3 IN (
+ SELECT id FROM actor.org_unit_descendants($2))),
+
+ -- all others pay cash
+ 1000
+ );
+$$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION rank_serial_copy_by_issuance(issuance_id INT, groupings TEXT) RETURNS NUMERIC AS $$
+DECLARE
+ row RECORD;
+ rank NUMERIC;
+ display_subfields TEXT;
+ grouping_array TEXT[];
+ grouping_store HSTORE;
+ more_weight INT;
+ weight INT;
+ weight_length INT;
+ row_text TEXT;
+ row_value INT;
+ rank_decimal_length INT;
+ display_grouping TEXT;
+BEGIN
+
+ -- get the display_grouping so we know how to order this
+ SELECT sdist.display_grouping FROM serial.distribution AS sdist
+ INNER JOIN serial.subscription AS ssub ON (sdist.subscription = ssub.id)
+ INNER JOIN serial.issuance AS siss ON (ssub.id = siss.subscription)
+ WHERE siss.id = issuance_id INTO display_grouping;
+
+ grouping_array := string_to_array(groupings, '^');
+
+ grouping_store := hstore(grouping_array);
+
+ display_subfields := grouping_store->display_grouping;
+
+ -- It is possible for the first level of enumeration to be 0.
+ -- So, we initialize rank to -1 to indicate that it has not been set yet
+ rank := -1;
+
+ -- If we are grouping by chron, but there is no enum defined in this subscription's
+ -- capture patter, then the chron values will be recorded in enum's a-f
+ IF (display_grouping = 'chron') THEN
+ IF (SELECT NOT EXISTS(SELECT value FROM serial.materialized_holding_code WHERE issuance = issuance_id AND subfield IN (SELECT * FROM unnest(string_to_array(display_subfields, ','))))) THEN
+ display_grouping = 'enum';
+ display_subfields := grouping_store->display_grouping;
+ END IF;
+ END IF;
+
+ FOR row IN SELECT regexp_replace(value, '^\D*(\d*).*$', '\1') AS value FROM serial.materialized_holding_code WHERE issuance = issuance_id AND subfield IN (SELECT * FROM unnest(string_to_array(display_subfields, ','))) ORDER BY subfield LOOP
+ row_text := row.value;
+
+ -- strip leading 0s from the subfield values, unless we have a value of 0 or the empty string
+ -- If we have an emptry string, then there was no digit present int materialzed_holding_code, so we default to a value of 0
+ IF (row_text != '0' AND row_text != '') THEN
+ row_value := trim(leading '0' from row_text)::INT;
+ ELSE
+ row_value = 0;
+ END IF;
+
+ IF (rank = -1) THEN
+ rank := row_value;
+ ELSE
+ -- Determine weight.
+ -- Here we multiply by 2 to get an even number, so that when
+ -- we add 1 we are assured of getting an odd number.
+ -- This avoids having a number that ends in 0, which would cause the
+ -- loss of a significant digit because these values are being
+ -- appended after the decimal place. It also allows us room
+ -- to play with the weight value to ensure it never begins with a 9.
+ weight := row_value * 2 + 1;
+
+ -- Because a row_value of 1 becomes a weight of 3, we can assign a row_value of
+ -- 0 to a weight of 1. This means 0 values in the MFHD data will be sorted properly.
+ -- If they are left at 0, this information will be lost because the weight is added
+ -- to the manitissa.
+ IF (weight = 0) THEN
+ weight = 1;
+ END IF;
+
+ -- We use 9's to deliminate that start of a new level of
+ -- enumeration or chronology in the mantissa.
+ -- We add a number of 9's equal to that of the legnth of weight (row_value * 2 + 1),
+ -- which is the number we will be appending to the mantissa, to the beginning of weight.
+ weight_length := length(weight::TEXT);
+
+ -- Determine how many 9s to add to the weight.
+ -- If we are adding a level with a value over 9 then we have to add
+ -- a requisite number of 9's in front of the derived weight to ensure the
+ -- rank is valid. If the value is less than 9, then we do not add
+ -- a 9 because we subtract 10 ^ (legnth of weight - 1) from weights
+ -- derived form values over 9 (This is explained below).
+ -- This means we do not want 9's in front of values < 9 because that would
+ -- move them above items they should follow in the rankings
+ more_weight := 0;
+
+ IF (row_value > 9) THEN
+ -- We loop from 0 to row_length -2 because we are trying to determine how many
+ -- 9s to append to the mantissa.
+ -- For example if row_value is 10, then we want to append one 9 to the mantissa,
+ -- so we go through this loop once.
+ -- We want to append a 9 to the mantisaa to ensure this number is placed before
+ -- numbers underneath 10.
+ -- For instance, if the volume number is 2 and we are dealinig with issue 10,
+ -- then rank is currently = 2.0. We are going to append a value of
+ -- 10 * 2 + 1 - 10 to rank.
+ -- However, to ensure that 10 comes after numbers < 10 we append a single .9 to the mantissa.
+ -- After this loop is done, in the case of a value of 10, but before we add the final
+ -- calcuated value to rank, we would have a rank of 2.9. This ensures that this number is always
+ -- going to be greater than 2.03 - 2.19 which would represent volume 2 issues 1 to 9 respectively.
+ -- In the case of 10, the final rank would be 2.911.
+ FOR exponent IN 0 .. (weight_length - 2) LOOP
+ more_weight := more_weight + (9 * (10 ^ exponent));
+ END LOOP;
+ END IF;
+
+ -- Get the length of the current mantissa so we know how many 0's to place in front
+ -- of more_weight.
+ rank_decimal_length := length(rtrim(split_part(rank::TEXT, '.', 2), '0'));
+
+ -- Subtracting 10 ^ (legnth of weight - 1) from weight ensures that weight never starts with a 9.
+ -- We never add a weight starting with a 9 because 9 represents a new level in the enumeration
+ -- or chronology. When row_value is less than 10 we have a special case and weight is not decremented.
+ -- In these cases 1 = 3, 2 = 5 .. 9 = 19, values less than 9 do not have 9's prepended to them.
+ IF (row_value > 9) THEN
+ weight := weight - (10 ^ (length(weight::TEXT) - 1));
+ END IF;
+
+ -- Add a number of 0's to the end of more_weight so that weight
+ -- is appended to more_weight
+ IF (more_weight > 0) THEN
+ more_weight := more_weight * (10 ^ (length(weight::TEXT)));
+
+ weight := weight + more_weight;
+ END IF;
+
+ -- The rank_decimal_length + length of weight tells us how many decimal places to shift
+ -- weight, so it will be added to the end of the mantissa.
+ IF (weight > 10) THEN
+ rank := rank + (weight * (0.1 ^ (rank_decimal_length + length(weight::TEXT))));
+ ELSE
+ -- If weight is less than 10 then we add one more decimal place
+ -- becasue this means it reperesnts a level from 0 - 4, which we need
+ -- to preceed with a 0, so our weight will be 03, 05, 07, 09, which
+ -- will place them before 5 - 9 which are 11, 13, 15, 17, and 19
+ -- which fall before anyting > 9 because those all start with at least one 9.
+ rank := rank + (weight * (0.1 ^ (rank_decimal_length + length(weight::TEXT) + 1)));
+ END IF;
+ END IF;
+ END LOOP;
+
+ RETURN rank;
+END; $$ LANGUAGE 'plpgsql';
+
CREATE OR REPLACE FUNCTION evergreen.rank_cp_status(status INT)
RETURNS INTEGER AS $$
WITH totally_available AS (
--- /dev/null
+BEGIN;
+
+SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+INSERT INTO config.org_unit_setting_type
+ (name, label, datatype, description, grp, update_perm, view_perm)
+VALUES (
+ 'opac.holding_sort_type',
+ oils_i18n_gettext(
+ 'opac.holding_sort_type',
+ 'Specify how items are ordered',
+ 'coust',
+ 'label'
+ ),
+ 'string',
+ oils_i18n_gettext(
+ 'opac.holding_sort_type',
+ 'This value specifies how items are ordered in search results and record views within the org unit. To sort from newest to oldest by active date use ''desc''. To sort from oldest to newest by active date use ''asc''. To sort by call number use ''call''.',
+ 'coust',
+ 'description'
+ ),
+ 'opac',
+ 93,
+ 192
+);
+
+COMMIT;
--- /dev/null
+BEGIN;
+
+CREATE OR REPLACE FUNCTION evergreen.rank_ou_root_search_with_pref_lib(lib INT, search_lib INT, pref_lib INT DEFAULT NULL)
+RETURNS INTEGER AS $$
+
+ -- In the case of ranking OUs in holdings displays
+ -- If the top level OU is the search lib,
+ -- meaning the search_lib's parent_ou is NULL, then
+ -- we only want to give special rankings to items
+ -- related to the pref_lib.
+ SELECT COALESCE(
+
+ -- lib matches pref_lib
+ (SELECT CASE WHEN $1 = $3 THEN -10000 END),
+
+
+ -- pref_lib is a child of search_lib and lib is a child of pref lib.
+ (SELECT distance - 5000
+ FROM actor.org_unit_descendants_distance($3)
+ WHERE id = $1 AND $3 IN (
+ SELECT id FROM actor.org_unit_descendants($2))),
+
+ -- all others pay cash
+ 1000
+ );
+
+$$ LANGUAGE SQL STABLE;
+
+COMMIT;
--- /dev/null
+BEGIN;
+
+CREATE OR REPLACE FUNCTION rank_serial_copy_by_issuance(issuance_id INT, groupings TEXT) RETURNS NUMERIC AS $$
+DECLARE
+ row RECORD;
+ rank NUMERIC;
+ display_subfields TEXT;
+ grouping_array TEXT[];
+ grouping_store HSTORE;
+ more_weight INT;
+ weight INT;
+ weight_length INT;
+ row_text TEXT;
+ row_value INT;
+ rank_decimal_length INT;
+ display_grouping TEXT;
+BEGIN
+
+ -- get the display_grouping so we know how to order this
+ SELECT sdist.display_grouping FROM serial.distribution AS sdist
+ INNER JOIN serial.subscription AS ssub ON (sdist.subscription = ssub.id)
+ INNER JOIN serial.issuance AS siss ON (ssub.id = siss.subscription)
+ WHERE siss.id = issuance_id INTO display_grouping;
+
+ grouping_array := string_to_array(groupings, '^');
+
+ grouping_store := hstore(grouping_array);
+
+ display_subfields := grouping_store->display_grouping;
+
+ -- It is possible for the first level of enumeration to be 0.
+ -- So, we initialize rank to -1 to indicate that it has not been set yet
+ rank := -1;
+
+ -- If we are grouping by chron, but there is no enum defined in this subscription's
+ -- capture patter, then the chron values will be recorded in enum's a-f
+ IF (display_grouping = 'chron') THEN
+ IF (SELECT NOT EXISTS(SELECT value FROM serial.materialized_holding_code WHERE issuance = issuance_id AND subfield IN (SELECT * FROM unnest(string_to_array(display_subfields, ','))))) THEN
+ display_grouping = 'enum';
+ display_subfields := grouping_store->display_grouping;
+ END IF;
+ END IF;
+
+ FOR row IN SELECT regexp_replace(value, '^\D*(\d*).*$', '\1') AS value FROM serial.materialized_holding_code WHERE issuance = issuance_id AND subfield IN (SELECT * FROM unnest(string_to_array(display_subfields, ','))) ORDER BY subfield LOOP
+ row_text := row.value;
+
+ -- strip leading 0s from the subfield values, unless we have a value of 0 or the empty string
+ -- If we have an emptry string, then there was no digit present int materialzed_holding_code, so we default to a value of 0
+ IF (row_text != '0' AND row_text != '') THEN
+ row_value := trim(leading '0' from row_text)::INT;
+ ELSE
+ row_value = 0;
+ END IF;
+
+ IF (rank = -1) THEN
+ rank := row_value;
+ ELSE
+ -- Determine weight.
+ -- Here we multiply by 2 to get an even number, so that when
+ -- we add 1 we are assured of getting an odd number.
+ -- This avoids having a number that ends in 0, which would cause the
+ -- loss of a significant digit because these values are being
+ -- appended after the decimal place. It also allows us room
+ -- to play with the weight value to ensure it never begins with a 9.
+ weight := row_value * 2 + 1;
+
+ -- Because a row_value of 1 becomes a weight of 3, we can assign a row_value of
+ -- 0 to a weight of 1. This means 0 values in the MFHD data will be sorted properly.
+ -- If they are left at 0, this information will be lost because the weight is added
+ -- to the manitissa.
+ IF (weight = 0) THEN
+ weight = 1;
+ END IF;
+
+ -- We use 9's to deliminate that start of a new level of
+ -- enumeration or chronology in the mantissa.
+ -- We add a number of 9's equal to that of the legnth of weight (row_value * 2 + 1),
+ -- which is the number we will be appending to the mantissa, to the beginning of weight.
+ weight_length := length(weight::TEXT);
+
+ -- Determine how many 9s to add to the weight.
+ -- If we are adding a level with a value over 9 then we have to add
+ -- a requisite number of 9's in front of the derived weight to ensure the
+ -- rank is valid. If the value is less than 9, then we do not add
+ -- a 9 because we subtract 10 ^ (legnth of weight - 1) from weights
+ -- derived form values over 9 (This is explained below).
+ -- This means we do not want 9's in front of values < 9 because that would
+ -- move them above items they should follow in the rankings
+ more_weight := 0;
+
+ IF (row_value > 9) THEN
+ -- We loop from 0 to row_length -2 because we are trying to determine how many
+ -- 9s to append to the mantissa.
+ -- For example if row_value is 10, then we want to append one 9 to the mantissa,
+ -- so we go through this loop once.
+ -- We want to append a 9 to the mantisaa to ensure this number is placed before
+ -- numbers underneath 10.
+ -- For instance, if the volume number is 2 and we are dealinig with issue 10,
+ -- then rank is currently = 2.0. We are going to append a value of
+ -- 10 * 2 + 1 - 10 to rank.
+ -- However, to ensure that 10 comes after numbers < 10 we append a single .9 to the mantissa.
+ -- After this loop is done, in the case of a value of 10, but before we add the final
+ -- calcuated value to rank, we would have a rank of 2.9. This ensures that this number is always
+ -- going to be greater than 2.03 - 2.19 which would represent volume 2 issues 1 to 9 respectively.
+ -- In the case of 10, the final rank would be 2.911.
+ FOR exponent IN 0 .. (weight_length - 2) LOOP
+ more_weight := more_weight + (9 * (10 ^ exponent));
+ END LOOP;
+ END IF;
+
+ -- Get the length of the current mantissa so we know how many 0's to place in front
+ -- of more_weight.
+ rank_decimal_length := length(rtrim(split_part(rank::TEXT, '.', 2), '0'));
+
+ -- Subtracting 10 ^ (legnth of weight - 1) from weight ensures that weight never starts with a 9.
+ -- We never add a weight starting with a 9 because 9 represents a new level in the enumeration
+ -- or chronology. When row_value is less than 10 we have a special case and weight is not decremented.
+ -- In these cases 1 = 3, 2 = 5 .. 9 = 19, values less than 9 do not have 9's prepended to them.
+ IF (row_value > 9) THEN
+ weight := weight - (10 ^ (length(weight::TEXT) - 1));
+ END IF;
+
+ -- Add a number of 0's to the end of more_weight so that weight
+ -- is appended to more_weight
+ IF (more_weight > 0) THEN
+ more_weight := more_weight * (10 ^ (length(weight::TEXT)));
+
+ weight := weight + more_weight;
+ END IF;
+
+ -- The rank_decimal_length + length of weight tells us how many decimal places to shift
+ -- weight, so it will be added to the end of the mantissa.
+ IF (weight > 10) THEN
+ rank := rank + (weight * (0.1 ^ (rank_decimal_length + length(weight::TEXT))));
+ ELSE
+ -- If weight is less than 10 then we add one more decimal place
+ -- becasue this means it reperesnts a level from 0 - 4, which we need
+ -- to preceed with a 0, so our weight will be 03, 05, 07, 09, which
+ -- will place them before 5 - 9 which are 11, 13, 15, 17, and 19
+ -- which fall before anyting > 9 because those all start with at least one 9.
+ rank := rank + (weight * (0.1 ^ (rank_decimal_length + length(weight::TEXT) + 1)));
+ END IF;
+ END IF;
+ END LOOP;
+
+ RETURN rank;
+END; $$ LANGUAGE 'plpgsql';
+
+COMMIT;
END; # FOREACH bib
-%]
[%- last_cn = 0;
+ count = 0;
+ copies_max = copies.max;
+ copies_size = copies.size;
FOR copy_info IN copies;
+ IF copies_size > ctx.copy_limit;
+ IF copies_max == count;
+ LAST;
+ END;
+ count = count + 1;
+ END;
callnum = copy_info.call_number_label;
NEXT IF callnum == '##URI##';
l('Previous [_1]', ctx.copy_offset - new_offset) %]</a>
</td>
[%- END %]
- [%- IF copies.size >= ctx.copy_limit AND NOT serial_holdings %]
+ [%- IF copies.size > ctx.copy_limit AND NOT serial_holdings %]
<td>
<a href="[% mkurl('', {copy_offset => ctx.copy_offset + ctx.copy_limit, copy_limit => ctx.copy_limit}) %]">[%
l('Next [_1]', ctx.copy_limit) %] »</a>
</td>
<td><a href="[% uri.href %]">[% uri.link | html %]</a>[% ' - ' _ uri.note | html IF uri.note %]</td>
</tr>
- [% END %]
- [%- IF args.holdings.size > 0;
- FOREACH copy IN args.holdings;
- IF copy.part_label != '';
- has_parts = 'true';
- LAST;
- END;
+ [% END %]
+ [%-
+ rec_id = rec.id;
+ IF ctx.holdings.$rec_id.size > 0;
+ FOREACH copy IN ctx.holdings.$rec_id;
+ IF copy.part_label != '';
+ has_parts = 'true';
+ LAST;
END;
- %]
+ END %]
<tr name='bib_cn_list' class='result_table_title_cell'>
<td colspan='2'>
<table title="[% l('Record Holdings Details') %]"
<th>[% l('Status') %]</th>
</tr></thead>
<tbody>
- [% FOR copy IN args.holdings %]
- <tr>
- <td>
-[%- copy_info = copy;
- INCLUDE "opac/parts/library_name_link.tt2"; %]
- </td>
- <td>[% copy.location | html %]</td>
- <td>[% copy.label | html %]</td>
- [%- IF has_parts == 'true'; %]
- <td>[% copy.part_label %]</td>
- [%- END %]
- <td>[% copy.status | html %]</td>
- </tr>
- [% END %]
+ [%
+ copies = ctx.holdings.$rec_id;
+
+ FOR copy IN copies;
+ callnum = copy.call_number_label;
+ NEXT IF callnum == '##URI##';
+
+ IF copy.part_label != '';
+ has_parts = 'true';
+ END %]
+ <tr>
+ <td>[%
+ org_name = ctx.get_aou(copy.circ_lib).name;
+ org_name | html
+ %]
+ </td>
+ <td>[% copy.copy_location | html %]</td>
+ <td>[% copy.call_number_label | html %]</td>
+ [%- IF has_parts == 'true'; %]
+ <td>[% copy.part_label %]</td>
+ [%- END %]
+ <td>[% copy.copy_status | html %]</td>
+ </tr>
+ [% END %]
</tbody>
</table>
</td>
</tr>
[%- has_parts = 'false';
- END;
- %]
+ END;
+ %]
[% END %] <!-- END detail_record_view -->
</table>
[% PROCESS "opac/parts/result/copy_counts.tt2" %]