From bcd6f20b9a5c2cead82d9f28925a57d59be4b379 Mon Sep 17 00:00:00 2001 From: miker Date: Tue, 29 Mar 2011 15:24:44 +0000 Subject: [PATCH] Monograph Parts; Unified vol/copy wizard; Call Number affixes; Instant Detail * Monograph Bibliographic Parts - One MARC record should be able to support all volumes associated with the title, subdivide records in multiple ways, and the ability to support holds on individual Parts. * Unified Volume/Copy Wizard - The ability to enter call number, barcode number, and all copy information on the same screen. Also, the ability to include some information associated with the call number, such as classification scheme, prefix and suffix, in the item template feature of the Unified Volume/Copy Wizard. * Call Number Affixes - Delimiting the call number so that the prefix and suffix reside in separate fields. This prefix and suffix should display in the OPAC as part of the call number and should also be available for use in spine labels. * Instant Detail for One Record Hit List - When searching for records in the staff client, the ability to immediately land on the bibliographic record instead of the search results screen when there is only on matching search result in the system. This enhancement does not change the way search results are returned in the public catalog. These features were sponsored by the Massachusetts Library Network Cooperative. git-svn-id: svn://svn.open-ils.org/ILS/trunk@19883 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- Open-ILS/examples/fm_IDL.xml | 89 +- .../lib/OpenILS/Application/AppUtils.pm | 23 +- .../perlmods/lib/OpenILS/Application/Cat.pm | 27 +- .../OpenILS/Application/Cat/AssetCommon.pm | 79 +- .../perlmods/lib/OpenILS/Application/Circ.pm | 4 +- .../lib/OpenILS/Application/Circ/Holds.pm | 178 ++- .../lib/OpenILS/Application/Search/Biblio.pm | 65 +- .../lib/OpenILS/Application/Serial.pm | 1 + .../lib/OpenILS/Application/Storage/CDBI.pm | 9 + .../OpenILS/Application/Storage/CDBI/asset.pm | 26 +- .../Application/Storage/CDBI/biblio.pm | 8 + .../Application/Storage/Driver/Pg/dbi.pm | 23 + .../Application/Storage/Publisher/action.pm | 9 + .../Application/Storage/Publisher/biblio.pm | 19 +- .../lib/OpenILS/Application/SuperCat.pm | 51 +- Open-ILS/src/perlmods/lib/OpenILS/Const.pm | 1 + Open-ILS/src/sql/Pg/002.schema.config.sql | 2 +- Open-ILS/src/sql/Pg/010.schema.biblio.sql | 26 + Open-ILS/src/sql/Pg/040.schema.asset.sql | 49 +- Open-ILS/src/sql/Pg/950.data.seed-values.sql | 14 + Open-ILS/src/sql/Pg/990.schema.unapi.sql | 88 +- .../upgrade/0504.schema.parts_and_cnaffix.sql | 321 +++++ .../web/js/ui/default/acq/common/li_table.js | 1 - .../conify/global/config/acn_prefix.js | 72 ++ .../conify/global/config/acn_suffix.js | 72 ++ Open-ILS/web/opac/locale/en-US/lang.dtd | 21 +- Open-ILS/web/opac/locale/en-US/opac.dtd | 3 + .../web/opac/skin/default/js/copy_details.js | 13 + Open-ILS/web/opac/skin/default/js/holds.js | 119 +- Open-ILS/web/opac/skin/default/js/myopac.js | 9 +- Open-ILS/web/opac/skin/default/js/rresult.js | 11 + .../opac/skin/default/xml/common/holds.xml | 10 + .../skin/default/xml/myopac/myopac_holds.xml | 5 +- .../xml/rdetail/rdetail_cn_details.xml | 2 + .../conify/global/biblio/monograph_part.tt2 | 37 + .../conify/global/config/acn_prefix.tt2 | 37 + .../conify/global/config/acn_suffix.tt2 | 37 + .../chrome/content/OpenILS/data.js | 69 +- .../chrome/content/OpenILS/global_util.js | 20 +- .../staff_client/chrome/content/cat/opac.js | 58 +- .../staff_client/chrome/content/cat/opac.xul | 1 + .../chrome/content/main/constants.js | 14 +- .../staff_client/chrome/content/main/menu.js | 12 +- .../chrome/content/main/menu_frame_menus.xul | 4 + .../chrome/content/util/browser.js | 12 +- .../staff_client/chrome/content/util/list.js | 19 +- .../chrome/content/util/widgets.js | 11 +- .../chrome/locale/en-US/offline.properties | 1 + .../xul/staff_client/server/cat/bib_brief.js | 51 +- .../xul/staff_client/server/cat/bib_brief.xul | 10 +- .../staff_client/server/cat/copy_browser.js | 27 +- .../staff_client/server/cat/copy_browser.xul | 7 +- .../staff_client/server/cat/copy_editor.js | 166 ++- .../staff_client/server/cat/copy_editor.xul | 13 +- .../staff_client/server/cat/spine_labels.js | 21 +- Open-ILS/xul/staff_client/server/cat/util.js | 8 +- .../server/cat/volume_copy_creator.js | 1151 ++++++++++++++--- .../server/cat/volume_copy_creator.xul | 90 +- .../server/cat/volume_copy_editor.js | 111 ++ .../server/cat/volume_copy_editor.xul | 54 + .../server/cat/volume_copy_editor_horiz.xul | 54 + .../staff_client/server/cat/volume_editor.js | 217 ++++ .../staff_client/server/cat/volume_editor.xul | 90 +- .../staff_client/server/circ/copy_status.js | 8 +- Open-ILS/xul/staff_client/server/circ/util.js | 112 +- .../server/locale/en-US/cat.properties | 14 +- .../server/locale/en-US/circ.properties | 3 + .../server/locale/en-US/common.properties | 1 + .../xul/staff_client/server/patron/util.js | 3 +- 69 files changed, 3543 insertions(+), 450 deletions(-) create mode 100644 Open-ILS/src/sql/Pg/upgrade/0504.schema.parts_and_cnaffix.sql create mode 100644 Open-ILS/web/js/ui/default/conify/global/config/acn_prefix.js create mode 100644 Open-ILS/web/js/ui/default/conify/global/config/acn_suffix.js create mode 100644 Open-ILS/web/templates/default/conify/global/biblio/monograph_part.tt2 create mode 100644 Open-ILS/web/templates/default/conify/global/config/acn_prefix.tt2 create mode 100644 Open-ILS/web/templates/default/conify/global/config/acn_suffix.tt2 create mode 100644 Open-ILS/xul/staff_client/server/cat/volume_copy_editor.js create mode 100644 Open-ILS/xul/staff_client/server/cat/volume_copy_editor.xul create mode 100644 Open-ILS/xul/staff_client/server/cat/volume_copy_editor_horiz.xul create mode 100644 Open-ILS/xul/staff_client/server/cat/volume_editor.js diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 8836291546..04b85049fc 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -1814,14 +1814,57 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1839,6 +1882,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + @@ -1850,6 +1895,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + @@ -2027,6 +2074,44 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4684,6 +4769,7 @@ SELECT usr, + @@ -4700,6 +4786,7 @@ SELECT usr, + diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm index ec5c56fd93..8b297015bd 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm @@ -713,7 +713,7 @@ sub fetch_copy_location_by_name { } sub fetch_callnumber { - my( $self, $id ) = @_; + my( $self, $id, $flesh ) = @_; my $evt = undef; my $e = OpenILS::Event->new( 'ASSET_CALL_NUMBER_NOT_FOUND', id => $id ); @@ -726,6 +726,27 @@ sub fetch_callnumber { 'open-ils.cstore.direct.asset.call_number.retrieve', $id ); $evt = $e unless $cn; + if ($flesh && $cn) { + $cn->prefix( + $self->simplereq( + 'open-ils.cstore', + 'open-ils.cstore.direct.asset.call_number_prefix.retrieve', $cn->prefix + ) + ); + $cn->suffix( + $self->simplereq( + 'open-ils.cstore', + 'open-ils.cstore.direct.asset.call_number_suffix.retrieve', $cn->suffix + ) + ); + $cn->label_class( + $self->simplereq( + 'open-ils.cstore', + 'open-ils.cstore.direct.asset.call_number_class.retrieve', $cn->label_class + ) + ); + } + return ( $cn, $evt ); } diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat.pm index b787999899..525871d3c8 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat.pm @@ -660,16 +660,23 @@ sub _build_volume_list { $search_hash->{deleted} = 'f'; my $e = new_editor(); - my $vols = $e->search_asset_call_number([$search_hash, { 'order_by' => { - 'acn' => 'oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib' - } } ] ); + my $vols = $e->search_asset_call_number([ + $search_hash, + { + flesh => 1, + flesh_fields => { acn => ['prefix','suffix','label_class'] }, + 'order_by' => { 'acn' => 'oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib' } + } + ]); my @volumes; for my $volume (@$vols) { - my $copies = $e->search_asset_copy( - { call_number => $volume->id , deleted => 'f' }); + my $copies = $e->search_asset_copy([ + { call_number => $volume->id , deleted => 'f' }, + { flesh => 1, flesh_fields => { acp => ['parts'] } } + ]); $copies = [ sort { $a->barcode cmp $b->barcode } @$copies ]; @@ -912,6 +919,8 @@ sub update_volume { owning_lib => $vol->owning_lib, record => $vol->record, label => $vol->label, + prefix => $vol->prefix, + suffix => $vol->suffix, deleted => 'f', id => {'!=' => $vol->id} }); @@ -1034,6 +1043,8 @@ sub batch_volume_transfer { my $existing_vol = $e->search_asset_call_number( { label => $vol->label, + prefix => $vol->prefix, + suffix => $vol->suffix, record => $rec, owning_lib => $o_lib, deleted => 'f' @@ -1114,15 +1125,15 @@ __PACKAGE__->register_method( ); sub find_or_create_volume { - my( $self, $conn, $auth, $label, $record_id, $org_id ) = @_; + my( $self, $conn, $auth, $label, $record_id, $org_id, $prefix, $suffix, $label_class ) = @_; my $e = new_editor(authtoken=>$auth, xact=>1); return $e->die_event unless $e->checkauth; my ($vol, $evt, $exists) = - OpenILS::Application::Cat::AssetCommon->find_or_create_volume($e, $label, $record_id, $org_id); + OpenILS::Application::Cat::AssetCommon->find_or_create_volume($e, $label, $record_id, $org_id, $prefix, $suffix, $label_class); return $evt if $evt; $e->rollback if $exists; $e->commit if $vol; - return $vol->id; + return { 'acn_id' => $vol->id, 'existed' => $exists }; } diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat/AssetCommon.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat/AssetCommon.pm index b2dffc5518..c24a9f3d50 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat/AssetCommon.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat/AssetCommon.pm @@ -133,6 +133,63 @@ sub update_copy_stat_entries { return undef; } +# if 'delete_maps' is true, the copy->parts data is treated as the +# authoritative list for the copy. existing part maps not targeting +# these parts will be deleted from the DB +sub update_copy_parts { + my($class, $editor, $copy, $delete_maps) = @_; + + return undef if $copy->isdeleted; + return undef unless $copy->ischanged or $copy->isnew; + + my $evt; + my $incoming_parts = $copy->parts; + + if( $delete_maps ) { + $incoming_parts = ($incoming_parts and @$incoming_parts) ? $incoming_parts : []; + } else { + return undef unless ($incoming_parts and @$incoming_parts); + } + + my $maps = $editor->search_asset_copy_part_map({target_copy=>$copy->id}); + + if(!$copy->isnew) { + # if there is no part map on the copy who's id matches the + # current map's id, remove the map from the database + for my $map (@$maps) { + if(! grep { $_->id == $map->part } @$incoming_parts ) { + + $logger->info("copy update found stale ". + "monographic part map ".$map->id. " on copy ".$copy->id); + + $editor->delete_asset_copy_part_map($map) + or return $editor->event; + } + } + } + + # go through the part map update/create process + for my $incoming_part (@$incoming_parts) { + next unless $incoming_part; + + # if this link already exists in the DB, don't attempt to re-create it + next if( grep{$_->part == $incoming_part->id} @$maps ); + + my $new_map = Fieldmapper::asset::copy_part_map->new(); + + $new_map->part( $incoming_part->id ); + $new_map->target_copy( $copy->id ); + + $editor->create_asset_copy_part_map($new_map) + or return $editor->event; + + $logger->info("copy update created new monographic part copy map ".$editor->data); + } + + return undef; +} + + sub update_copy { my($class, $editor, $override, $vol, $copy, $retarget_holds, $force_delete_empty_bib) = @_; @@ -224,6 +281,9 @@ sub update_fleshed_copies { my $sc_entries = $copy->stat_cat_entries; $copy->clear_stat_cat_entries; + my $parts = $copy->parts; + $copy->clear_parts; + if( $copy->isdeleted ) { $evt = $class->delete_copy($editor, $override, $vol, $copy, $retarget_holds, $force_delete_empty_bib); return $evt if $evt; @@ -240,6 +300,9 @@ sub update_fleshed_copies { $copy->stat_cat_entries( $sc_entries ); $evt = $class->update_copy_stat_entries($editor, $copy, $delete_stats); + $copy->parts( $parts ); + # probably okay to use $delete_stats here for simplicity + $evt = $class->update_copy_parts($editor, $copy, $delete_stats); return $evt if $evt; } @@ -305,6 +368,8 @@ sub create_volume { owning_lib => $vol->owning_lib, record => $vol->record, label => $vol->label, + prefix => $vol->prefix, + suffix => $vol->suffix, deleted => 'f' } ); @@ -345,13 +410,16 @@ sub create_volume { # returns the volume if it exists sub volume_exists { - my($class, $e, $rec_id, $label, $owning_lib) = @_; + my($class, $e, $rec_id, $label, $owning_lib, $prefix, $suffix) = @_; return $e->search_asset_call_number( - {label => $label, record => $rec_id, owning_lib => $owning_lib, deleted => 'f'})->[0]; + {label => $label, record => $rec_id, owning_lib => $owning_lib, deleted => 'f', prefix => $prefix, suffix => $suffix})->[0]; } sub find_or_create_volume { - my($class, $e, $label, $record_id, $org_id) = @_; + my($class, $e, $label, $record_id, $org_id, $prefix, $suffix, $label_class) = @_; + + $prefix ||= '-1'; + $suffix ||= '-1'; my $vol; @@ -360,7 +428,7 @@ sub find_or_create_volume { or return (undef, $e->die_event); } else { - $vol = $class->volume_exists($e, $record_id, $label, $org_id); + $vol = $class->volume_exists($e, $record_id, $label, $org_id, $prefix, $suffix); } # If the volume exists, return the ID @@ -373,7 +441,10 @@ sub find_or_create_volume { $vol = Fieldmapper::asset::call_number->new; $vol->owning_lib($org_id); + $vol->label_class($label_class) if ($label_class); $vol->label($label); + $vol->prefix($prefix); + $vol->suffix($suffix); $vol->record($record_id); my $evt = OpenILS::Application::Cat::AssetCommon->create_volume(0, $e, $vol); diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm index 67b2dc1a3d..88af4f55d8 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm @@ -973,8 +973,8 @@ sub copy_details { { flesh => 2, flesh_fields => { - acp => ['call_number'], - acn => ['record'] + acp => ['call_number','parts'], + acn => ['record','prefix','suffix','label_class'] } } ]) or return $e->event; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Holds.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Holds.pm index 74957f1845..428b986ff4 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Holds.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Holds.pm @@ -183,6 +183,8 @@ sub create_hold { return $e->die_event unless $e->allowed('TITLE_HOLDS', $porg); } elsif ( $t eq OILS_HOLD_TYPE_VOLUME ) { return $e->die_event unless $e->allowed('VOLUME_HOLDS', $porg); + } elsif ( $t eq OILS_HOLD_TYPE_MONOPART ) { + return $e->die_event unless $e->allowed('TITLE_HOLDS', $porg); } elsif ( $t eq OILS_HOLD_TYPE_ISSUANCE ) { return $e->die_event unless $e->allowed('ISSUANCE_HOLDS', $porg); } elsif ( $t eq OILS_HOLD_TYPE_COPY ) { @@ -1947,6 +1949,7 @@ The named fields in the hash are: pickup_lib - destination for hold, fallback value for selection_ou selection_ou - ID of org_unit establishing hard and soft hold boundary settings issuanceid - ID of the issuance to be held, required for Issuance level hold + partid - ID of the monograph part to be held, required for monograph part level hold titleid - ID (BRN) of the title to be held, required for Title level hold volume_id - required for Volume level hold copy_id - required for Copy level hold @@ -2038,6 +2041,7 @@ sub do_possibility_checks { my($e, $patron, $request_lib, $depth, %params) = @_; my $issuanceid = $params{issuanceid} || ""; + my $partid = $params{partid} || ""; my $titleid = $params{titleid} || ""; my $volid = $params{volume_id}; my $copyid = $params{copy_id}; @@ -2082,6 +2086,12 @@ sub do_possibility_checks { $issuanceid, $depth, $request_lib, $patron, $e->requestor, $pickup_lib, $selection_ou ); + } elsif( $hold_type eq OILS_HOLD_TYPE_MONOPART ) { + + return _check_monopart_hold_is_possible( + $partid, $depth, $request_lib, $patron, $e->requestor, $pickup_lib, $selection_ou + ); + } elsif( $hold_type eq OILS_HOLD_TYPE_METARECORD ) { my $maps = $e->search_metabib_metarecord_source_map({metarecord=>$mrid}); @@ -2379,6 +2389,140 @@ sub _check_issuance_hold_is_possible { return @status; } +sub _check_monopart_hold_is_possible { + my( $partid, $depth, $request_lib, $patron, $requestor, $pickup_lib, $selection_ou ) = @_; + + my $e = new_editor(); + my %org_filter = create_ranged_org_filter($e, $selection_ou, $depth); + + # this monster will grab the id and circ_lib of all of the "holdable" copies for the given record + my $copies = $e->json_query( + { + select => { acp => ['id', 'circ_lib'] }, + from => { + acp => { + acpm => { + field => 'target_copy', + fkey => 'id', + filter => { part => $partid } + }, + acpl => { field => 'id', filter => { holdable => 't'}, fkey => 'location' }, + ccs => { field => 'id', filter => { holdable => 't'}, fkey => 'status' } + } + }, + where => { + '+acp' => { circulate => 't', deleted => 'f', holdable => 't', %org_filter } + }, + distinct => 1 + } + ); + + $logger->info("monopart possible found ".scalar(@$copies)." potential copies"); + + my $empty_ok; + if (!@$copies) { + $empty_ok = $e->retrieve_config_global_flag('circ.holds.empty_part_ok'); + $empty_ok = ($empty_ok and $U->is_true($empty_ok->enabled)); + + return ( + 0, 0, [ + new OpenILS::Event( + "HIGH_LEVEL_HOLD_HAS_NO_COPIES", + "payload" => {"fail_part" => "no_ultimate_items"} + ) + ] + ) unless $empty_ok; + + return (1, 0); + } + + # ----------------------------------------------------------------------- + # sort the copies into buckets based on their circ_lib proximity to + # the patron's home_ou. + # ----------------------------------------------------------------------- + + my $home_org = $patron->home_ou; + my $req_org = $request_lib->id; + + $logger->info("prox cache $home_org " . $prox_cache{$home_org}); + + $prox_cache{$home_org} = + $e->search_actor_org_unit_proximity({from_org => $home_org}) + unless $prox_cache{$home_org}; + my $home_prox = $prox_cache{$home_org}; + + my %buckets; + my %hash = map { ($_->to_org => $_->prox) } @$home_prox; + push( @{$buckets{ $hash{$_->{circ_lib}} } }, $_->{id} ) for @$copies; + + my @keys = sort { $a <=> $b } keys %buckets; + + + if( $home_org ne $req_org ) { + # ----------------------------------------------------------------------- + # shove the copies close to the request_lib into the primary buckets + # directly before the farthest away copies. That way, they are not + # given priority, but they are checked before the farthest copies. + # ----------------------------------------------------------------------- + $prox_cache{$req_org} = + $e->search_actor_org_unit_proximity({from_org => $req_org}) + unless $prox_cache{$req_org}; + my $req_prox = $prox_cache{$req_org}; + + my %buckets2; + my %hash2 = map { ($_->to_org => $_->prox) } @$req_prox; + push( @{$buckets2{ $hash2{$_->{circ_lib}} } }, $_->{id} ) for @$copies; + + my $highest_key = $keys[@keys - 1]; # the farthest prox in the exising buckets + my $new_key = $highest_key - 0.5; # right before the farthest prox + my @keys2 = sort { $a <=> $b } keys %buckets2; + for my $key (@keys2) { + last if $key >= $highest_key; + push( @{$buckets{$new_key}}, $_ ) for @{$buckets2{$key}}; + } + } + + @keys = sort { $a <=> $b } keys %buckets; + + my $title; + my %seen; + my @status; + OUTER: for my $key (@keys) { + my @cps = @{$buckets{$key}}; + + $logger->info("looking at " . scalar(@{$buckets{$key}}). " copies in proximity bucket $key"); + + for my $copyid (@cps) { + + next if $seen{$copyid}; + $seen{$copyid} = 1; # there could be dupes given the merged buckets + my $copy = $e->retrieve_asset_copy($copyid); + $logger->debug("looking at bucket_key=$key, copy $copyid : circ_lib = " . $copy->circ_lib); + + unless($title) { # grab the title if we don't already have it + my $vol = $e->retrieve_asset_call_number( + [ $copy->call_number, { flesh => 1, flesh_fields => { bre => ['fixed_fields'], acn => ['record'] } } ] ); + $title = $vol->record; + } + + @status = verify_copy_for_hold( + $patron, $requestor, $title, $copy, $pickup_lib, $request_lib); + + last OUTER if $status[0]; + } + } + + if (!$status[0]) { + if (!defined($empty_ok)) { + $empty_ok = $e->retrieve_config_global_flag('circ.holds.empty_part_ok'); + $empty_ok = ($empty_ok and $U->is_true($empty_ok->enabled)); + } + + return (1,0) if ($empty_ok); + } + return @status; +} + sub _check_volume_hold_is_possible { my( $vol, $title, $depth, $request_lib, $patron, $requestor, $pickup_lib, $selection_ou ) = @_; @@ -2688,7 +2832,7 @@ sub uber_hold_impl { $hold->usr($user->id); - my( $mvr, $volume, $copy, $issuance, $bre ) = find_hold_mvr($e, $hold, $args->{suppress_mvr}); + my( $mvr, $volume, $copy, $issuance, $part, $bre ) = find_hold_mvr($e, $hold, $args->{suppress_mvr}); flesh_hold_notices([$hold], $e) unless $args->{suppress_notices}; flesh_hold_transits([$hold]) unless $args->{suppress_transits}; @@ -2697,12 +2841,15 @@ sub uber_hold_impl { my $resp = { hold => $hold, - copy => $copy, - volume => $volume, + ($copy ? (copy => $copy) : ()), + ($volume ? (volume => $volume) : ()), + ($issuance ? (issuance => $issuance) : ()), + ($part ? (part => $part) : ()), + ($args->{include_bre} ? (bre => $bre) : ()), + ($args->{suppress_mvr} ? () : (mvr => $mvr)), %$details }; - $resp->{mvr} = $mvr unless $args->{suppress_mvr}; unless($args->{suppress_patron_details}) { my $card = $e->retrieve_actor_card($user->card) or return $e->event; $resp->{patron_first} = $user->first_given_name, @@ -2711,8 +2858,6 @@ sub uber_hold_impl { $resp->{patron_alias} = $user->alias, }; - $resp->{bre} = $bre if $args->{include_bre}; - return $resp; } @@ -2729,6 +2874,7 @@ sub find_hold_mvr { my $copy; my $volume; my $issuance; + my $part; if( $hold->hold_type eq OILS_HOLD_TYPE_METARECORD ) { my $mr = $e->retrieve_metabib_metarecord($hold->target) @@ -2751,6 +2897,14 @@ sub find_hold_mvr { $tid = $issuance->subscription->record_entry; + } elsif( $hold->hold_type eq OILS_HOLD_TYPE_MONOPART ) { + $part = $e->retrieve_biblio_monographic_part([ + $hold->target, + {flesh => 1, flesh_fields => {bmp => [ qw/record/ ]}} + ]) or return $e->event; + + $tid = $part->record; + } elsif( $hold->hold_type eq OILS_HOLD_TYPE_COPY ) { $copy = $e->retrieve_asset_copy([ $hold->target, @@ -2772,7 +2926,7 @@ sub find_hold_mvr { # TODO return metarcord mvr for M holds my $title = $e->retrieve_biblio_record_entry($tid); - return ( ($no_mvr) ? undef : $U->record_to_mvr($title), $volume, $copy, $issuance, $title ); + return ( ($no_mvr) ? undef : $U->record_to_mvr($title), $volume, $copy, $issuance, $part, $title ); } __PACKAGE__->register_method( @@ -3139,6 +3293,14 @@ sub hold_item_is_checked_out { $query->{where}->{'+acp'}->{call_number} = $hold_target; + } elsif($hold_type eq 'P') { + + $query->{from}->{acp}->{acpm} = { + field => 'target_copy', + fkey => 'id', + filter => {part => $hold_target}, + }; + } elsif($hold_type eq 'I') { $query->{from}->{acp}->{sitem} = { @@ -3305,7 +3467,7 @@ sub rec_hold_count { '-or' => [ { '-and' => { - hold_type => 'C', + hold_type => [qw/C F R/], target => { in => { select => {acp => ['id']}, diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Biblio.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Biblio.pm index 946677f768..226ad4422c 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Biblio.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Biblio.pm @@ -446,7 +446,7 @@ sub fleshed_copy_retrieve_batch { "open-ils.cstore.direct.asset.copy.search.atomic", { id => $ids }, { flesh => 1, - flesh_fields => { acp => [ qw/ circ_lib location status stat_cat_entries / ] } + flesh_fields => { acp => [ qw/ circ_lib location status stat_cat_entries parts / ] } }); } @@ -494,7 +494,7 @@ sub fleshed_copy_retrieve2 { flesh => 2, flesh_fields => { acp => [ - qw/ location status stat_cat_entry_copy_maps notes age_protect / + qw/ location status stat_cat_entry_copy_maps notes age_protect parts / ], ascecm => [qw/ stat_cat stat_cat_entry /], } @@ -2226,6 +2226,21 @@ sub fetch_cn { return $cn; } +__PACKAGE__->register_method( + method => "fetch_fleshed_cn", + api_name => "open-ils.search.callnumber.fleshed.retrieve", + authoritative => 1, + notes => "retrieves a callnumber based on ID, fleshing prefix, suffix, and label_class", +); + +sub fetch_fleshed_cn { + my( $self, $client, $id ) = @_; + my( $cn, $evt ) = $apputils->fetch_callnumber( $id, 1 ); + return $evt if $evt; + return $cn; +} + + __PACKAGE__->register_method( method => "fetch_copy_by_cn", api_name => 'open-ils.search.copies_by_call_number.retrieve', @@ -2342,6 +2357,52 @@ sub fetch_slim_record { return \@res; } +__PACKAGE__->register_method( + method => 'rec_hold_parts', + api_name => 'open-ils.search.biblio.record_hold_parts', + signature => q/ + Returns a list of {label :foo, id : bar} objects for viable monograph parts for a given record + / +); + +sub rec_hold_parts { + my( $self, $conn, $args ) = @_; + + my $rec = $$args{record}; + my $mrec = $$args{metarecord}; + my $pickup_lib = $$args{pickup_lib}; + my $e = new_editor(); + + my $query = { + select => {bmp => ['id', 'label']}, + from => 'bmp', + where => { + id => { + in => { + select => {'acpm' => ['part']}, + from => {acpm => {acp => {join => {acn => {join => 'bre'}}}}}, + where => { + '+acp' => {'deleted' => 'f'}, + '+bre' => {id => $rec} + }, + distinct => 1, + } + } + } + }; + + if(defined $pickup_lib) { + my $hard_boundary = $U->ou_ancestor_setting_value($pickup_lib, OILS_SETTING_HOLD_HARD_BOUNDARY); + if($hard_boundary) { + my $orgs = $e->json_query({from => ['actor.org_unit_descendants' => $pickup_lib, $hard_boundary]}); + $query->{where}->{'+acp'}->{circ_lib} = [ map { $_->{id} } @$orgs ]; + } + } + + return $e->json_query($query); +} + + __PACKAGE__->register_method( diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Serial.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Serial.pm index efd3da1c86..2ee5cba09f 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Serial.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Serial.pm @@ -1360,6 +1360,7 @@ sub unitize_items { sub _find_or_create_call_number { my ($e, $lib, $cn_string, $record) = @_; + # FIXME: should suffix and prefix come into play here? my $existing = $e->search_asset_call_number({ "owning_lib" => $lib, "label" => $cn_string, diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI.pm index d004f3898d..4cec39f8f3 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI.pm @@ -640,6 +640,15 @@ sub modify_from_fieldmapper { asset::call_number->has_many( copies => 'asset::copy' ); asset::call_number->has_many( notes => 'asset::call_number_note' ); + asset::call_number->has_a( prefix => 'asset::call_number_prefix' ); + asset::call_number->has_a( suffix => 'asset::call_number_suffix' ); + + asset::call_number_prefix->has_a( owning_lib => 'actor::org_unit' ); + asset::call_number_suffix->has_a( owning_lib => 'actor::org_unit' ); + + asset::call_number_prefix->has_many( call_numbers => 'asset::call_number' ); + asset::call_number_suffix->has_many( call_numbers => 'asset::call_number' ); + authority::record_entry->has_many( record_descriptor => 'authority::record_descriptor' ); authority::record_entry->has_many( notes => 'authority::record_note' ); diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/asset.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/asset.pm index 39eb01b2ad..b464de57f1 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/asset.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/asset.pm @@ -20,6 +20,22 @@ __PACKAGE__->table( 'asset_copy_location_order' ); __PACKAGE__->columns( Primary => qw/id/ ); __PACKAGE__->columns( Essential => qw/location org position/ ); +#------------------------------------------------------------------------------- +package asset::call_number_suffix; +use base qw/asset/; + +__PACKAGE__->table( 'asset_call_number_suffix' ); +__PACKAGE__->columns( Primary => qw/id/ ); +__PACKAGE__->columns( Essential => qw/owning_lib label label_sortkey/ ); + +#------------------------------------------------------------------------------- +package asset::call_number_prefix; +use base qw/asset/; + +__PACKAGE__->table( 'asset_call_number_prefix' ); +__PACKAGE__->columns( Primary => qw/id/ ); +__PACKAGE__->columns( Essential => qw/owning_lib label label_sortkey/ ); + #------------------------------------------------------------------------------- package asset::call_number_class; use base qw/asset/; @@ -34,7 +50,7 @@ use base qw/asset/; __PACKAGE__->table( 'asset_call_number' ); __PACKAGE__->columns( Primary => qw/id/ ); -__PACKAGE__->columns( Essential => qw/record label creator create_date editor +__PACKAGE__->columns( Essential => qw/record label creator create_date editor prefix suffix edit_date record label owning_lib deleted label_class label_sortkey/ ); #------------------------------------------------------------------------------- @@ -58,6 +74,14 @@ __PACKAGE__->columns( Essential => qw/call_number barcode creator create_date ed holdable dummy_title dummy_author deleted alert_message age_protect floating cost status_changed_time/ ); +#------------------------------------------------------------------------------- +package asset::copy_part_map; +use base qw/asset/; + +__PACKAGE__->table( 'asset_copy_part_map' ); +__PACKAGE__->columns( Primary => qw/id/ ); +__PACKAGE__->columns( Essential => qw/target_copy part/); + #------------------------------------------------------------------------------- package asset::stat_cat; use base qw/asset/; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/biblio.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/biblio.pm index 46fefaab6e..3df2ee5aca 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/biblio.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/biblio.pm @@ -22,5 +22,13 @@ biblio::record_note->columns( Essential => qw/id record value creator editor create_date edit_date pub/ ); #------------------------------------------------------------------------------- +#------------------------------------------------------------------------------- +package biblio::monograph_part; +use base qw/biblio/; + +biblio::monograph_part->table( 'biblio_monograph_part' ); +biblio::monograph_part->columns( Essential => qw/id record label label_sortkey/ ); +#------------------------------------------------------------------------------- + 1; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm index 5ca6fd008b..623be5fbeb 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm @@ -1,5 +1,16 @@ { + #------------------------------------------------------------------------------- + package asset::copy_part_map; + + asset::copy_part_map->table( 'asset.copy_part_map' ); + + #------------------------------------------------------------------------------- + package biblio::monograph_part; + + biblio::monograph_part->table( 'biblio.monograph_part' ); + biblio::monograph_part->sequence( 'biblio.monograph_part_id_seq' ); + #------------------------------------------------------------------------------- package container::user_bucket; @@ -321,6 +332,18 @@ asset::call_number->table( 'asset.call_number' ); asset::call_number->sequence( 'asset.call_number_id_seq' ); + #--------------------------------------------------------------------- + package asset::call_number_suffix; + + asset::call_number_suffix->table( 'asset.call_number_suffix' ); + asset::call_number_suffix->sequence( 'asset.call_number_suffix_id_seq' ); + + #--------------------------------------------------------------------- + package asset::call_number_prefix; + + asset::call_number_prefix->table( 'asset.call_number_prefix' ); + asset::call_number_prefix->sequence( 'asset.call_number_prefix_id_seq' ); + #--------------------------------------------------------------------- package asset::call_number_class; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm index 0760fcb005..cd0cdb84a9 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm @@ -1233,6 +1233,15 @@ sub new_hold_copy_targeter { { id => [map {$_->id} @{ $vtree->copies }], deleted => 'f' } ) if ($vtree && @{ $vtree->copies }); + + } elsif ($hold->hold_type eq 'P') { + my @part_maps = asset::copy_part_map->search_where( { part => $hold->target } ); + $all_copies = [ + asset::copy->search_where( + { id => [map {$_->target_copy} @part_maps], + deleted => 'f' } + ) + ] if (@part_maps); } elsif ($hold->hold_type eq 'I') { my ($itree) = $self diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/biblio.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/biblio.pm index 5b609bee73..7c7c83078c 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/biblio.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/biblio.pm @@ -380,19 +380,28 @@ sub record_copy_status_count { my $descendants = "actor.org_unit_descendants(?,?)"; my $cn_table = asset::call_number->table; + my $cnp_table = asset::call_number_prefix->table; + my $cns_table = asset::call_number_prefix->table; my $cp_table = asset::copy->table; my $cl_table = asset::copy_location->table; my $cs_table = config::copy_status->table; my $sql = <<" SQL"; - SELECT cp.circ_lib, cn.label, cp.status, count(cp.id) + SELECT cp.circ_lib, + CASE WHEN cnp.id > -1 THEN cnp.label || ' ' ELSE '' END || cn.label || CASE WHEN cns.id > -1 THEN ' ' || cns.label ELSE '' END, + cp.status, + count(cp.id) FROM $cp_table cp, $cn_table cn, + $cns_table cns, + $cnp_table cnp, $cl_table cl, $cs_table cs, $descendants d WHERE cn.record = ? + AND cnp.id = cn.prefix + AND cns.id = cn.suffix AND cp.call_number = cn.id AND cp.location = cl.id AND cp.circ_lib = d.id @@ -440,6 +449,8 @@ sub record_copy_status_location_count { my $descendants = "actor.org_unit_descendants(?,?)"; my $cn_table = asset::call_number->table; + my $cnp_table = asset::call_number_prefix->table; + my $cns_table = asset::call_number_prefix->table; my $cp_table = asset::copy->table; my $cl_table = asset::copy_location->table; my $cs_table = config::copy_status->table; @@ -450,16 +461,20 @@ sub record_copy_status_location_count { my $sql = <<" SQL"; SELECT cp.circ_lib, - cn.label, + CASE WHEN cnp.id > -1 THEN cnp.label || ' ' ELSE '' END || cn.label || CASE WHEN cns.id > -1 THEN ' ' || cns.label ELSE '' END, oils_i18n_xlate('asset.copy_location', 'acpl', 'name', 'id', cl.id::TEXT, ?), cp.status, count(cp.id) FROM $cp_table cp, $cn_table cn, + $cns_table cns, + $cnp_table cnp, $cl_table cl, $cs_table cs, $descendants d WHERE cn.record = ? + AND cnp.id = cn.prefix + AND cns.id = cn.suffix AND cp.call_number = cn.id AND cp.location = cl.id AND cp.circ_lib = d.id diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/SuperCat.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/SuperCat.pm index b2419c21e5..68928a617b 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/SuperCat.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/SuperCat.pm @@ -330,7 +330,7 @@ sub cn_browse { @cp_filter }, { flesh => 1, - flesh_fields => { acn => [qw/record owning_lib/] }, + flesh_fields => { acn => [qw/record owning_lib prefix suffix/] }, order_by => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(label) desc, id desc, owning_lib desc" }, limit => $before_limit, offset => abs($page) * $page_size - $before_offset, @@ -348,7 +348,7 @@ sub cn_browse { @cp_filter }, { flesh => 1, - flesh_fields => { acn => [qw/record owning_lib/] }, + flesh_fields => { acn => [qw/record owning_lib prefix suffix/] }, order_by => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib" }, limit => $after_limit, offset => abs($page) * $page_size - $after_offset, @@ -455,7 +455,7 @@ sub cn_startwith { @cp_filter }, { flesh => 1, - flesh_fields => { acn => [qw/record owning_lib/] }, + flesh_fields => { acn => [qw/record owning_lib prefix suffix/] }, order_by => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(label) desc, id desc, owning_lib desc" }, limit => $limit, offset => $offset, @@ -473,7 +473,7 @@ sub cn_startwith { @cp_filter }, { flesh => 1, - flesh_fields => { acn => [qw/record owning_lib/] }, + flesh_fields => { acn => [qw/record owning_lib prefix suffix/] }, order_by => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib" }, limit => $limit, offset => $offset, @@ -1690,7 +1690,7 @@ sub retrieve_uri { flesh_fields => { auri => [qw/call_number_maps/], auricnm => [qw/call_number/], - acn => [qw/owning_lib record/], + acn => [qw/owning_lib record prefix suffix/], } }) ->gather(1)) @@ -1731,8 +1731,8 @@ sub retrieve_copy { $cpid, { flesh => 2, flesh_fields => { - acn => [qw/owning_lib record/], - acp => [qw/call_number location status circ_lib stat_cat_entries notes/], + acn => [qw/owning_lib record prefix suffix/], + acp => [qw/call_number location status circ_lib stat_cat_entries notes parts/], } }) ->gather(1)) @@ -1774,9 +1774,9 @@ sub retrieve_callnumber { $cnid, { flesh => 5, flesh_fields => { - acn => [qw/owning_lib record copies uri_maps/], + acn => [qw/owning_lib record copies uri_maps prefix suffix/], auricnm => [qw/uri/], - acp => [qw/location status circ_lib stat_cat_entries notes/], + acp => [qw/location status circ_lib stat_cat_entries notes parts/], } }) ->gather(1)) @@ -1823,8 +1823,8 @@ sub basic_record_holdings { { flesh => 5, flesh_fields => { bre => [qw/call_numbers/], - acn => [qw/copies owning_lib/], - acp => [qw/location status circ_lib/], + acn => [qw/copies owning_lib prefix suffix/], + acp => [qw/location status circ_lib parts/], } } )->gather(1); @@ -1975,9 +1975,9 @@ sub new_record_holdings { }, { flesh => 5, flesh_fields => { - acn => [qw/copies owning_lib uri_maps/], + acn => [qw/copies owning_lib uri_maps prefix suffix/], auricnm => [qw/uri/], - acp => [qw/circ_lib location status stat_cat_entries notes/], + acp => [qw/circ_lib location status stat_cat_entries notes parts/], asce => [qw/stat_cat/], }, ( $limit > -1 ? ( limit => $limit ) : () ), @@ -2049,7 +2049,7 @@ sub new_record_holdings { sstr => [qw/items/], sitem => [qw/notes unit/], sunit => [qw/notes location status circ_lib stat_cat_entries call_number/], - acn => [qw/owning_lib/], + acn => [qw/owning_lib prefix suffix/], }, ( $limit > -1 ? ( limit => $limit ) : () ), ( $offset ? ( offset => $offset ) : () ), @@ -3021,6 +3021,20 @@ sub as_xml { } + $xml .= ' obj->prefix->id . '" '; + $xml .= 'id="tag:open-ils.org:asset-call_number_prefix/' . $self->obj->prefix->id . '" '; + $xml .= 'label_sortkey="'.$self->escape( $self->obj->prefix->label_sortkey ) .'">'; + $xml .= $self->escape( $self->obj->prefix->label ) .''; + $xml .= "\n"; + + $xml .= ' obj->suffix->id . '" '; + $xml .= 'id="tag:open-ils.org:asset-call_number_suffix/' . $self->obj->suffix->id . '" '; + $xml .= 'label_sortkey="'.$self->escape( $self->obj->suffix->label_sortkey ) .'">'; + $xml .= $self->escape( $self->obj->suffix->label ) .''; + $xml .= "\n"; + $xml .= ' obj->owning_lib->id . '" '; $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" '; @@ -3453,6 +3467,15 @@ sub as_xml { $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'" opac_visible="'.$self->obj->circ_lib->opac_visible.'"/>'; $xml .= "\n"; + $xml .= " \n"; + if (ref($self->obj->parts) && $self->obj->parts) { + for my $part ( @{$self->obj->parts} ) { + $xml .= sprintf(' %s',$part->record, $self->escape($part->label_sortkey), $self->escape($part->label)); + $xml .= "\n"; + } + } + + $xml .= " \n"; $xml .= " \n"; if (ref($self->obj->notes) && $self->obj->notes) { for my $note ( @{$self->obj->notes} ) { diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Const.pm b/Open-ILS/src/perlmods/lib/OpenILS/Const.pm index 281f465bd0..6da2ed60aa 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Const.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Const.pm @@ -102,6 +102,7 @@ econst OILS_HOLD_TYPE_ISSUANCE => 'I'; econst OILS_HOLD_TYPE_VOLUME => 'V'; econst OILS_HOLD_TYPE_TITLE => 'T'; econst OILS_HOLD_TYPE_METARECORD => 'M'; +econst OILS_HOLD_TYPE_MONOPART => 'P'; econst OILS_BILLING_TYPE_OVERDUE_MATERIALS => 'Overdue materials'; diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql index af39c34103..72ce6c5bd6 100644 --- a/Open-ILS/src/sql/Pg/002.schema.config.sql +++ b/Open-ILS/src/sql/Pg/002.schema.config.sql @@ -70,7 +70,7 @@ CREATE TABLE config.upgrade_log ( install_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() ); -INSERT INTO config.upgrade_log (version) VALUES ('0503'); -- miker for tsbere +INSERT INTO config.upgrade_log (version) VALUES ('0504'); -- miker for tsbere CREATE TABLE config.bib_source ( id SERIAL PRIMARY KEY, diff --git a/Open-ILS/src/sql/Pg/010.schema.biblio.sql b/Open-ILS/src/sql/Pg/010.schema.biblio.sql index 428b575481..cf404f2f54 100644 --- a/Open-ILS/src/sql/Pg/010.schema.biblio.sql +++ b/Open-ILS/src/sql/Pg/010.schema.biblio.sql @@ -79,4 +79,30 @@ CREATE INDEX biblio_record_note_record_idx ON biblio.record_note ( record ); CREATE INDEX biblio_record_note_creator_idx ON biblio.record_note ( creator ); CREATE INDEX biblio_record_note_editor_idx ON biblio.record_note ( editor ); +CREATE TABLE biblio.monograph_part ( + id SERIAL PRIMARY KEY, + record BIGINT NOT NULL REFERENCES biblio.record_entry (id), + label TEXT NOT NULL, + label_sortkey TEXT NOT NULL, + CONSTRAINT record_label_unique UNIQUE (record,label) +); + +CREATE OR REPLACE FUNCTION biblio.normalize_biblio_monograph_part_sortkey () RETURNS TRIGGER AS $$ +BEGIN + NEW.label_sortkey := REGEXP_REPLACE( + lpad_number_substrings( + naco_normalize(NEW.label), + '0', + 10 + ), + E'\\s+', + '', + 'g' + ); + RETURN NEW; +END; +$$ LANGUAGE PLPGSQL; + +CREATE TRIGGER norm_sort_label BEFORE INSERT OR UPDATE ON biblio.monograph_part FOR EACH ROW EXECUTE PROCEDURE biblio.normalize_biblio_monograph_part_sortkey(); + COMMIT; diff --git a/Open-ILS/src/sql/Pg/040.schema.asset.sql b/Open-ILS/src/sql/Pg/040.schema.asset.sql index 6509bca19f..dbe0c09fb2 100644 --- a/Open-ILS/src/sql/Pg/040.schema.asset.sql +++ b/Open-ILS/src/sql/Pg/040.schema.asset.sql @@ -91,6 +91,13 @@ CREATE INDEX cp_editor_idx ON asset.copy ( editor ); CREATE INDEX cp_create_date ON asset.copy (create_date); CREATE RULE protect_copy_delete AS ON DELETE TO asset.copy DO INSTEAD UPDATE asset.copy SET deleted = TRUE WHERE OLD.id = asset.copy.id; +CREATE TABLE asset.copy_part_map ( + id SERIAL PRIMARY KEY, + target_copy BIGINT NOT NULL, -- points o asset.copy + part INT NOT NULL REFERENCES biblio.monograph_part (id) ON DELETE CASCADE +); +CREATE UNIQUE INDEX copy_part_map_cp_part_idx ON asset.copy_part_map (target_copy, part); + CREATE TABLE asset.opac_visible_copies ( id BIGINT primary key, -- copy id record BIGINT, @@ -279,6 +286,42 @@ INSERT INTO asset.call_number_class (name, normalizer, field) VALUES ('Library of Congress (LC)', 'asset.label_normalizer_lc', '050ab,055ab,090abef') ; +CREATE OR REPLACE FUNCTION asset.normalize_affix_sortkey () RETURNS TRIGGER AS $$ +BEGIN + NEW.label_sortkey := REGEXP_REPLACE( + lpad_number_substrings( + naco_normalize(NEW.label), + '0', + 10 + ), + E'\\s+', + '', + 'g' + ); + RETURN NEW; +END; +$$ LANGUAGE PLPGSQL; + +CREATE TABLE asset.call_number_prefix ( + id SERIAL PRIMARY KEY, + owning_lib INT NOT NULL REFERENCES actor.org_unit (id), + label TEXT NOT NULL, -- i18n + label_sortkey TEXT +); +CREATE TRIGGER prefix_normalize_tgr BEFORE INSERT OR UPDATE ON asset.call_number_prefix FOR EACH ROW EXECUTE PROCEDURE asset.normalize_affix_sortkey(); +CREATE UNIQUE INDEX asset_call_number_prefix_once_per_lib ON asset.call_number_prefix (label, owning_lib); +CREATE INDEX asset_call_number_prefix_sortkey_idx ON asset.call_number_prefix (label_sortkey); + +CREATE TABLE asset.call_number_suffix ( + id SERIAL PRIMARY KEY, + owning_lib INT NOT NULL REFERENCES actor.org_unit (id), + label TEXT NOT NULL, -- i18n + label_sortkey TEXT +); +CREATE TRIGGER suffix_normalize_tgr BEFORE INSERT OR UPDATE ON asset.call_number_suffix FOR EACH ROW EXECUTE PROCEDURE asset.normalize_affix_sortkey(); +CREATE UNIQUE INDEX asset_call_number_suffix_once_per_lib ON asset.call_number_suffix (label, owning_lib); +CREATE INDEX asset_call_number_suffix_sortkey_idx ON asset.call_number_suffix (label_sortkey); + CREATE TABLE asset.call_number ( id bigserial PRIMARY KEY, creator BIGINT NOT NULL, @@ -286,9 +329,11 @@ CREATE TABLE asset.call_number ( editor BIGINT NOT NULL, edit_date TIMESTAMP WITH TIME ZONE DEFAULT NOW(), record bigint NOT NULL, - owning_lib INT NOT NULL, + owning_lib INT NOT NULL, label TEXT NOT NULL, deleted BOOL NOT NULL DEFAULT FALSE, + prefix INT NOT NULL DEFAULT -1 REFERENCES asset.call_number_prefix(id) DEFERRABLE INITIALLY DEFERRED, + suffix INT NOT NULL DEFAULT -1 REFERENCES asset.call_number_suffix(id) DEFERRABLE INITIALLY DEFERRED, label_class BIGINT DEFAULT 1 NOT NULL REFERENCES asset.call_number_class(id) DEFERRABLE INITIALLY DEFERRED, @@ -300,7 +345,7 @@ CREATE INDEX asset_call_number_editor_idx ON asset.call_number (editor); CREATE INDEX asset_call_number_dewey_idx ON asset.call_number (public.call_number_dewey(label)); CREATE INDEX asset_call_number_upper_label_id_owning_lib_idx ON asset.call_number (oils_text_as_bytea(label),id,owning_lib); CREATE INDEX asset_call_number_label_sortkey ON asset.call_number(oils_text_as_bytea(label_sortkey)); -CREATE UNIQUE INDEX asset_call_number_label_once_per_lib ON asset.call_number (record, owning_lib, label) WHERE deleted = FALSE OR deleted IS FALSE; +CREATE UNIQUE INDEX asset_call_number_label_once_per_lib ON asset.call_number (record, owning_lib, label, prefix, suffix) WHERE deleted = FALSE OR deleted IS FALSE; CREATE INDEX asset_call_number_label_sortkey_browse ON asset.call_number(oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib) WHERE deleted IS FALSE OR deleted = FALSE; CREATE RULE protect_cn_delete AS ON DELETE TO asset.call_number DO INSTEAD UPDATE asset.call_number SET deleted = TRUE WHERE OLD.id = asset.call_number.id; CREATE TRIGGER asset_label_sortkey_trigger diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql index bad385a37a..549d03474e 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -1588,6 +1588,8 @@ INSERT INTO biblio.record_entry VALUES (-1,1,1,1,-1,NOW(),NOW(),FALSE,FALSE,'',' INSERT INTO asset.copy_location (id, name,owning_lib) VALUES (1, oils_i18n_gettext(1, 'Stacks', 'acpl', 'name'),1); SELECT SETVAL('asset.copy_location_id_seq'::TEXT, 100); +INSERT INTO asset.call_number_suffix (id, owning_lib, label) VALUES (-1, 1, ''); +INSERT INTO asset.call_number_prefix (id, owning_lib, label) VALUES (-1, 1, ''); INSERT INTO asset.call_number VALUES (-1,1,NOW(),1,NOW(),-1,1,'UNCATALOGED'); -- circ matrix @@ -7941,3 +7943,15 @@ INSERT INTO action_trigger.environment (event_def, path) VALUES (37, 'circ_lib.billing_address') ; +INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES ( + 'ui.cat.volume_copy_editor.horizontal', + oils_i18n_gettext( + 'ui.cat.volume_copy_editor.horizontal', + 'GUI: Horizontal layout for Volume/Copy Creator/Editor.', + 'coust', 'label'), + oils_i18n_gettext( + 'ui.cat.volume_copy_editor.horizontal', + 'The main entry point for this interface is in Holdings Maintenance, Actions for Selected Rows, Edit Item Attributes / Call Numbers / Replace Barcodes. This setting changes the top and bottom panes for that interface into left and right panes.', + 'coust', 'description'), + 'bool' +); diff --git a/Open-ILS/src/sql/Pg/990.schema.unapi.sql b/Open-ILS/src/sql/Pg/990.schema.unapi.sql index e7abed50b0..f84b3fe782 100644 --- a/Open-ILS/src/sql/Pg/990.schema.unapi.sql +++ b/Open-ILS/src/sql/Pg/990.schema.unapi.sql @@ -27,6 +27,8 @@ INSERT INTO unapi.bre_output_layout -- Dummy functions, so we can create the real ones out of order CREATE OR REPLACE FUNCTION unapi.aou ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL; +CREATE OR REPLACE FUNCTION unapi.acnp ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL; +CREATE OR REPLACE FUNCTION unapi.acns ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION unapi.acn ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION unapi.ssub ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION unapi.sdist ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL; @@ -44,6 +46,7 @@ CREATE OR REPLACE FUNCTION unapi.acl ( obj_id BIGINT, format TEXT, ename TEXT CREATE OR REPLACE FUNCTION unapi.ccs ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION unapi.bre ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL; +CREATE OR REPLACE FUNCTION unapi.bmp ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION unapi.holdings_xml ( bid BIGINT, ouid INT, org TEXT, depth INT DEFAULT NULL, includes TEXT[] DEFAULT NULL::TEXT[], slimit INT DEFAULT NULL, soffset INT DEFAULT NULL) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL; @@ -230,6 +233,13 @@ CREATE OR REPLACE FUNCTION unapi.holdings_xml (bid BIGINT, ouid INT, org TEXT, d ORDER BY 1 )x) ), + CASE + WHEN ('bmp' = ANY ($5)) THEN + XMLELEMENT( name monograph_parts, + XMLAGG((SELECT unapi.bmp( id, 'xml', 'monograph_part', array_remove_item_by_value( array_remove_item_by_value($5,'bre'), 'holdings_xml'), $3, $4, $6, $7) FROM biblio.monograph_part WHERE record = $1)) + ) + ELSE NULL + END, CASE WHEN ('acn' = ANY ('{acn,auri}'::TEXT[] || $5)) THEN XMLELEMENT( name volumes, @@ -540,6 +550,39 @@ CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT, ename TEX WHERE asce.id = $1; $F$ LANGUAGE SQL; +CREATE OR REPLACE FUNCTION unapi.bmp ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name monograph_part, + XMLATTRIBUTES( + 'http://open-ils.org/spec/holdings/v1' AS xmlns, + 'tag:open-ils.org:U2@bmp/' || id AS id, + id AS ident, + label, + label_sortkey, + 'tag:open-ils.org:U2@bre/' || record AS record + ), + CASE + WHEN ('acp' = ANY ($4)) THEN + XMLELEMENT( name copies, + (SELECT XMLAGG(acp) FROM ( + SELECT unapi.acp( cp.id, 'xml', 'copy', array_remove_item_by_value($4,'bmp'), $5, $6, $7, $8) + FROM asset.copy cp + JOIN asset.copy_part_map cpm ON (cpm.target_copy = cp.id) + WHERE cpm.part = $1 + ORDER BY COALESCE(cp.copy_number,0), cp.barcode + LIMIT $7 + OFFSET $8 + )x) + ) + ELSE NULL + END, + CASE WHEN ('bre' = ANY ($4)) THEN unapi.bre( record, 'marcxml', 'record', array_remove_item_by_value($4,'bmp'), $5, $6, $7, $8) ELSE NULL END + ) + FROM biblio.monograph_part + WHERE id = $1 + GROUP BY id, label, label_sortkey, record; +$F$ LANGUAGE SQL; + CREATE OR REPLACE FUNCTION unapi.acp ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL ) RETURNS XML AS $F$ SELECT XMLELEMENT( name copy, @@ -565,10 +608,17 @@ CREATE OR REPLACE FUNCTION unapi.acp ( obj_id BIGINT, format TEXT, ename TEXT, XMLELEMENT( name statcats, CASE WHEN ('ascecm' = ANY ($4)) THEN - XMLAGG((SELECT unapi.acpn( stat_cat_entry, 'xml', 'statcat', array_remove_item_by_value($4,'acp'), $5, $6, $7, $8) FROM asset.stat_cat_entry_copy_map WHERE owning_copy = cp.id)) + XMLAGG((SELECT unapi.ascecm( stat_cat_entry, 'xml', 'statcat', array_remove_item_by_value($4,'acp'), $5, $6, $7, $8) FROM asset.stat_cat_entry_copy_map WHERE owning_copy = cp.id)) ELSE NULL END - ) + ), + CASE + WHEN ('bmp' = ANY ($4)) THEN + XMLELEMENT( name monograph_parts, + XMLAGG((SELECT unapi.bmp( part, 'xml', 'monograph_part', array_remove_item_by_value($4,'acp'), $5, $6, $7, $8) FROM asset.copy_part_map WHERE target_copy = cp.id)) + ) + ELSE NULL + END ) FROM asset.copy cp WHERE id = $1 @@ -645,12 +695,44 @@ CREATE OR REPLACE FUNCTION unapi.acn ( obj_id BIGINT, format TEXT, ename TEXT, name uris, (SELECT XMLAGG(auri) FROM (SELECT unapi.auri(uri,'xml','uri', array_remove_item_by_value($4,'acn'), $5, $6, $7, $8) FROM asset.uri_call_number_map WHERE call_number = acn.id)x) ), + CASE WHEN ('acnp' = ANY ($4)) THEN unapi.acnp( acn.prefix, 'marcxml', 'prefix', array_remove_item_by_value($4,'acn'), $5, $6, $7, $8) ELSE NULL END, + CASE WHEN ('acns' = ANY ($4)) THEN unapi.acns( acn.suffix, 'marcxml', 'suffix', array_remove_item_by_value($4,'acn'), $5, $6, $7, $8) ELSE NULL END, CASE WHEN ('bre' = ANY ($4)) THEN unapi.bre( acn.record, 'marcxml', 'record', array_remove_item_by_value($4,'acn'), $5, $6, $7, $8) ELSE NULL END ) AS x FROM asset.call_number acn JOIN actor.org_unit o ON (o.id = acn.owning_lib) WHERE acn.id = $1 - GROUP BY acn.id, o.shortname, o.opac_visible, deleted, label, label_sortkey, label_class, owning_lib, record; + GROUP BY acn.id, o.shortname, o.opac_visible, deleted, label, label_sortkey, label_class, owning_lib, record, acn.prefix, acn.suffix; +$F$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION unapi.acnp ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name call_number_prefix, + XMLATTRIBUTES( + 'http://open-ils.org/spec/holdings/v1' AS xmlns, + id AS ident, + label, + label_sortkey + ), + unapi.aou( owning_lib, $2, 'owning_lib', array_remove_item_by_value($4,'acnp'), $5, $6, $7, $8) + ) + FROM asset.call_number_prefix + WHERE id = $1; +$F$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION unapi.acns ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name call_number_suffix, + XMLATTRIBUTES( + 'http://open-ils.org/spec/holdings/v1' AS xmlns, + id AS ident, + label, + label_sortkey + ), + unapi.aou( owning_lib, $2, 'owning_lib', array_remove_item_by_value($4,'acns'), $5, $6, $7, $8) + ) + FROM asset.call_number_suffix + WHERE id = $1; $F$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION unapi.auri ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL ) RETURNS XML AS $F$ diff --git a/Open-ILS/src/sql/Pg/upgrade/0504.schema.parts_and_cnaffix.sql b/Open-ILS/src/sql/Pg/upgrade/0504.schema.parts_and_cnaffix.sql new file mode 100644 index 0000000000..5245e37f6c --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/0504.schema.parts_and_cnaffix.sql @@ -0,0 +1,321 @@ +BEGIN; + +INSERT INTO config.upgrade_log (version) VALUES ('0504'); -- miker + +CREATE TABLE biblio.monograph_part ( + id SERIAL PRIMARY KEY, + record BIGINT NOT NULL REFERENCES biblio.record_entry (id), + label TEXT NOT NULL, + label_sortkey TEXT NOT NULL, + CONSTRAINT record_label_unique UNIQUE (record,label) +); + +CREATE OR REPLACE FUNCTION biblio.normalize_biblio_monograph_part_sortkey () RETURNS TRIGGER AS $$ +BEGIN + NEW.label_sortkey := REGEXP_REPLACE( + lpad_number_substrings( + naco_normalize(NEW.label), + '0', + 10 + ), + E'\\s+', + '', + 'g' + ); + RETURN NEW; +END; +$$ LANGUAGE PLPGSQL; + +CREATE TRIGGER norm_sort_label BEFORE INSERT OR UPDATE ON biblio.monograph_part FOR EACH ROW EXECUTE PROCEDURE biblio.normalize_biblio_monograph_part_sortkey(); + +CREATE TABLE asset.copy_part_map ( + id SERIAL PRIMARY KEY, + target_copy BIGINT NOT NULL, -- points o asset.copy + part INT NOT NULL REFERENCES biblio.monograph_part (id) ON DELETE CASCADE +); +CREATE UNIQUE INDEX copy_part_map_cp_part_idx ON asset.copy_part_map (target_copy, part); + +CREATE OR REPLACE FUNCTION asset.normalize_affix_sortkey () RETURNS TRIGGER AS $$ +BEGIN + NEW.label_sortkey := REGEXP_REPLACE( + lpad_number_substrings( + naco_normalize(NEW.label), + '0', + 10 + ), + E'\\s+', + '', + 'g' + ); + RETURN NEW; +END; +$$ LANGUAGE PLPGSQL; + +CREATE TABLE asset.call_number_prefix ( + id SERIAL PRIMARY KEY, + owning_lib INT NOT NULL REFERENCES actor.org_unit (id), + label TEXT NOT NULL, -- i18n + label_sortkey TEXT +); +CREATE TRIGGER prefix_normalize_tgr BEFORE INSERT OR UPDATE ON asset.call_number_prefix FOR EACH ROW EXECUTE PROCEDURE asset.normalize_affix_sortkey(); +CREATE UNIQUE INDEX asset_call_number_prefix_once_per_lib ON asset.call_number_prefix (label, owning_lib); +CREATE INDEX asset_call_number_prefix_sortkey_idx ON asset.call_number_prefix (label_sortkey); + +CREATE TABLE asset.call_number_suffix ( + id SERIAL PRIMARY KEY, + owning_lib INT NOT NULL REFERENCES actor.org_unit (id), + label TEXT NOT NULL, -- i18n + label_sortkey TEXT +); +CREATE TRIGGER suffix_normalize_tgr BEFORE INSERT OR UPDATE ON asset.call_number_suffix FOR EACH ROW EXECUTE PROCEDURE asset.normalize_affix_sortkey(); +CREATE UNIQUE INDEX asset_call_number_suffix_once_per_lib ON asset.call_number_suffix (label, owning_lib); +CREATE INDEX asset_call_number_suffix_sortkey_idx ON asset.call_number_suffix (label_sortkey); + +INSERT INTO asset.call_number_suffix (id, owning_lib, label) VALUES (-1, 1, ''); +INSERT INTO asset.call_number_prefix (id, owning_lib, label) VALUES (-1, 1, ''); + +DROP INDEX IF EXISTS asset.asset_call_number_label_once_per_lib; + +ALTER TABLE asset.call_number + ADD COLUMN prefix INT NOT NULL DEFAULT -1 REFERENCES asset.call_number_prefix(id) DEFERRABLE INITIALLY DEFERRED, + ADD COLUMN suffix INT NOT NULL DEFAULT -1 REFERENCES asset.call_number_suffix(id) DEFERRABLE INITIALLY DEFERRED; + +CREATE UNIQUE INDEX asset_call_number_label_once_per_lib ON asset.call_number (record, owning_lib, label, prefix, suffix) WHERE deleted = FALSE OR deleted IS FALSE; + +INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES ( + 'ui.cat.volume_copy_editor.horizontal', + oils_i18n_gettext( + 'ui.cat.volume_copy_editor.horizontal', + 'GUI: Horizontal layout for Volume/Copy Creator/Editor.', + 'coust', 'label'), + oils_i18n_gettext( + 'ui.cat.volume_copy_editor.horizontal', + 'The main entry point for this interface is in Holdings Maintenance, Actions for Selected Rows, Edit Item Attributes / Call Numbers / Replace Barcodes. This setting changes the top and bottom panes for that interface into left and right panes.', + 'coust', 'description'), + 'bool' +); + + +CREATE OR REPLACE FUNCTION unapi.bmp ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name monograph_part, + XMLATTRIBUTES( + 'http://open-ils.org/spec/holdings/v1' AS xmlns, + 'tag:open-ils.org:U2@bmp/' || id AS id, + id AS ident, + label, + label_sortkey, + 'tag:open-ils.org:U2@bre/' || record AS record + ), + CASE + WHEN ('acp' = ANY ($4)) THEN + XMLELEMENT( name copies, + (SELECT XMLAGG(acp) FROM ( + SELECT unapi.acp( cp.id, 'xml', 'copy', array_remove_item_by_value($4,'bmp'), $5, $6, $7, $8) + FROM asset.copy cp + JOIN asset.copy_part_map cpm ON (cpm.target_copy = cp.id) + WHERE cpm.part = $1 + ORDER BY COALESCE(cp.copy_number,0), cp.barcode + LIMIT $7 + OFFSET $8 + )x) + ) + ELSE NULL + END, + CASE WHEN ('bre' = ANY ($4)) THEN unapi.bre( record, 'marcxml', 'record', array_remove_item_by_value($4,'bmp'), $5, $6, $7, $8) ELSE NULL END + ) + FROM biblio.monograph_part + WHERE id = $1 + GROUP BY id, label, label_sortkey, record; +$F$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION unapi.acnp ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name call_number_prefix, + XMLATTRIBUTES( + 'http://open-ils.org/spec/holdings/v1' AS xmlns, + id AS ident, + label, + label_sortkey + ), + unapi.aou( owning_lib, $2, 'owning_lib', array_remove_item_by_value($4,'acnp'), $5, $6, $7, $8) + ) + FROM asset.call_number_prefix + WHERE id = $1; +$F$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION unapi.acns ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name call_number_suffix, + XMLATTRIBUTES( + 'http://open-ils.org/spec/holdings/v1' AS xmlns, + id AS ident, + label, + label_sortkey + ), + unapi.aou( owning_lib, $2, 'owning_lib', array_remove_item_by_value($4,'acns'), $5, $6, $7, $8) + ) + FROM asset.call_number_suffix + WHERE id = $1; +$F$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION unapi.holdings_xml (bid BIGINT, ouid INT, org TEXT, depth INT DEFAULT NULL, includes TEXT[] DEFAULT NULL::TEXT[], slimit INT DEFAULT NULL, soffset INT DEFAULT NULL) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name holdings, + XMLATTRIBUTES( + 'http://open-ils.org/spec/holdings/v1' AS xmlns, + CASE WHEN ('bre' = ANY ('{acn,auri}'::TEXT[] || $5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id + ), + XMLELEMENT( + name counts, + (SELECT XMLAGG(XMLELEMENT::XML) FROM ( + SELECT XMLELEMENT( + name count, + XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow) + )::text + FROM asset.opac_ou_record_copy_count($2, $1) + UNION + SELECT XMLELEMENT( + name count, + XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow) + )::text + FROM asset.staff_ou_record_copy_count($2, $1) + ORDER BY 1 + )x) + ), + CASE + WHEN ('bmp' = ANY ($5)) THEN + XMLELEMENT( name monograph_parts, + XMLAGG((SELECT unapi.bmp( id, 'xml', 'monograph_part', array_remove_item_by_value( array_remove_item_by_value($5,'bre'), 'holdings_xml'), $3, $4, $6, $7) FROM biblio.monograph_part WHERE record = $1)) + ) + ELSE NULL + END, + CASE WHEN ('acn' = ANY ('{acn,auri}'::TEXT[] || $5)) THEN + XMLELEMENT( + name volumes, + (SELECT XMLAGG(acn) FROM ( + SELECT unapi.acn(acn.id,'xml','volume',array_remove_item_by_value(array_remove_item_by_value('{acn,auri}'::TEXT[] || $5,'holdings_xml'),'bre'), $3, $4, $6, $7) + FROM asset.call_number acn + WHERE acn.record = $1 + AND EXISTS ( + SELECT 1 + FROM asset.copy acp + JOIN actor.org_unit_descendants( + $2, + (COALESCE( + $4, + (SELECT aout.depth + FROM actor.org_unit_type aout + JOIN actor.org_unit aou ON (aou.ou_type = aout.id AND aou.id = $2) + ) + )) + ) aoud ON (acp.circ_lib = aoud.id) + LIMIT 1 + ) + ORDER BY label_sortkey + LIMIT $6 + OFFSET $7 + )x) + ) + ELSE NULL END, + CASE WHEN ('ssub' = ANY ('{acn,auri}'::TEXT[] || $5)) THEN + XMLELEMENT( + name subscriptions, + (SELECT XMLAGG(ssub) FROM ( + SELECT unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7) + FROM serial.subscription + WHERE record_entry = $1 + )x) + ) + ELSE NULL END + ); +$F$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION unapi.acp ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name copy, + XMLATTRIBUTES( + 'http://open-ils.org/spec/holdings/v1' AS xmlns, + 'tag:open-ils.org:U2@acp/' || id AS id, + create_date, edit_date, copy_number, circulate, deposit, + ref, holdable, deleted, deposit_amount, price, barcode, + circ_modifier, circ_as_type, opac_visible + ), + unapi.ccs( status, $2, 'status', array_remove_item_by_value($4,'acp'), $5, $6, $7, $8), + unapi.acl( location, $2, 'location', array_remove_item_by_value($4,'acp'), $5, $6, $7, $8), + unapi.aou( circ_lib, $2, 'circ_lib', array_remove_item_by_value($4,'acp'), $5, $6, $7, $8), + unapi.aou( circ_lib, $2, 'circlib', array_remove_item_by_value($4,'acp'), $5, $6, $7, $8), + CASE WHEN ('acn' = ANY ($4)) THEN unapi.acn( call_number, $2, 'call_number', array_remove_item_by_value($4,'acp'), $5, $6, $7, $8) ELSE NULL END, + XMLELEMENT( name copy_notes, + CASE + WHEN ('acpn' = ANY ($4)) THEN + XMLAGG((SELECT unapi.acpn( id, 'xml', 'copy_note', array_remove_item_by_value($4,'acp'), $5, $6, $7, $8) FROM asset.copy_note WHERE owning_copy = cp.id AND pub)) + ELSE NULL + END + ), + XMLELEMENT( name statcats, + CASE + WHEN ('ascecm' = ANY ($4)) THEN + XMLAGG((SELECT unapi.ascecm( stat_cat_entry, 'xml', 'statcat', array_remove_item_by_value($4,'acp'), $5, $6, $7, $8) FROM asset.stat_cat_entry_copy_map WHERE owning_copy = cp.id)) + ELSE NULL + END + ), + CASE + WHEN ('bmp' = ANY ($4)) THEN + XMLELEMENT( name monograph_parts, + XMLAGG((SELECT unapi.bmp( part, 'xml', 'monograph_part', array_remove_item_by_value($4,'acp'), $5, $6, $7, $8) FROM asset.copy_part_map WHERE target_copy = cp.id)) + ) + ELSE NULL + END + ) + FROM asset.copy cp + WHERE id = $1 + GROUP BY id, status, location, circ_lib, call_number, create_date, edit_date, copy_number, circulate, deposit, ref, holdable, deleted, deposit_amount, price, barcode, circ_modifier, circ_as_type, opac_visible; +$F$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION unapi.acn ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name volume, + XMLATTRIBUTES( + 'http://open-ils.org/spec/holdings/v1' AS xmlns, + 'tag:open-ils.org:U2@acn/' || acn.id AS id, + o.shortname AS lib, + o.opac_visible AS opac_visible, + deleted, label, label_sortkey, label_class, record + ), + unapi.aou( owning_lib, $2, 'owning_lib', array_remove_item_by_value($4,'acn'), $5, $6, $7, $8), + XMLELEMENT( name copies, + CASE + WHEN ('acp' = ANY ($4)) THEN + (SELECT XMLAGG(acp) FROM ( + SELECT unapi.acp( cp.id, 'xml', 'copy', array_remove_item_by_value($4,'acn'), $5, $6, $7, $8) + FROM asset.copy cp + JOIN actor.org_unit_descendants( + (SELECT id FROM actor.org_unit WHERE shortname = $5), + (COALESCE($6,(SELECT aout.depth FROM actor.org_unit_type aout JOIN actor.org_unit aou ON (aou.ou_type = aout.id AND aou.shortname = $5)))) + ) aoud ON (cp.circ_lib = aoud.id) + WHERE cp.call_number = acn.id + ORDER BY COALESCE(cp.copy_number,0), cp.barcode + LIMIT $7 + OFFSET $8 + )x) + ELSE NULL + END + ), + XMLELEMENT( + name uris, + (SELECT XMLAGG(auri) FROM (SELECT unapi.auri(uri,'xml','uri', array_remove_item_by_value($4,'acn'), $5, $6, $7, $8) FROM asset.uri_call_number_map WHERE call_number = acn.id)x) + ), + CASE WHEN ('acnp' = ANY ($4)) THEN unapi.acnp( acn.prefix, 'marcxml', 'prefix', array_remove_item_by_value($4,'acn'), $5, $6, $7, $8) ELSE NULL END, + CASE WHEN ('acns' = ANY ($4)) THEN unapi.acns( acn.suffix, 'marcxml', 'suffix', array_remove_item_by_value($4,'acn'), $5, $6, $7, $8) ELSE NULL END, + CASE WHEN ('bre' = ANY ($4)) THEN unapi.bre( acn.record, 'marcxml', 'record', array_remove_item_by_value($4,'acn'), $5, $6, $7, $8) ELSE NULL END + ) AS x + FROM asset.call_number acn + JOIN actor.org_unit o ON (o.id = acn.owning_lib) + WHERE acn.id = $1 + GROUP BY acn.id, o.shortname, o.opac_visible, deleted, label, label_sortkey, label_class, owning_lib, record, acn.prefix, acn.suffix; +$F$ LANGUAGE SQL; + + +COMMIT; + diff --git a/Open-ILS/web/js/ui/default/acq/common/li_table.js b/Open-ILS/web/js/ui/default/acq/common/li_table.js index acb96734d1..044335ef72 100644 --- a/Open-ILS/web/js/ui/default/acq/common/li_table.js +++ b/Open-ILS/web/js/ui/default/acq/common/li_table.js @@ -2467,7 +2467,6 @@ function AcqLiTable() { copyList.push(copy); } if (xulG) { - // If we need to, we can pass in an update_copy function to handle the update instead of volume_item_creator xulG.volume_item_creator( { 'existing_copies' : copyList } ); } } catch(E) { diff --git a/Open-ILS/web/js/ui/default/conify/global/config/acn_prefix.js b/Open-ILS/web/js/ui/default/conify/global/config/acn_prefix.js new file mode 100644 index 0000000000..2d307a8906 --- /dev/null +++ b/Open-ILS/web/js/ui/default/conify/global/config/acn_prefix.js @@ -0,0 +1,72 @@ +dojo.require('dojox.grid.DataGrid'); +dojo.require('openils.widget.AutoGrid'); +dojo.require('dojox.grid.cells.dijit'); +dojo.require('dojo.data.ItemFileWriteStore'); +dojo.require('dijit.form.CurrencyTextBox'); +dojo.require('dijit.Dialog'); +dojo.require('dojox.widget.PlaceholderMenuItem'); +dojo.require('fieldmapper.OrgUtils'); +dojo.require('dijit.form.FilteringSelect'); +dojo.require('openils.PermaCrud'); +dojo.require('openils.widget.OrgUnitFilteringSelect'); + +var thingContextOrg; +var thingList; + +/** really need to put this in a shared location... */ +function getOrgInfo(rowIndex, item) { + if(!item) return ''; + var orgId = this.grid.store.getValue(item, this.field); + return fieldmapper.aou.findOrgUnit(orgId).shortname(); +} + +function thingInit() { + + thingGrid.disableSelectorForRow = function(rowIdx) { + var item = thingGrid.getItem(rowIdx); + return (thingGrid.store.getValue(item, 'id') < 0); + } + + buildGrid(); + var connect = function() { + dojo.connect(thingContextOrgSelect, 'onChange', + function() { + thingContextOrg = this.getValue(); + thingGrid.resetStore(); + buildGrid(); + } + ); + }; + // go ahead and let staff see everything + new openils.User().buildPermOrgSelector('STAFF_LOGIN', thingContextOrgSelect, null, connect); +} + +function buildGrid() { + if(thingContextOrg == null) + thingContextOrg = openils.User.user.ws_ou(); + + fieldmapper.standardRequest( + ['open-ils.pcrud', 'open-ils.pcrud.search.acnp.atomic'], + { async: true, + params: [ + openils.User.authtoken, + {"owning_lib":fieldmapper.aou.descendantNodeList(thingContextOrg,true)}, + {"order_by":{"acnp":"label_sortkey"}} + ], + oncomplete: function(r) { + if(thingList = openils.Util.readResponse(r)) { + thingList = openils.Util.objectSort(thingList); + dojo.forEach(thingList, + function(e) { + thingGrid.store.newItem(acnp.toStoreItem(e)); + } + ); + } + } + } + ); +} + +openils.Util.addOnLoad(thingInit); + + diff --git a/Open-ILS/web/js/ui/default/conify/global/config/acn_suffix.js b/Open-ILS/web/js/ui/default/conify/global/config/acn_suffix.js new file mode 100644 index 0000000000..c79cd0e569 --- /dev/null +++ b/Open-ILS/web/js/ui/default/conify/global/config/acn_suffix.js @@ -0,0 +1,72 @@ +dojo.require('dojox.grid.DataGrid'); +dojo.require('openils.widget.AutoGrid'); +dojo.require('dojox.grid.cells.dijit'); +dojo.require('dojo.data.ItemFileWriteStore'); +dojo.require('dijit.form.CurrencyTextBox'); +dojo.require('dijit.Dialog'); +dojo.require('dojox.widget.PlaceholderMenuItem'); +dojo.require('fieldmapper.OrgUtils'); +dojo.require('dijit.form.FilteringSelect'); +dojo.require('openils.PermaCrud'); +dojo.require('openils.widget.OrgUnitFilteringSelect'); + +var thingContextOrg; +var thingList; + +/** really need to put this in a shared location... */ +function getOrgInfo(rowIndex, item) { + if(!item) return ''; + var orgId = this.grid.store.getValue(item, this.field); + return fieldmapper.aou.findOrgUnit(orgId).shortname(); +} + +function thingInit() { + + thingGrid.disableSelectorForRow = function(rowIdx) { + var item = thingGrid.getItem(rowIdx); + return (thingGrid.store.getValue(item, 'id') < 0); + } + + buildGrid(); + var connect = function() { + dojo.connect(thingContextOrgSelect, 'onChange', + function() { + thingContextOrg = this.getValue(); + thingGrid.resetStore(); + buildGrid(); + } + ); + }; + // go ahead and let staff see everything + new openils.User().buildPermOrgSelector('STAFF_LOGIN', thingContextOrgSelect, null, connect); +} + +function buildGrid() { + if(thingContextOrg == null) + thingContextOrg = openils.User.user.ws_ou(); + + fieldmapper.standardRequest( + ['open-ils.pcrud', 'open-ils.pcrud.search.acns.atomic'], + { async: true, + params: [ + openils.User.authtoken, + {"owning_lib":fieldmapper.aou.descendantNodeList(thingContextOrg,true)}, + {"order_by":{"acns":"label_sortkey"}} + ], + oncomplete: function(r) { + if(thingList = openils.Util.readResponse(r)) { + thingList = openils.Util.objectSort(thingList); + dojo.forEach(thingList, + function(e) { + thingGrid.store.newItem(acns.toStoreItem(e)); + } + ); + } + } + } + ); +} + +openils.Util.addOnLoad(thingInit); + + diff --git a/Open-ILS/web/opac/locale/en-US/lang.dtd b/Open-ILS/web/opac/locale/en-US/lang.dtd index f9fcd6eb83..e8bb029fb8 100644 --- a/Open-ILS/web/opac/locale/en-US/lang.dtd +++ b/Open-ILS/web/opac/locale/en-US/lang.dtd @@ -284,6 +284,8 @@ + + @@ -708,6 +710,8 @@ + + @@ -2368,6 +2372,7 @@ + @@ -2462,7 +2467,7 @@ - + @@ -2486,7 +2491,6 @@ - @@ -2513,7 +2517,7 @@ - + @@ -2766,6 +2770,12 @@ + + + + + + @@ -2774,6 +2784,11 @@ + + + + + diff --git a/Open-ILS/web/opac/locale/en-US/opac.dtd b/Open-ILS/web/opac/locale/en-US/opac.dtd index aef4bbb430..c1ebf6bec9 100644 --- a/Open-ILS/web/opac/locale/en-US/opac.dtd +++ b/Open-ILS/web/opac/locale/en-US/opac.dtd @@ -202,6 +202,7 @@ avoid using bookbags all together. Thank you."> + @@ -488,6 +489,7 @@ Please see a librarian to renew your account."> Rdetail ================================================================= --> + @@ -592,6 +594,7 @@ We recommend that you remove this title from any bookbags it may have been added + diff --git a/Open-ILS/web/opac/skin/default/js/copy_details.js b/Open-ILS/web/opac/skin/default/js/copy_details.js index e5c50c0939..04e8e416f5 100644 --- a/Open-ILS/web/opac/skin/default/js/copy_details.js +++ b/Open-ILS/web/opac/skin/default/js/copy_details.js @@ -208,6 +208,7 @@ function cpdDrawCopies(r) { function cpdDrawCopy(r) { var copy = r.getResultObject(); var row = r.row; + var trow = r.args.templateRow; if (r.args.copy_location && copy.location().name() != r.args.copy_location) { hideMe(row); @@ -218,6 +219,18 @@ function cpdDrawCopy(r) { $n(row, 'location').appendChild(text(copy.location().name())); $n(row, 'status').appendChild(text(copy.status().name())); + // append comma-separated list of part this copy is linked to + if(copy.parts() && copy.parts().length) { + unHideMe($n(trow, 'copy_part_label')); + unHideMe($n(row, 'copy_part')); + for(var i = 0; i < copy.parts().length; i++) { + var part = copy.parts()[i]; + var node = $n(row, 'copy_part'); + if(i > 0) node.appendChild(text(',')); + node.appendChild(text(part.label())); + } + } + if(isXUL()) { /* show the hold link */ var l = $n(row, 'copy_hold_link'); diff --git a/Open-ILS/web/opac/skin/default/js/holds.js b/Open-ILS/web/opac/skin/default/js/holds.js index 748f98c27e..73514c1243 100644 --- a/Open-ILS/web/opac/skin/default/js/holds.js +++ b/Open-ILS/web/opac/skin/default/js/holds.js @@ -16,7 +16,8 @@ var holdTargetTypeMap = { T : 'record', V : 'volume', I : 'issuance', - C : 'copy' + C : 'copy', + P : 'part' }; @@ -228,8 +229,11 @@ function holdFetchObjects(hold, doneCallback) { } else if( type == 'I' ) { _h_set_issuance(args, doneCallback); + } else if( type == 'P' ) { + _h_set_parts(args, doneCallback); + } else { - if( type == 'T' ) { + if( type == 'T') { _h_set_rec(args, doneCallback); } else { _h_set_rec_descriptors(args, doneCallback); @@ -240,6 +244,25 @@ function holdFetchObjects(hold, doneCallback) { return args; } +function _h_set_parts(args, doneCallback) { + + var preq = new Request( + 'open-ils.fielder:open-ils.fielder.bmp.atomic', + {"cache":1, "fields":["label", "record"],"query": {"id":args.part}} + ); + + preq.callback( + function(r) { + var part = r.getResultObject()[0]; + args.record = part.record; + args.partObject = part; + _h_set_rec(args, doneCallback); + } + ); + + preq.send(); +} + function _h_set_vol(args, doneCallback) { if( args.volumeObject ) { @@ -291,10 +314,13 @@ function _h_set_rec(args, doneCallback) { else args.recordObject = findRecord( args.record, 'T' ); - if( args.type == 'T' || args.type == 'M' ) + if( args.type == 'T' || args.type == 'M' ) { _h_set_rec_descriptors(args, doneCallback); - else + //} else if(args.type == 'P') { + //_h_get_parts(args, doneCallback); + } else { if(doneCallback) doneCallback(args); + } } @@ -304,7 +330,7 @@ function _h_set_rec_descriptors(args, doneCallback) { args.pickup_lib = getSelectorVal($('holds_org_selector')); if(args.pickup_lib === null) - args.pickup_lib = holdArgs.recipient.home_ou(); + args.pickup_lib = args.recipient.home_ou(); // grab the list of record desciptors attached to this records metarecord if( ! args.recordDescriptors ) { @@ -332,23 +358,47 @@ function _h_set_rec_descriptors(args, doneCallback) { req.callback( function(r) { var data = r.getResultObject(); - holdArgs.recordDescriptors = args.recordDescriptors = data.descriptors; - holdArgs.metarecord = args.metarecord = data.metarecord; + args.recordDescriptors = args.recordDescriptors = data.descriptors; + args.metarecord = args.metarecord = data.metarecord; if( args.type == 'M' && ! args.metarecordObject) - holdArgs.metarecordObject = args.metarecordObject = findRecord(args.metarecord, 'M'); + args.metarecordObject = args.metarecordObject = findRecord(args.metarecord, 'M'); - if(doneCallback) doneCallback(args); + _h_get_parts(args, doneCallback); } ); req.send(); } else { - if(doneCallback) doneCallback(args); + _h_get_parts(args, doneCallback); } return args; } +function _h_get_parts(args, doneCallback) { + + if(args.type == 'M' || args.editHold || args.holdParts) { + if(doneCallback) + doneCallback(args); + + } else { + + var req = new Request( + 'open-ils.search:open-ils.search.biblio.record_hold_parts', + {pickup_lib: args.pickup_lib, record: args.record} + ); + + req.callback( + function(r) { + args.recordParts = r.getResultObject(); + if(doneCallback) + doneCallback(args); + } + ); + req.send(); + } +} + function holdsDrawWindow() { @@ -453,6 +503,30 @@ function __holdsDrawWindow() { hideMe($('holds_issuance_row')); } + if(holdArgs.recordParts && holdArgs.recordParts.length) { + var selector = $('holds_parts_selector'); + unHideMe($('holds_parts_row')); + unHideMe(selector); + + var nodeList = []; + dojo.forEach(selector.options, + function(node) { if(node.value != '') nodeList.push(node) } ); + + dojo.forEach(nodeList, function(node) { selector.removeChild(node); }); + + dojo.forEach( + holdArgs.recordParts, + function(part) { + insertSelectorVal(selector, -1, part.label, part.id); + } + ); + + } else if(holdArgs.type == 'P') { + unHideMe($('holds_parts_row')); + unHideMe($('holds_parts_label')); + appendClear( $('holds_parts_label'), text(holdArgs.partObject.label)); + } + removeChildren($('holds_format')); var mods_formats = rec.types_of_resource(); @@ -623,6 +697,20 @@ function holdsSetFormatSelector() { if(type=='M') opt.selected=true; unHideMe(opt); } + + // If the user selects a format, P-type holds are no longer an option + // disable and reset the P-type form control + selector.onchange = function() { + var partsSel = $('holds_parts_selector'); + for(var i = 0; i < selector.options.length; i++) { + if(selector.options[i].selected) { + partsSel.selectedIndex = 0; // none selected + partsSel.disabled = true; + return; + } + } + partsSel.disabled = false; + } } function findFormatSelectorOptByParts( sel, val ) { @@ -747,7 +835,8 @@ function holdsCheckPossibility(pickuplib, hold, recurse) { hold_type : holdArgs.type, patronid : holdArgs.recipient.id(), depth : 0, - pickup_lib : pickuplib + pickup_lib : pickuplib, + partid : holdArgs.part }; if(recurse) { @@ -839,8 +928,16 @@ function holdsBuildHoldFromWindow() { else hold.email_notify(0); + var part = getSelectorVal($('holds_parts_selector')); + if(part) { + holdArgs.type = 'P'; + holdArgs.part = part; + } + var target = holdArgs[holdTargetTypeMap[holdArgs.type]]; + // a mono part is selected + hold.pickup_lib(org); //hold.request_lib(org); hold.requestor(holdArgs.requestor.id()); diff --git a/Open-ILS/web/opac/skin/default/js/myopac.js b/Open-ILS/web/opac/skin/default/js/myopac.js index 6fd49f99c8..819f8aa048 100644 --- a/Open-ILS/web/opac/skin/default/js/myopac.js +++ b/Open-ILS/web/opac/skin/default/js/myopac.js @@ -499,7 +499,7 @@ function myOShowHoldStatus(r) { function myOPACDrawHoldTitle(hold) { var method; - if( hold.hold_type() == 'T' || hold.hold_type() == 'M' ) { + if( hold.hold_type() == 'T' || hold.hold_type() == 'M') { if(hold.hold_type() == "M") method = FETCH_MRMODS; if(hold.hold_type() == "T") method = FETCH_RMODS; var req = new Request(method, hold.target()); @@ -521,7 +521,7 @@ function myOPACFleshHoldTitle(r) { function _myOPACFleshHoldTitle(hold, holdObjects) { - var record = holdObjects.recordObject; + var record = holdObjects.recordObject; var volume = holdObjects.volumeObject; var copy = holdObjects.copyObject; @@ -539,6 +539,11 @@ function _myOPACFleshHoldTitle(hold, holdObjects) { buildTitleDetailLink(record, title_link); buildSearchLink(STYPE_AUTHOR, record.author(), author_link); + if(hold.hold_type() == 'P') { + unHideMe($n(row, 'vol_copy')); + $n(row, 'part').appendChild(text(holdObjects.partObject.label)); + } + if( volume ) { $n(row, 'volume').appendChild(text(volume.label())); unHideMe($n(row, 'vol_copy')); diff --git a/Open-ILS/web/opac/skin/default/js/rresult.js b/Open-ILS/web/opac/skin/default/js/rresult.js index ff94bb8158..6dccc497ec 100644 --- a/Open-ILS/web/opac/skin/default/js/rresult.js +++ b/Open-ILS/web/opac/skin/default/js/rresult.js @@ -279,6 +279,17 @@ function rresultCollectRecords(ids, base) { runEvt("result", "preCollectRecords"); var x = 0; + // don't perform rdetail redirect if user was on rdetail and cliecked Back + if(findCurrentPage() == RRESULT && isXUL()) { + if(ids.length == 1 && !xulG.fromBack) { + var args = {}; + args.page = RDETAIL; + args[PARAM_OFFSET] = 0; + args[PARAM_RID] = ids[0]; + location.href = buildOPACLink(args); + } + } + if (!base) base = 0; if( rresultIsPaged ) base = 0; diff --git a/Open-ILS/web/opac/skin/default/xml/common/holds.xml b/Open-ILS/web/opac/skin/default/xml/common/holds.xml index 962a366fbe..7d8712d235 100644 --- a/Open-ILS/web/opac/skin/default/xml/common/holds.xml +++ b/Open-ILS/web/opac/skin/default/xml/common/holds.xml @@ -51,6 +51,16 @@ + + &common.mono_parts.label; + + + + + + &common.call.number.label; diff --git a/Open-ILS/web/opac/skin/default/xml/myopac/myopac_holds.xml b/Open-ILS/web/opac/skin/default/xml/myopac/myopac_holds.xml index 4d5b46db66..08d16906d1 100644 --- a/Open-ILS/web/opac/skin/default/xml/myopac/myopac_holds.xml +++ b/Open-ILS/web/opac/skin/default/xml/myopac/myopac_holds.xml @@ -71,8 +71,9 @@
-
-
+
+
+
diff --git a/Open-ILS/web/opac/skin/default/xml/rdetail/rdetail_cn_details.xml b/Open-ILS/web/opac/skin/default/xml/rdetail/rdetail_cn_details.xml index 09765fd4e9..45a5c85d5d 100644 --- a/Open-ILS/web/opac/skin/default/xml/rdetail/rdetail_cn_details.xml +++ b/Open-ILS/web/opac/skin/default/xml/rdetail/rdetail_cn_details.xml @@ -13,6 +13,7 @@ &rdetail.cn.barcode; &common.status; &rdetail.cn.location; + &rdetail.cn.part; &rdetail.cn.hold.age; &rdetail.cn.genesis; &rdetail.cn.holdable; @@ -35,6 +36,7 @@ + &rdetail.cn.disabled; diff --git a/Open-ILS/web/templates/default/conify/global/biblio/monograph_part.tt2 b/Open-ILS/web/templates/default/conify/global/biblio/monograph_part.tt2 new file mode 100644 index 0000000000..ee1256b7e4 --- /dev/null +++ b/Open-ILS/web/templates/default/conify/global/biblio/monograph_part.tt2 @@ -0,0 +1,37 @@ +[% WRAPPER default/base.tt2 %] +[% ctx.page_title = 'Configure Monograph Parts' %] +
+
+
Monograph Parts
+
+ + +
+
+
+ + + + +[% END %] + + diff --git a/Open-ILS/web/templates/default/conify/global/config/acn_prefix.tt2 b/Open-ILS/web/templates/default/conify/global/config/acn_prefix.tt2 new file mode 100644 index 0000000000..5e7f64080a --- /dev/null +++ b/Open-ILS/web/templates/default/conify/global/config/acn_prefix.tt2 @@ -0,0 +1,37 @@ +[% WRAPPER default/base.tt2 %] +[% ctx.page_title = 'Call Number Prefixes' %] + + + + +
+
+
Call Number Prefixes
+
+ + +
+
+
+ Context Org Unit + +
+
+ + + +
+
+
+[% END %] + + diff --git a/Open-ILS/web/templates/default/conify/global/config/acn_suffix.tt2 b/Open-ILS/web/templates/default/conify/global/config/acn_suffix.tt2 new file mode 100644 index 0000000000..40dcbb962f --- /dev/null +++ b/Open-ILS/web/templates/default/conify/global/config/acn_suffix.tt2 @@ -0,0 +1,37 @@ +[% WRAPPER default/base.tt2 %] +[% ctx.page_title = 'Call Number Suffixes' %] + + + + +
+
+
Call Number Suffixes
+
+ + +
+
+
+ Context Org Unit + +
+ + + + +
+
+
+[% END %] + + diff --git a/Open-ILS/xul/staff_client/chrome/content/OpenILS/data.js b/Open-ILS/xul/staff_client/chrome/content/OpenILS/data.js index 6db3c02064..e66fd4a75a 100644 --- a/Open-ILS/xul/staff_client/chrome/content/OpenILS/data.js +++ b/Open-ILS/xul/staff_client/chrome/content/OpenILS/data.js @@ -534,6 +534,9 @@ OpenILS.data.prototype = { } } + // If we don't clear these, then things like obj.list['acnp_for_lib_1'] may stick around + obj.hash = {}; obj.list = {}; + this.chain = []; this.chain.push( @@ -595,6 +598,27 @@ OpenILS.data.prototype = { } ); + this.chain.push( + function() { + var f = gen_fm_retrieval_func( + 'acnc', + [ + api.FM_ACNC_RETRIEVE_VIA_PCRUD.app, + api.FM_ACNC_RETRIEVE_VIA_PCRUD.method, + [ obj.session.key, {"id":{"!=":null}}, {"order_by":{"acnc":"name"}} ], + false + ] + ); + try { + f(); + } catch(E) { + var error = 'Error: ' + js2JSON(E); + obj.error.sdump('D_ERROR',error); + throw(E); + } + } + ); + this.chain.push( function() { var f = gen_fm_retrieval_func( @@ -827,7 +851,6 @@ OpenILS.data.prototype = { } ); - this.chain.push( function() { var f = gen_fm_retrieval_func( @@ -849,6 +872,50 @@ OpenILS.data.prototype = { } ); + this.chain.push( + function() { + var f = gen_fm_retrieval_func( + 'acnp', + [ + api.FM_ACNP_RETRIEVE_VIA_PCRUD.app, + api.FM_ACNP_RETRIEVE_VIA_PCRUD.method, + [ obj.session.key, {"owning_lib":{"=":obj.list.au[0].ws_ou()}}, {"order_by":{"acnp":"label_sortkey"}} ], + false + ] + ); + try { + f(); + obj.list['acnp_for_lib_'+obj.list.au[0].ws_ou()] = obj.list.acnp; + } catch(E) { + var error = 'Error: ' + js2JSON(E); + obj.error.sdump('D_ERROR',error); + throw(E); + } + } + ); + + this.chain.push( + function() { + var f = gen_fm_retrieval_func( + 'acns', + [ + api.FM_ACNS_RETRIEVE_VIA_PCRUD.app, + api.FM_ACNS_RETRIEVE_VIA_PCRUD.method, + [ obj.session.key, {"owning_lib":{"=":obj.list.au[0].ws_ou()}}, {"order_by":{"acns":"label_sortkey"}} ], + false + ] + ); + try { + f(); + obj.list['acns_for_lib_'+obj.list.au[0].ws_ou()] = obj.list.acns; + } catch(E) { + var error = 'Error: ' + js2JSON(E); + obj.error.sdump('D_ERROR',error); + throw(E); + } + } + ); + this.chain.push( function() { var f = gen_fm_retrieval_func( diff --git a/Open-ILS/xul/staff_client/chrome/content/OpenILS/global_util.js b/Open-ILS/xul/staff_client/chrome/content/OpenILS/global_util.js index 364d573121..befb693aec 100644 --- a/Open-ILS/xul/staff_client/chrome/content/OpenILS/global_util.js +++ b/Open-ILS/xul/staff_client/chrome/content/OpenILS/global_util.js @@ -413,6 +413,12 @@ function update_modal_xulG(v) { try { + if (typeof xulG != "undefined" && xulG.not_modal) { + xulG = v; + xulG.not_modal = true; + return; + } + JSAN.use('OpenILS.data'); var data = new OpenILS.data(); data.init({'via':'stash'}); var key = location.pathname + location.search + location.hash; if (typeof data.modal_xulG_stack != 'undefined' && typeof data.modal_xulG_stack[key] != 'undefined') { @@ -453,11 +459,15 @@ } if (typeof _params.no_xulG == 'undefined') { if (typeof _params.modal_xulG != 'undefined') { - JSAN.use('OpenILS.data'); var data = new OpenILS.data(); data.init({'via':'stash'}); - var key = location.pathname + location.search + location.hash; - //dump('xul_param, considering modal key = ' + key + '\n'); - if (typeof data.modal_xulG_stack != 'undefined' && typeof data.modal_xulG_stack[key] != 'undefined') { - xulG = data.modal_xulG_stack[key][ data.modal_xulG_stack[key].length - 1 ]; + if (typeof xulG != 'undefined' && xulG.not_modal) { + // for interfaces that used to be modal but aren't now, do nothing + } else { + JSAN.use('OpenILS.data'); var data = new OpenILS.data(); data.init({'via':'stash'}); + var key = location.pathname + location.search + location.hash; + //dump('xul_param, considering modal key = ' + key + '\n'); + if (typeof data.modal_xulG_stack != 'undefined' && typeof data.modal_xulG_stack[key] != 'undefined') { + xulG = data.modal_xulG_stack[key][ data.modal_xulG_stack[key].length - 1 ]; + } } } if (typeof xulG == 'object' && typeof xulG[ param_name ] != 'undefined') { diff --git a/Open-ILS/xul/staff_client/chrome/content/cat/opac.js b/Open-ILS/xul/staff_client/chrome/content/cat/opac.js index b5b15aba55..7c64f928d8 100644 --- a/Open-ILS/xul/staff_client/chrome/content/cat/opac.js +++ b/Open-ILS/xul/staff_client/chrome/content/cat/opac.js @@ -89,18 +89,27 @@ function opac_wrapper_set_help_context() { function set_brief_view() { var url = xulG.url_prefix( urls.XUL_BIB_BRIEF ) + '?docid=' + window.escape(docid); dump('spawning ' + url + '\n'); + + var content_params = { + 'set_tab_name' : function(n) { + if (typeof window.xulG == 'object' && typeof window.xulG.set_tab_name == 'function') { + try { window.xulG.set_tab_name(document.getElementById('offlineStrings').getFormattedString("cat.bib_record", [n])); } catch(E) { alert(E); } + } else { + dump('no set_tab_name\n'); + } + } + }; + + ["url_prefix", "new_tab", "set_tab", "close_tab", "new_patron_tab", + "set_patron_tab", "volume_item_creator", "get_new_session", + "holdings_maintenance_tab", "open_chrome_window", "url_prefix", + "network_meter", "page_meter", "set_statusbar", "set_help_context" + ].forEach(function(k) { content_params[k] = xulG[k]; }); + top_pane.set_iframe( url, - {}, - { - 'set_tab_name' : function(n) { - if (typeof window.xulG == 'object' && typeof window.xulG.set_tab_name == 'function') { - try { window.xulG.set_tab_name(document.getElementById('offlineStrings').getFormattedString("cat.bib_record", [n])); } catch(E) { alert(E); } - } else { - dump('no set_tab_name\n'); - } - } - } + {}, + content_params ); } @@ -172,13 +181,13 @@ function set_marc_edit() { JSAN.use('util.error'); error = new util.error(); JSAN.use('util.network'); var network = new util.network(); - var acn_id = network.simple_request( + var acn_blob = network.simple_request( 'FM_ACN_FIND_OR_CREATE', [ ses(), cn_label, doc_id, ses('ws_ou') ] ); - if (typeof acn_id.ilsevent != 'undefined') { - error.standard_unexpected_error_alert('Error in chrome/content/cat/opac.js, cat.util.fast_item_add', acn_id); + if (typeof acn_blob.ilsevent != 'undefined') { + error.standard_unexpected_error_alert('Error in chrome/content/cat/opac.js, cat.util.fast_item_add', acn_blob); return; } @@ -186,7 +195,7 @@ function set_marc_edit() { copy_obj.id( -1 ); copy_obj.isnew('1'); copy_obj.barcode( cp_barcode ); - copy_obj.call_number( acn_id ); + copy_obj.call_number( acn_blob.acn_id ); copy_obj.circ_lib( ses('ws_ou') ); /* FIXME -- use constants */ copy_obj.deposit(0); @@ -822,8 +831,10 @@ function add_volumes() { var title = document.getElementById('offlineStrings').getFormattedString('staff.circ.copy_status.add_volumes.title', [docid]); + var horizontal_interface = String( g.data.hash.aous['ui.cat.volume_copy_editor.horizontal'] ) == 'true'; + var url = window.xulG.url_prefix( horizontal_interface ? urls.XUL_VOLUME_COPY_CREATOR_HORIZONTAL : urls.XUL_VOLUME_COPY_CREATOR ); var w = xulG.new_tab( - window.xulG.url_prefix(urls.XUL_VOLUME_COPY_CREATOR), + url, { 'tab_name' : title }, { 'doc_id' : docid, 'ou_ids' : [ ses('ws_ou') ] } ); @@ -831,3 +842,20 @@ function add_volumes() { alert('Error in chrome/content/cat/opac.js, add_volumes(): ' + E); } } + +function manage_parts() { + try { + var title = document.getElementById('offlineStrings').getFormattedString('staff.cat.manage_parts.title', [docid]); + var loc = urls.XUL_BROWSER + "?url=" + window.escape( + window.xulG.url_prefix(urls.CONIFY_MANAGE_PARTS) + '?r=' + docid + ); + var w = xulG.new_tab( + loc, + { 'tab_name' : title }, + {} + ); + } catch(E) { + alert('Error in chrome/content/cat/opac.js, manage_parts(): ' + E); + } +} + diff --git a/Open-ILS/xul/staff_client/chrome/content/cat/opac.xul b/Open-ILS/xul/staff_client/chrome/content/cat/opac.xul index 5197d89c85..9e8549d53e 100644 --- a/Open-ILS/xul/staff_client/chrome/content/cat/opac.xul +++ b/Open-ILS/xul/staff_client/chrome/content/cat/opac.xul @@ -59,6 +59,7 @@ + diff --git a/Open-ILS/xul/staff_client/chrome/content/main/constants.js b/Open-ILS/xul/staff_client/chrome/content/main/constants.js index 8e8663edfd..802592d185 100644 --- a/Open-ILS/xul/staff_client/chrome/content/main/constants.js +++ b/Open-ILS/xul/staff_client/chrome/content/main/constants.js @@ -81,13 +81,16 @@ var api = { 'CHECKOUT_RENEW' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.renew' }, 'CIRC_MODIFIER_LIST' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.circ_modifier.retrieve.all' }, 'CLEAR_HOLD_SHELF' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.hold.clear_shelf.process', 'secure' : false }, - 'FM_ACN_RETRIEVE' : { 'app' : 'open-ils.search', 'method' : 'open-ils.search.callnumber.retrieve', 'secure' : false }, - 'FM_ACN_RETRIEVE.authoritative' : { 'app' : 'open-ils.search', 'method' : 'open-ils.search.callnumber.retrieve.authoritative', 'secure' : false }, + 'FM_ACN_RETRIEVE' : { 'app' : 'open-ils.search', 'method' : 'open-ils.search.callnumber.fleshed.retrieve', 'secure' : false }, + 'FM_ACN_RETRIEVE.authoritative' : { 'app' : 'open-ils.search', 'method' : 'open-ils.search.callnumber.fleshed.retrieve.authoritative', 'secure' : false }, 'FM_ACN_TREE_UPDATE' : { 'app' : 'open-ils.cat', 'method' : 'open-ils.cat.asset.volume.fleshed.batch.update' }, 'FM_ACN_TREE_LIST_RETRIEVE_VIA_RECORD_ID_AND_ORG_IDS' : { 'app' : 'open-ils.cat', 'method' : 'open-ils.cat.asset.copy_tree.retrieve', 'secure' : false }, 'FM_ACN_TREE_LIST_RETRIEVE_VIA_RECORD_ID_AND_ORG_IDS.authoritative' : { 'app' : 'open-ils.cat', 'method' : 'open-ils.cat.asset.copy_tree.retrieve.authoritative', 'secure' : false }, 'FM_ACN_TRANSFER' : { 'app' : 'open-ils.cat', 'method' : 'open-ils.cat.asset.volume.batch.transfer' }, - 'FM_ACN_FIND_OR_CREATE' : { 'app' : 'open-ils.cat', 'method' : 'open-ils.cat.call_number.find_or_create', 'secure' : false }, + 'FM_ACN_FIND_OR_CREATE' : { 'app' : 'open-ils.cat', 'method' : 'open-ils.cat.call_number.find_or_create' }, + 'FM_ACNC_RETRIEVE_VIA_PCRUD' : { 'app' : 'open-ils.pcrud', 'method' : 'open-ils.pcrud.search.acnc.atomic' }, + 'FM_ACNP_RETRIEVE_VIA_PCRUD' : { 'app' : 'open-ils.pcrud', 'method' : 'open-ils.pcrud.search.acnp.atomic' }, + 'FM_ACNS_RETRIEVE_VIA_PCRUD' : { 'app' : 'open-ils.pcrud', 'method' : 'open-ils.pcrud.search.acns.atomic' }, 'FM_ACP_DETAILS' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.copy_details.retrieve' }, 'FM_ACP_DETAILS_VIA_BARCODE' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.copy_details.retrieve.barcode' }, 'FM_ACP_DETAILS_VIA_BARCODE.authoritative' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.copy_details.retrieve.barcode.authoritative' }, @@ -471,7 +474,9 @@ var urls = { 'XUL_USER_BUCKETS' : '/xul/server/patron/user_buckets.xul', 'XUL_VERIFY_CREDENTIALS' : '/xul/server/main/verify_credentials.xul', 'XUL_VOLUME_BUCKETS' : '/xul/server/cat/volume_buckets.xul', - 'XUL_VOLUME_COPY_CREATOR' : '/xul/server/cat/volume_copy_creator.xul', + 'XUL_VOLUME_COPY_CREATOR' : '/xul/server/cat/volume_copy_editor.xul', + 'XUL_VOLUME_COPY_CREATOR_HORIZONTAL' : '/xul/server/cat/volume_copy_editor_horiz.xul', + 'XUL_VOLUME_COPY_CREATOR_ORIGINAL' : '/xul/server/cat/volume_copy_creator.xul', 'XUL_VOLUME_EDITOR' : '/xul/server/cat/volume_editor.xul', 'XUL_WORK_LOG' : '/xul/server/admin/work_log.xul', 'XUL_WORKSTATION_INFO' : '/xul/server/main/ws_info.xul', @@ -479,6 +484,7 @@ var urls = { 'TEST_HTML' : '/xul/server/main/test.html', 'TEST_XUL' : '/xul/server/main/test.xul', 'CONIFY' : '/conify/' + LOCALE + '/global', + 'CONIFY_MANAGE_PARTS' : '/eg/conify/global/biblio/monograph_part', 'EG_WEB_BASE' : '/eg', 'XUL_LOCAL_ADMIN_BASE' : '/xul/server/admin', 'XUL_REPORTS' : '/reports/oils_rpt.xhtml', diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu.js b/Open-ILS/xul/staff_client/chrome/content/main/menu.js index 28c83afacc..9a219a8f03 100644 --- a/Open-ILS/xul/staff_client/chrome/content/main/menu.js +++ b/Open-ILS/xul/staff_client/chrome/content/main/menu.js @@ -732,9 +732,13 @@ main.menu.prototype = { ['oncommand'], function() { open_eg_web_page('conify/global/config/record_attr_definition'); } ], - 'cmd_server_admin_coded_value_map' : [ + 'cmd_server_admin_acn_prefix' : [ ['oncommand'], - function() { open_eg_web_page('conify/global/config/coded_value_map'); } + function() { open_eg_web_page('conify/global/config/acn_prefix'); } + ], + 'cmd_server_admin_acn_suffix' : [ + ['oncommand'], + function() { open_eg_web_page('conify/global/config/acn_suffix'); } ], 'cmd_server_admin_billing_type' : [ ['oncommand'], @@ -1588,8 +1592,10 @@ main.menu.prototype = { }, 'volume_item_creator' : function(params) { var obj = this; + var horizontal_interface = String( obj.data.hash.aous['ui.cat.volume_copy_editor.horizontal'] ) == 'true'; + var url = obj.url_prefix( horizontal_interface ? urls.XUL_VOLUME_COPY_CREATOR_HORIZONTAL : urls.XUL_VOLUME_COPY_CREATOR ); var w = obj.new_tab( - obj.url_prefix(urls.XUL_VOLUME_COPY_CREATOR), + url, { 'tab_name' : document.getElementById('offlineStrings').getString('staff.cat.create_or_rebarcode_items') }, params ); diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul index 0e71b6efb2..40a23ff7d5 100644 --- a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul +++ b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul @@ -141,6 +141,8 @@ + + @@ -392,6 +394,8 @@ + + diff --git a/Open-ILS/xul/staff_client/chrome/content/util/browser.js b/Open-ILS/xul/staff_client/chrome/content/util/browser.js index 41ff0d2c6a..63b00bec39 100644 --- a/Open-ILS/xul/staff_client/chrome/content/util/browser.js +++ b/Open-ILS/xul/staff_client/chrome/content/util/browser.js @@ -14,6 +14,9 @@ util.browser.prototype = { 'lock_reload' : false, // as opposed to lock 'n load :) + 'back_button_clicked' : false, + 'from_back' : false, + 'init' : function( params ) { try { @@ -115,7 +118,10 @@ util.browser.prototype = { try { netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); var n = obj.getWebNavigation(); - if (n.canGoBack) n.goBack(); + if (n.canGoBack) { + obj.back_button_clicked = true; + n.goBack(); + } } catch(E) { var err = 'cmd_back: ' + E; obj.error.sdump('D_ERROR',err); @@ -265,6 +271,7 @@ util.browser.prototype = { cw.IAMXUL = true; cw.XUL_BUILD_ID = '/xul/server/'.split(/\//)[2]; cw.xulG = obj.passthru_content_params || {}; + cw.xulG.fromBack = obj.from_back; if (!cw.xulG.set_tab) { cw.xulG.set_tab = function(a,b,c) { return window.xulG.set_tab(a,b,c); }; } if (!cw.xulG.new_tab) { cw.xulG.new_tab = function(a,b,c) { return window.xulG.new_tab(a,b,c); }; } if (!cw.xulG.close_tab) { cw.xulG.close_tab = function(a) { return window.xulG.close_tab(a); }; } @@ -412,6 +419,7 @@ util.browser.prototype = { if (stateFlags & nsIWebProgressListener.STATE_IS_DOCUMENT) { s += ('\tSTATE_IS_DOCUMENT\n'); if( stateFlags & nsIWebProgressListener.STATE_STOP ) { + var alert_string = 'document has stopped: ' + new Date() + '\n'; dump(alert_string); obj.push_variables(); obj.updateNavButtons(); if (typeof obj.on_url_load == 'function') { try { @@ -448,6 +456,8 @@ util.browser.prototype = { } if (stateFlags & nsIWebProgressListener.STATE_START) { s += ('\tSTATE_START\n'); + obj.from_back = obj.back_button_clicked; + obj.back_button_clicked = false; } if (stateFlags & nsIWebProgressListener.STATE_REDIRECTING) { s += ('\tSTATE_REDIRECTING\n'); diff --git a/Open-ILS/xul/staff_client/chrome/content/util/list.js b/Open-ILS/xul/staff_client/chrome/content/util/list.js index dc1570a841..fb46564b2c 100644 --- a/Open-ILS/xul/staff_client/chrome/content/util/list.js +++ b/Open-ILS/xul/staff_client/chrome/content/util/list.js @@ -30,6 +30,7 @@ util.list.prototype = { 'init' : function (params) { var obj = this; + obj.scratch_data = {}; JSAN.use('util.widgets'); @@ -1016,11 +1017,11 @@ util.list.prototype = { if (typeof params.map_row_to_column == 'function') { - label = params.map_row_to_column(params.row,this.columns[i]); + label = params.map_row_to_column(params.row,this.columns[i],this.scratch_data); } else if (typeof this.map_row_to_column == 'function') { - label = this.map_row_to_column(params.row,this.columns[i]); + label = this.map_row_to_column(params.row,this.columns[i],this.scratch_data); } if (this.columns[i].type == 'checkbox') { treecell.setAttribute('value',label); } else { treecell.setAttribute('label',label ? label : ''); } @@ -1032,11 +1033,11 @@ util.list.prototype = { if (typeof params.map_row_to_columns == 'function') { - labels = params.map_row_to_columns(params.row,this.columns); + labels = params.map_row_to_columns(params.row,this.columns,this.scratch_data); } else if (typeof this.map_row_to_columns == 'function') { - labels = this.map_row_to_columns(params.row,this.columns); + labels = this.map_row_to_columns(params.row,this.columns,this.scratch_data); } for (var i = 0; i < labels.length; i++) { @@ -1070,13 +1071,13 @@ util.list.prototype = { var value = ''; if (typeof params.map_row_to_column == 'function') { - value = params.map_row_to_column(params.row,this.columns[i]); + value = params.map_row_to_column(params.row,this.columns[i],this.scratch_data); } else { if (typeof this.map_row_to_column == 'function') { - value = this.map_row_to_column(params.row,this.columns[i]); + value = this.map_row_to_column(params.row,this.columns[i],this.scratch_data); } } if (typeof value == 'string' || typeof value == 'number') { @@ -1817,9 +1818,11 @@ util.list.prototype = { }, // Default for the map_row_to_columns function for .init 'std_map_row_to_columns' : function(error_value) { - return function(row,cols) { + return function(row,cols,scratch) { // row contains { 'my' : { 'acp' : {}, 'circ' : {}, 'mvr' : {} } } // cols contains all of the objects listed above in columns + // scratch is a temporary space shared by all cells/rows (or just per row if not explicitly passed in) + if (!scratch) { scratch = {}; } var obj = {}; JSAN.use('util.error'); obj.error = new util.error(); @@ -1833,7 +1836,7 @@ util.list.prototype = { try { for (var i = 0; i < cols.length; i++) { switch (typeof cols[i].render) { - case 'function': try { values[i] = cols[i].render(my); } catch(E) { values[i] = error_value; obj.error.sdump('D_COLUMN_RENDER_ERROR',E); } break; + case 'function': try { values[i] = cols[i].render(my,scratch); } catch(E) { values[i] = error_value; obj.error.sdump('D_COLUMN_RENDER_ERROR',E); } break; case 'string' : cmd += 'try { ' + cols[i].render + '; values['+i+'] = v; } catch(E) { values['+i+'] = error_value; }'; break; default: cmd += 'values['+i+'] = "??? '+(typeof cols[i].render)+'"; '; } diff --git a/Open-ILS/xul/staff_client/chrome/content/util/widgets.js b/Open-ILS/xul/staff_client/chrome/content/util/widgets.js index 358519a09a..2bad0b009f 100644 --- a/Open-ILS/xul/staff_client/chrome/content/util/widgets.js +++ b/Open-ILS/xul/staff_client/chrome/content/util/widgets.js @@ -134,7 +134,9 @@ util.widgets.make_menulist = function( items, dvalue ) { menuitem.setAttribute('disabled','true'); } } - menulist.setAttribute('value',dvalue); + if (typeof dvalue != 'undefined') { + menulist.setAttribute('value',dvalue); + } return menulist; } @@ -209,7 +211,7 @@ util.widgets.insertAfter = function(parent_node,new_node,sibling_node) { } } -util.widgets.apply_vertical_tab_on_enter_handler = function(node,onfailure) { +util.widgets.apply_vertical_tab_on_enter_handler = function(node,onfailure,no_enter_func) { try { node.addEventListener( 'keypress', @@ -224,9 +226,14 @@ util.widgets.apply_vertical_tab_on_enter_handler = function(node,onfailure) { ev.preventDefault(); ev.stopPropagation(); return true; } else { + dump('keypress: attempting onfailure\n'); if (typeof onfailure == 'function') return onfailure(ev); return false; } + } else { + if (typeof no_enter_func == 'function') { + no_enter_func(ev); + } } }, false diff --git a/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties b/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties index 6a125de544..7de93f97a5 100644 --- a/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties +++ b/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties @@ -256,6 +256,7 @@ staff.cat.util.copy_editor.edit=Edit staff.cat.util.copy_editor.view=View staff.circ.copy_status.add_volumes.perm_failure=You do not have permission to add volumes to the workstation library. staff.circ.copy_status.add_volumes.title=Add Volume/Item for Record # %1$s +staff.cat.manage_parts.title=Manage Parts for Record # %1$s staff.cat.z3950.marked_record_for_overlay_indicator.tcn.label=Record with TCN %1$s marked for overlay. staff.cat.z3950.marked_record_for_overlay_indicator.record_id.label=Record with ID %1$s marked for overlay. staff.cat.opac.marked_record_for_hold_transfer_indicator.tcn.label=Record with TCN %1$s marked for title hold transfer. diff --git a/Open-ILS/xul/staff_client/server/cat/bib_brief.js b/Open-ILS/xul/staff_client/server/cat/bib_brief.js index b4e283e58c..db87e5b484 100644 --- a/Open-ILS/xul/staff_client/server/cat/bib_brief.js +++ b/Open-ILS/xul/staff_client/server/cat/bib_brief.js @@ -9,13 +9,13 @@ function my_init() { JSAN.use('util.error'); g.error = new util.error(); g.error.sdump('D_TRACE','my_init() for cat_bib_brief.xul'); - JSAN.use('OpenILS.data'); var data = new OpenILS.data(); data.init({'via':'stash'}); + JSAN.use('OpenILS.data'); g.data = new OpenILS.data(); g.data.init({'via':'stash'}); docid = xul_param('docid'); var key = location.pathname + location.search + location.hash; - if (!docid && typeof data.modal_xulG_stack != 'undefined' && typeof data.modal_xulG_stack[key] != 'undefined') { - var xulG = data.modal_xulG_stack[key][ data.modal_xulG_stack[key].length - 1 ]; + if (!docid && typeof g.data.modal_xulG_stack != 'undefined' && typeof g.data.modal_xulG_stack[key] != 'undefined') { + var xulG = g.data.modal_xulG_stack[key][ g.data.modal_xulG_stack[key].length - 1 ]; if (typeof xulG == 'object') { docid = xulG.docid; } @@ -28,7 +28,7 @@ function my_init() { if (docid > -1) { - data.last_record = docid; data.stash('last_record'); + g.data.last_record = docid; g.data.stash('last_record'); g.network.simple_request( 'MODS_SLIM_RECORD_RETRIEVE.authoritative', @@ -87,6 +87,14 @@ function my_init() { } } +function unhide_add_volumes_button() { + if (xulG && typeof xulG == 'object' && typeof xulG['new_tab'] == 'function') { + document.getElementById('add_volumes').hidden = false; + document.getElementById('add_volumes_left_paren').hidden = false; + document.getElementById('add_volumes_right_paren').hidden = false; + } +} + function view_marc() { try { JSAN.use('util.window'); var win = new util.window(); @@ -114,4 +122,39 @@ function spawn_patron(span) { } } +function add_volumes() { + try { + var edit = 0; + try { + edit = g.network.request( + api.PERM_MULTI_ORG_CHECK.app, + api.PERM_MULTI_ORG_CHECK.method, + [ + ses(), + ses('staff_id'), + [ ses('ws_ou') ], + [ 'CREATE_VOLUME', 'CREATE_COPY' ] + ] + ).length == 0 ? 1 : 0; + } catch(E) { + g.error.sdump('D_ERROR','batch permission check: ' + E); + } + if (edit==0) { + alert(document.getElementById('offlineStrings').getString('staff.circ.copy_status.add_volumes.perm_failure')); + return; // no read-only view for this interface + } + + var title = document.getElementById('offlineStrings').getFormattedString('staff.circ.copy_status.add_volumes.title', [docid]); + + var horizontal_interface = String( g.data.hash.aous['ui.cat.volume_copy_editor.horizontal'] ) == 'true'; + var url = window.xulG.url_prefix( horizontal_interface ? urls.XUL_VOLUME_COPY_CREATOR_HORIZONTAL : urls.XUL_VOLUME_COPY_CREATOR ); + var w = xulG.new_tab( + url, + { 'tab_name' : title }, + { 'doc_id' : docid, 'ou_ids' : [ ses('ws_ou') ] } + ); + } catch(E) { + alert('Error in server/cat/bib_brief.js, add_volumes(): ' + E); + } +} diff --git a/Open-ILS/xul/staff_client/server/cat/bib_brief.xul b/Open-ILS/xul/staff_client/server/cat/bib_brief.xul index 21c36c8cbb..bce81b73ae 100644 --- a/Open-ILS/xul/staff_client/server/cat/bib_brief.xul +++ b/Open-ILS/xul/staff_client/server/cat/bib_brief.xul @@ -22,7 +22,7 @@ vim: noet:sw=4:ts=4: @@ -39,7 +39,13 @@ vim: noet:sw=4:ts=4: - diff --git a/Open-ILS/xul/staff_client/server/cat/copy_browser.js b/Open-ILS/xul/staff_client/server/cat/copy_browser.js index e8d1644968..e962dfd19a 100644 --- a/Open-ILS/xul/staff_client/server/cat/copy_browser.js +++ b/Open-ILS/xul/staff_client/server/cat/copy_browser.js @@ -288,14 +288,16 @@ cat.copy_browser.prototype = { var title = document.getElementById('catStrings').getString('staff.cat.copy_browser.add_item.title'); + var horizontal_interface = String( obj.data.hash.aous['ui.cat.volume_copy_editor.horizontal'] ) == 'true'; + var url = window.xulG.url_prefix( horizontal_interface ? urls.XUL_VOLUME_COPY_CREATOR_HORIZONTAL : urls.XUL_VOLUME_COPY_CREATOR ); var w = xulG.new_tab( - window.xulG.url_prefix(urls.XUL_VOLUME_COPY_CREATOR), + url, { 'tab_name' : title }, { 'doc_id' : obj.docid, 'ou_ids' : list, 'copy_shortcut' : copy_shortcut, - 'refresh' : function() { obj.refresh_list(); } + 'onrefresh' : function() { obj.refresh_list(); } } ); } catch(E) { @@ -331,7 +333,7 @@ cat.copy_browser.prototype = { } } ], - 'cmd_replace_barcode' : [ + 'cmd_edit_items' : [ ['command'], function() { try { @@ -356,12 +358,12 @@ cat.copy_browser.prototype = { xulG.volume_item_creator( {'existing_copies':list, 'onrefresh' : function() { obj.refresh_list(); } } ); } catch(E) { - obj.error.standard_unexpected_error_alert(document.getElementById('catStrings').getString('staff.cat.copy_browser.replace_barcode.error'),E); + obj.error.standard_unexpected_error_alert(document.getElementById('catStrings').getString('staff.cat.copy_browser.edit_items.error'),E); obj.refresh_list(); } } ], - 'cmd_edit_items' : [ + 'old_cmd_edit_items' : [ ['command'], function() { try { @@ -536,10 +538,12 @@ cat.copy_browser.prototype = { var title = document.getElementById('catStrings').getString('staff.cat.copy_browser.add_volume.title'); + var horizontal_interface = String( obj.data.hash.aous['ui.cat.volume_copy_editor.horizontal'] ) == 'true'; + var url = window.xulG.url_prefix( horizontal_interface ? urls.XUL_VOLUME_COPY_CREATOR_HORIZONTAL : urls.XUL_VOLUME_COPY_CREATOR ); var w = xulG.new_tab( - window.xulG.url_prefix(urls.XUL_VOLUME_COPY_CREATOR), + url, { 'tab_name' : title }, - { 'doc_id' : obj.docid, 'ou_ids' : list, 'refresh' : function() { obj.refresh_list(); } } + { 'doc_id' : obj.docid, 'ou_ids' : list, 'onrefresh' : function() { obj.refresh_list(); } } ); } catch(E) { @@ -639,7 +643,6 @@ cat.copy_browser.prototype = { } if (robj.ilsevent != 0) throw(robj); } - alert(document.getElementById('catStrings').getString('staff.cat.copy_browser.delete_volume.success')); obj.refresh_list(); } } catch(E) { @@ -1448,6 +1451,7 @@ cat.copy_browser.prototype = { var data = { 'row' : { 'my' : { + 'doc_id' : obj.docid, 'aou' : obj.data.hash.aou[ acn_tree.owning_lib() ], 'acn' : acn_tree, 'acp' : acp_item, @@ -1517,6 +1521,7 @@ cat.copy_browser.prototype = { 'circ_lib' : { 'hidden' : false }, 'owning_lib' : { 'hidden' : false }, 'call_number' : { 'hidden' : false }, + 'parts' : { 'hidden' : false }, 'due_date' : { 'hidden' : false }, 'acp_status' : { 'hidden' : false }, }, @@ -1525,8 +1530,12 @@ cat.copy_browser.prototype = { 'due_date', 'owning_lib', 'circ_lib', + 'label_class', + 'prefix', 'call_number', + 'suffix', 'copy_number', + 'parts', 'location', 'barcode', 'loan_duration', @@ -1668,7 +1677,6 @@ cat.copy_browser.prototype = { obj.controller.view.cmd_add_items.setAttribute('disabled','true'); obj.controller.view.cmd_add_items_to_buckets.setAttribute('disabled','true'); obj.controller.view.cmd_edit_items.setAttribute('disabled','true'); - obj.controller.view.cmd_replace_barcode.setAttribute('disabled','true'); obj.controller.view.cmd_delete_items.setAttribute('disabled','true'); obj.controller.view.cmd_print_spine_labels.setAttribute('disabled','true'); obj.controller.view.cmd_add_volumes.setAttribute('disabled','true'); @@ -1700,7 +1708,6 @@ cat.copy_browser.prototype = { obj.controller.view.sel_mark_items_missing.setAttribute('disabled','false'); obj.controller.view.cmd_add_items_to_buckets.setAttribute('disabled','false'); obj.controller.view.cmd_edit_items.setAttribute('disabled','false'); - obj.controller.view.cmd_replace_barcode.setAttribute('disabled','false'); obj.controller.view.cmd_delete_items.setAttribute('disabled','false'); obj.controller.view.cmd_print_spine_labels.setAttribute('disabled','false'); obj.controller.view.cmd_transfer_items.setAttribute('disabled','false'); diff --git a/Open-ILS/xul/staff_client/server/cat/copy_browser.xul b/Open-ILS/xul/staff_client/server/cat/copy_browser.xul index 242804317f..319ae35480 100644 --- a/Open-ILS/xul/staff_client/server/cat/copy_browser.xul +++ b/Open-ILS/xul/staff_client/server/cat/copy_browser.xul @@ -86,7 +86,6 @@ vim:noet:sw=4:ts=4: - @@ -128,7 +127,6 @@ vim:noet:sw=4:ts=4: - @@ -142,8 +140,6 @@ vim:noet:sw=4:ts=4: