From 054f070cad6c29dad473bb84d1b47088afd12b65 Mon Sep 17 00:00:00 2001 From: miker Date: Tue, 12 Apr 2011 19:51:54 +0000 Subject: [PATCH] Add support for Multi-Homed Items (aka Foreign Bibs, aka Linked Items) Evergreen needs to support the ability to attach a barcoded item to more than one bibliographic record. Use cases include: 1. Barcoded E-Readers with preloaded content * Readers would all be items attached to a single "master" bib record in the traditional way, through call numbers that define their ownership * Each reader, as a barcoded item, can be attached through Multi-homed Items to records describing the list of preloaded content * These attached Multi-homed Items can be added and removed as content is swapped out on each reader 2. Dual-language items * Cataloger decides which of several alternate languages is the primary, and attaches the barcoded item to that record in the traditional way * Alternate language records are attached to this item through Multi-homed Items 3. "Back-to-back" books -- two books printed upside down relative to one another, with two "front" covers * Cataloger decides which of the two titles is the primary, and attaches the barcoded item to that record in the traditional way * Alternate title record is attached to this item through Multi-homed Items 4. Bound Volumes -- Sets of individual works collected into a single barcoded package * Cataloger decides which of the titles is the primary (or creates a record for the collection as a whole), and attaches the barcoded item to that record in the traditional way * Remaining title records for the collected peices are attached to this item through Multi-homed Items Functionality funded by Natural Resources Canada -- http://www.nrcan-rncan.gc.ca/com/ Please see http://git.esilibrary.com/?p=evergreen-equinox.git;a=shortlog;h=refs/heads/multi_home for the full commit history. git-svn-id: svn://svn.open-ils.org/ILS/trunk@20056 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- Open-ILS/examples/fm_IDL.xml | 49 + .../perlmods/lib/OpenILS/Application/Circ.pm | 2 +- .../lib/OpenILS/Application/Search/Biblio.pm | 112 ++- .../Application/Storage/CDBI/biblio.pm | 16 + .../Application/Storage/Driver/Pg/dbi.pm | 12 + Open-ILS/src/sql/Pg/002.schema.config.sql | 2 +- Open-ILS/src/sql/Pg/010.schema.biblio.sql | 14 + Open-ILS/src/sql/Pg/030.schema.metabib.sql | 1 + Open-ILS/src/sql/Pg/040.schema.asset.sql | 5 +- .../src/sql/Pg/300.schema.staged_search.sql | 74 +- Open-ILS/src/sql/Pg/950.data.seed-values.sql | 8 + Open-ILS/src/sql/Pg/990.schema.unapi.sql | 19 + Open-ILS/src/sql/Pg/999.functions.global.sql | 60 +- .../upgrade/0512.schema.multi-home-items.sql | 876 ++++++++++++++++++ Open-ILS/web/opac/common/js/config.js | 4 +- Open-ILS/web/opac/locale/en-US/lang.dtd | 21 + Open-ILS/web/opac/locale/en-US/opac.dtd | 2 + Open-ILS/web/opac/skin/default/js/advanced.js | 18 +- .../web/opac/skin/default/js/copy_details.js | 103 +- Open-ILS/web/opac/skin/default/js/rdetail.js | 82 ++ .../xml/rdetail/rdetail_cn_details.xml | 2 + .../default/xml/rdetail/rdetail_extras.xml | 16 + .../chrome/content/OpenILS/data.js | 21 + .../chrome/content/OpenILS/global_util.js | 30 + .../staff_client/chrome/content/cat/opac.js | 133 ++- .../staff_client/chrome/content/cat/opac.xul | 10 +- .../chrome/content/main/constants.js | 2 + .../staff_client/chrome/content/main/menu.js | 21 +- .../staff_client/chrome/content/util/exec.js | 12 +- .../chrome/content/util/widget_prompt.js | 78 ++ .../chrome/content/util/widget_prompt.xul | 49 + .../chrome/locale/en-US/offline.properties | 10 +- .../staff_client/server/cat/copy_browser.js | 39 + .../staff_client/server/cat/copy_browser.xul | 3 + .../server/cat/manage_multi_home_items.js | 477 ++++++++++ .../server/cat/manage_multi_home_items.xul | 92 ++ .../server/locale/en-US/cat.properties | 14 + 37 files changed, 2404 insertions(+), 85 deletions(-) create mode 100644 Open-ILS/src/sql/Pg/upgrade/0512.schema.multi-home-items.sql create mode 100644 Open-ILS/xul/staff_client/chrome/content/util/widget_prompt.js create mode 100644 Open-ILS/xul/staff_client/chrome/content/util/widget_prompt.xul create mode 100644 Open-ILS/xul/staff_client/server/cat/manage_multi_home_items.js create mode 100644 Open-ILS/xul/staff_client/server/cat/manage_multi_home_items.xul diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 04b85049fc..11faf841fa 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -84,6 +84,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + @@ -1045,6 +1046,50 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4770,6 +4815,8 @@ SELECT usr, + + @@ -4787,6 +4834,8 @@ SELECT usr, + + diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm index 88af4f55d8..cd3294e0d8 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm @@ -973,7 +973,7 @@ sub copy_details { { flesh => 2, flesh_fields => { - acp => ['call_number','parts'], + acp => ['call_number','parts','peer_record_maps'], acn => ['record','prefix','suffix','label_class'] } } 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 631a9be55a..5cd7087fc9 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Biblio.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Biblio.pm @@ -494,7 +494,7 @@ sub fleshed_copy_retrieve2 { flesh => 2, flesh_fields => { acp => [ - qw/ location status stat_cat_entry_copy_maps notes age_protect parts / + qw/ location status stat_cat_entry_copy_maps notes age_protect parts peer_record_maps / ], ascecm => [qw/ stat_cat stat_cat_entry /], } @@ -568,16 +568,32 @@ __PACKAGE__->register_method( api_name => 'open-ils.search.bib_id.by_barcode', authoritative => 1, signature => { - desc => 'Retrieve copy object with fleshed record, given the barcode', + desc => 'Retrieve bib record id associated with the copy identified by the given barcode', params => [ { desc => 'Item barcode', type => 'string' } ], return => { - desc => 'Asset copy object with fleshed record and callnumber, or event on error or null set' + desc => 'Bib record id.' } } ); +__PACKAGE__->register_method( + method => 'title_id_by_item_barcode', + api_name => 'open-ils.search.multi_home.bib_ids.by_barcode', + authoritative => 1, + signature => { + desc => 'Retrieve bib record ids associated with the copy identified by the given barcode. This includes peer bibs for Multi-Home items.', + params => [ + { desc => 'Item barcode', type => 'string' } + ], + return => { + desc => 'Array of bib record ids. First element is the native bib for the item.' + } + } +); + + sub title_id_by_item_barcode { my( $self, $conn, $barcode ) = @_; my $e = new_editor(); @@ -595,9 +611,97 @@ sub title_id_by_item_barcode { ); return $e->event unless @$copies; - return $$copies[0]->call_number->record->id; + + if( $self->api_name =~ /multi_home/ ) { + my $multi_home_list = $e->search_biblio_peer_bib_copy_map( + [ + { target_copy => $$copies[0]->id } + ] + ); + my @temp = map { $_->peer_record } @{ $multi_home_list }; + unshift @temp, $$copies[0]->call_number->record->id; + return \@temp; + } else { + return $$copies[0]->call_number->record->id; + } } +__PACKAGE__->register_method( + method => 'find_peer_bibs', + api_name => 'open-ils.search.peer_bibs.test', + authoritative => 1, + signature => { + desc => 'Tests to see if the specified record is a peer record.', + params => [ + { desc => 'Biblio record entry Id', type => 'number' } + ], + return => { + desc => 'True if specified id can be found in biblio.peer_record_copy_map.peer_record.', + type => 'bool' + } + } +); + +__PACKAGE__->register_method( + method => 'find_peer_bibs', + api_name => 'open-ils.search.peer_bibs', + authoritative => 1, + signature => { + desc => 'Return acps and mvrs for multi-home items linked to specified peer record.', + params => [ + { desc => 'Biblio record entry Id', type => 'number' } + ], + return => { + desc => '{ records => Array of mvrs, items => array of acps }', + } + } +); + + +sub find_peer_bibs { + my( $self, $client, $doc_id ) = @_; + my $e = new_editor(); + + my $multi_home_list = $e->search_biblio_peer_bib_copy_map( + [ + { peer_record => $doc_id }, + { + flesh => 2, + flesh_fields => { + bpbcm => [ 'target_copy', 'peer_type' ], + acp => [ 'call_number', 'location', 'status', 'peer_record_maps' ] + } + } + ] + ); + + if ($self->api_name =~ /test/) { + return scalar( @{$multi_home_list} ) > 0 ? 1 : 0; + } + + if (scalar(@{$multi_home_list})==0) { + return []; + } + + # create a unique hash of the primary record MVRs for foreign copies + # XXX PLEASE let's change to unAPI2 (supports foreign copies) in the TT opac?!? + my %rec_hash = map { + ($_->target_copy->call_number->record, _records_to_mods( $_->target_copy->call_number->record )->[0]) + } @$multi_home_list; + + # set the foreign_copy_maps field to an empty array + map { $rec_hash{$_}->foreign_copy_maps([]) } keys( %rec_hash ); + + # push the maps onto the correct MVRs + for (@$multi_home_list) { + push( + @{$rec_hash{ $_->target_copy->call_number->record }->foreign_copy_maps()}, + $_ + ); + } + + return [sort {$a->title cmp $b->title} values(%rec_hash)]; +}; __PACKAGE__->register_method( method => "biblio_copy_to_mods", 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 3df2ee5aca..7780cbcb6a 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,6 +22,22 @@ biblio::record_note->columns( Essential => qw/id record value creator editor create_date edit_date pub/ ); #------------------------------------------------------------------------------- +#------------------------------------------------------------------------------- +package biblio::peer_type; +use base qw/biblio/; + +biblio::peer_type->table( 'biblio_peer_type' ); +biblio::peer_type->columns( Essential => qw/id name/ ); +#------------------------------------------------------------------------------- + +#------------------------------------------------------------------------------- +package biblio::peer_record_copy_map; +use base qw/biblio/; + +biblio::peer_record_copy_map->table( 'biblio_peer_record_copy_map' ); +biblio::peer_record_copy_map->columns( Essential => qw/id peer_type peer_record target_copy/ ); +#------------------------------------------------------------------------------- + #------------------------------------------------------------------------------- package biblio::monograph_part; use base qw/biblio/; 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 623be5fbeb..5c6990ff22 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 @@ -11,6 +11,18 @@ biblio::monograph_part->table( 'biblio.monograph_part' ); biblio::monograph_part->sequence( 'biblio.monograph_part_id_seq' ); + #------------------------------------------------------------------------------- + package biblio::peer_record_copy_map; + + biblio::peer_record_copy_map->table( 'biblio.peer_record_copy_map' ); + biblio::peer_record_copy_map->sequence( 'biblio.peer_record_copy_map_id_seq' ); + + #------------------------------------------------------------------------------- + package biblio::peer_type; + + biblio::peer_type->table( 'biblio.peer_type' ); + biblio::peer_type->sequence( 'biblio.peer_type_id_seq' ); + #------------------------------------------------------------------------------- package container::user_bucket; diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql index 43390dab12..55bff9fa74 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 ('0511'); -- miker +INSERT INTO config.upgrade_log (version) VALUES ('0512'); -- miker 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 036b7b6371..4ce54ae96b 100644 --- a/Open-ILS/src/sql/Pg/010.schema.biblio.sql +++ b/Open-ILS/src/sql/Pg/010.schema.biblio.sql @@ -79,6 +79,20 @@ 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.peer_type ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL UNIQUE -- i18n +); + +CREATE TABLE biblio.peer_bib_copy_map ( + id SERIAL PRIMARY KEY, + peer_type INT NOT NULL REFERENCES biblio.peer_type (id), + peer_record BIGINT NOT NULL REFERENCES biblio.record_entry (id), + target_copy BIGINT NOT NULL -- can't use fkey because of acp subtables +); +CREATE INDEX peer_bib_copy_map_record_idx ON biblio.peer_bib_copy_map (peer_record); +CREATE INDEX peer_bib_copy_map_copy_idx ON biblio.peer_bib_copy_map (target_copy); + CREATE TABLE biblio.monograph_part ( id SERIAL PRIMARY KEY, record BIGINT NOT NULL REFERENCES biblio.record_entry (id), diff --git a/Open-ILS/src/sql/Pg/030.schema.metabib.sql b/Open-ILS/src/sql/Pg/030.schema.metabib.sql index 8530aaf506..ad41652f16 100644 --- a/Open-ILS/src/sql/Pg/030.schema.metabib.sql +++ b/Open-ILS/src/sql/Pg/030.schema.metabib.sql @@ -950,6 +950,7 @@ BEGIN DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage DELETE FROM metabib.record_attr WHERE id = NEW.id; -- Kill the attrs hash, useless on deleted records DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible + DELETE FROM biblio.peer_bib_copy_map WHERE peer_record = NEW.id; -- Separate any multi-homed items RETURN NEW; -- and we're done END IF; diff --git a/Open-ILS/src/sql/Pg/040.schema.asset.sql b/Open-ILS/src/sql/Pg/040.schema.asset.sql index 73a55c4372..6d1e41f6bf 100644 --- a/Open-ILS/src/sql/Pg/040.schema.asset.sql +++ b/Open-ILS/src/sql/Pg/040.schema.asset.sql @@ -99,7 +99,8 @@ CREATE TABLE asset.copy_part_map ( 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 + id BIGSERIAL primary key, + copy_id BIGINT, -- copy id record BIGINT, circ_lib INTEGER ); @@ -109,6 +110,8 @@ search.query_parser_fts() to speed up OPAC visibility checks on large databases. Contents are maintained by a set of triggers. $$; CREATE INDEX opac_visible_copies_idx1 on asset.opac_visible_copies (record, circ_lib); +CREATE INDEX opac_visible_copies_copy_id_idx on asset.opac_visible_copies (copy_id); +CREATE UNIQUE INDEX opac_visible_copies_once_per_record_idx on asset.opac_visible_copies (copy_id, record); CREATE OR REPLACE FUNCTION asset.acp_status_changed() RETURNS TRIGGER AS $$ diff --git a/Open-ILS/src/sql/Pg/300.schema.staged_search.sql b/Open-ILS/src/sql/Pg/300.schema.staged_search.sql index 2ab2efd2dd..e13bb60075 100644 --- a/Open-ILS/src/sql/Pg/300.schema.staged_search.sql +++ b/Open-ILS/src/sql/Pg/300.schema.staged_search.sql @@ -190,9 +190,20 @@ BEGIN LIMIT 1; IF NOT FOUND THEN - -- RAISE NOTICE ' % were all status-excluded ... ', core_result.records; - excluded_count := excluded_count + 1; - CONTINUE; + PERFORM 1 + FROM biblio.peer_bib_copy_map pr + JOIN asset.copy cp ON (cp.id = pr.target_copy) + WHERE NOT cp.deleted + AND cp.status IN ( SELECT * FROM search.explode_array( param_statuses ) ) + AND pr.peer_record IN ( SELECT * FROM search.explode_array( core_result.records ) ) + AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) + LIMIT 1; + + IF NOT FOUND THEN + -- RAISE NOTICE ' % and multi-home linked records were all status-excluded ... ', core_result.records; + excluded_count := excluded_count + 1; + CONTINUE; + END IF; END IF; END IF; @@ -210,9 +221,20 @@ BEGIN LIMIT 1; IF NOT FOUND THEN - -- RAISE NOTICE ' % were all copy_location-excluded ... ', core_result.records; - excluded_count := excluded_count + 1; - CONTINUE; + PERFORM 1 + FROM biblio.peer_bib_copy_map pr + JOIN asset.copy cp ON (cp.id = pr.target_copy) + WHERE NOT cp.deleted + AND cp.location IN ( SELECT * FROM search.explode_array( param_locations ) ) + AND pr.peer_record IN ( SELECT * FROM search.explode_array( core_result.records ) ) + AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) + LIMIT 1; + + IF NOT FOUND THEN + -- RAISE NOTICE ' % and multi-home linked records were all copy_location-excluded ... ', core_result.records; + excluded_count := excluded_count + 1; + CONTINUE; + END IF; END IF; END IF; @@ -226,9 +248,19 @@ BEGIN LIMIT 1; IF NOT FOUND THEN - -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records; - excluded_count := excluded_count + 1; - CONTINUE; + PERFORM 1 + FROM biblio.peer_bib_copy_map pr + JOIN asset.opac_visible_copies cp ON (cp.copy_id = pr.target_copy) + WHERE cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) + AND pr.peer_record IN ( SELECT * FROM search.explode_array( core_result.records ) ) + LIMIT 1; + + IF NOT FOUND THEN + + -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records; + excluded_count := excluded_count + 1; + CONTINUE; + END IF; END IF; ELSE @@ -236,7 +268,6 @@ BEGIN PERFORM 1 FROM asset.call_number cn JOIN asset.copy cp ON (cp.call_number = cn.id) - JOIN actor.org_unit a ON (cp.circ_lib = a.id) WHERE NOT cn.deleted AND NOT cp.deleted AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) @@ -246,14 +277,25 @@ BEGIN IF NOT FOUND THEN PERFORM 1 - FROM asset.call_number cn - WHERE cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) + FROM biblio.peer_bib_copy_map pr + JOIN asset.copy cp ON (cp.id = pr.target_copy) + WHERE NOT cp.deleted + AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) + AND pr.peer_record IN ( SELECT * FROM search.explode_array( core_result.records ) ) LIMIT 1; - IF FOUND THEN - -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records; - excluded_count := excluded_count + 1; - CONTINUE; + IF NOT FOUND THEN + + PERFORM 1 + FROM asset.call_number cn + WHERE cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) + LIMIT 1; + + IF FOUND THEN + -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records; + excluded_count := excluded_count + 1; + CONTINUE; + END IF; END IF; END IF; 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 549d03474e..3734a160c1 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -7,6 +7,14 @@ INSERT INTO config.bib_source (id, quality, source, transcendant) VALUES (3, 1, oils_i18n_gettext(3, 'Project Gutenberg', 'cbs', 'source'), TRUE); SELECT SETVAL('config.bib_source_id_seq'::TEXT, 100); +INSERT INTO biblio.peer_type (id,name) VALUES + (1,oils_i18n_gettext(1,'Bound Volume','bpt','name')), + (2,oils_i18n_gettext(2,'Bilingual','bpt','name')), + (3,oils_i18n_gettext(3,'Back-to-back','bpt','name')), + (4,oils_i18n_gettext(4,'Set','bpt','name')), + (5,oils_i18n_gettext(5,'e-Reader Preload','bpt','name')); +SELECT SETVAL('biblio.peer_type_id_seq'::TEXT, 100); + INSERT INTO config.standing (id, value) VALUES (1, oils_i18n_gettext(1, 'Good', 'cst', 'value')); INSERT INTO config.standing (id, value) VALUES (2, oils_i18n_gettext(2, 'Barred', 'cst', 'value')); SELECT SETVAL('config.standing_id_seq'::TEXT, 100); diff --git a/Open-ILS/src/sql/Pg/990.schema.unapi.sql b/Open-ILS/src/sql/Pg/990.schema.unapi.sql index 46019f22fb..ae69074067 100644 --- a/Open-ILS/src/sql/Pg/990.schema.unapi.sql +++ b/Open-ILS/src/sql/Pg/990.schema.unapi.sql @@ -281,6 +281,17 @@ CREATE OR REPLACE FUNCTION unapi.holdings_xml (bid BIGINT, ouid INT, org TEXT, d WHERE record_entry = $1 )x) ) + ELSE NULL END, + CASE WHEN ('acp' = ANY ($5)) THEN + XMLELEMENT( + name foreign_copies, + (SELECT XMLAGG(acp) FROM ( + SELECT unapi.acp(p.target_copy,'xml','copy','{}'::TEXT[], $3, $4, $6, $7, FALSE) + FROM biblio.peer_bib_copy_map p + JOIN asset.copy c ON (p.target_copy = c.id) + WHERE NOT c.deleted AND peer_record = $1 + )x) + ) ELSE NULL END ); $F$ LANGUAGE SQL; @@ -616,6 +627,14 @@ CREATE OR REPLACE FUNCTION unapi.acp ( obj_id BIGINT, format TEXT, ename TEXT, ELSE NULL END ), + XMLELEMENT( name foreign_records, + CASE + WHEN ('bre' = ANY ($4)) THEN + XMLAGG((SELECT unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE) FROM biblio.peer_bib_copy_map WHERE target_copy = cp.id)) + ELSE NULL + END + + ), CASE WHEN ('bmp' = ANY ($4)) THEN XMLELEMENT( name monograph_parts, diff --git a/Open-ILS/src/sql/Pg/999.functions.global.sql b/Open-ILS/src/sql/Pg/999.functions.global.sql index d621300cf2..30cf0e8f34 100644 --- a/Open-ILS/src/sql/Pg/999.functions.global.sql +++ b/Open-ILS/src/sql/Pg/999.functions.global.sql @@ -1128,7 +1128,7 @@ CREATE OR REPLACE FUNCTION asset.refresh_opac_visible_copies_mat_view () RETURNS TRUNCATE TABLE asset.opac_visible_copies; - INSERT INTO asset.opac_visible_copies (id, circ_lib, record) + INSERT INTO asset.opac_visible_copies (copy_id, circ_lib, record) SELECT cp.id, cp.circ_lib, cn.record FROM asset.copy cp JOIN asset.call_number cn ON (cn.id = cp.call_number) @@ -1139,6 +1139,18 @@ CREATE OR REPLACE FUNCTION asset.refresh_opac_visible_copies_mat_view () RETURNS WHERE NOT cp.deleted AND NOT cn.deleted AND NOT b.deleted + AND cs.opac_visible + AND cl.opac_visible + AND cp.opac_visible + AND a.opac_visible + UNION + SELECT cp.id, cp.circ_lib, pbcm.peer_record AS record + FROM asset.copy cp + JOIN biblio.peer_bib_copy_map pbcm ON (pbcm.target_copy = cp.id) + JOIN actor.org_unit a ON (cp.circ_lib = a.id) + JOIN asset.copy_location cl ON (cp.location = cl.id) + JOIN config.copy_status cs ON (cp.status = cs.id) + WHERE NOT cp.deleted AND cs.opac_visible AND cl.opac_visible AND cp.opac_visible @@ -1157,8 +1169,9 @@ DECLARE do_remove BOOLEAN := false; BEGIN add_query := $$ - INSERT INTO asset.opac_visible_copies (id, circ_lib, record) - SELECT cp.id, cp.circ_lib, cn.record + INSERT INTO asset.opac_visible_copies (copy_id, circ_lib, record) + SELECT id, circ_lib, record FROM ( + SELECT cp.id, cp.circ_lib, cn.record, cn.id AS call_number FROM asset.copy cp JOIN asset.call_number cn ON (cn.id = cp.call_number) JOIN actor.org_unit a ON (cp.circ_lib = a.id) @@ -1172,14 +1185,40 @@ BEGIN AND cl.opac_visible AND cp.opac_visible AND a.opac_visible + UNION + SELECT cp.id, cp.circ_lib, pbcm.peer_record AS record, NULL AS call_number + FROM asset.copy cp + JOIN biblio.peer_bib_copy_map pbcm ON (pbcm.target_copy = cp.id) + JOIN actor.org_unit a ON (cp.circ_lib = a.id) + JOIN asset.copy_location cl ON (cp.location = cl.id) + JOIN config.copy_status cs ON (cp.status = cs.id) + WHERE NOT cp.deleted + AND cs.opac_visible + AND cl.opac_visible + AND cp.opac_visible + AND a.opac_visible + ) AS x + $$; - remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE id IN ( SELECT id FROM asset.copy WHERE $$; + remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE copy_id IN ( SELECT id FROM asset.copy WHERE $$; + + IF TG_TABLE_NAME = 'peer_bib_copy_map' THEN + IF TG_OP = 'INSERT' THEN + add_query := add_query || 'WHERE x.id = ' || NEW.target_copy || ' AND x.record = ' || NEW.peer_record || ';'; + EXECUTE add_query; + RETURN NEW; + ELSE + remove_query := 'DELETE FROM asset.opac_visible_copies WHERE copy_id = ' || OLD.target_copy || ' AND record = ' || OLD.peer_record || ';'; + EXECUTE remove_query; + RETURN OLD; + END IF; + END IF; IF TG_OP = 'INSERT' THEN IF TG_TABLE_NAME IN ('copy', 'unit') THEN - add_query := add_query || 'AND cp.id = ' || NEW.id || ';'; + add_query := add_query || 'WHERE x.id = ' || NEW.id || ';'; EXECUTE add_query; END IF; @@ -1225,7 +1264,7 @@ BEGIN DELETE FROM asset.opac_visible_copies WHERE id = NEW.id; END IF; IF do_add THEN - add_query := add_query || 'AND cp.id = ' || NEW.id || ';'; + add_query := add_query || 'WHERE x.id = ' || NEW.id || ';'; EXECUTE add_query; END IF; @@ -1252,11 +1291,11 @@ BEGIN ELSIF OLD.deleted THEN -- add rows IF TG_TABLE_NAME IN ('copy','unit') THEN - add_query := add_query || 'AND cp.id = ' || NEW.id || ';'; + add_query := add_query || 'WHERE x.id = ' || NEW.id || ';'; ELSIF TG_TABLE_NAME = 'call_number' THEN - add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';'; + add_query := add_query || 'WHERE x.call_number = ' || NEW.id || ';'; ELSIF TG_TABLE_NAME = 'record_entry' THEN - add_query := add_query || 'AND cn.record = ' || NEW.id || ';'; + add_query := add_query || 'WHERE x.record = ' || NEW.id || ';'; END IF; EXECUTE add_query; @@ -1272,7 +1311,7 @@ BEGIN -- call number is linked to different bib remove_query := remove_query || 'call_number = ' || NEW.id || ');'; EXECUTE remove_query; - add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';'; + add_query := add_query || 'WHERE x.call_number = ' || NEW.id || ';'; EXECUTE add_query; END IF; @@ -1321,6 +1360,7 @@ $func$ LANGUAGE PLPGSQL; COMMENT ON FUNCTION asset.cache_copy_visibility() IS $$ Trigger function to update the copy OPAC visiblity cache. $$; +CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR DELETE ON biblio.peer_bib_copy_map FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility(); CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility(); CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON asset.copy FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility(); CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON asset.call_number FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility(); diff --git a/Open-ILS/src/sql/Pg/upgrade/0512.schema.multi-home-items.sql b/Open-ILS/src/sql/Pg/upgrade/0512.schema.multi-home-items.sql new file mode 100644 index 0000000000..04816a74e2 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/0512.schema.multi-home-items.sql @@ -0,0 +1,876 @@ +BEGIN; + +INSERT INTO config.upgrade_log (version) VALUES ('0512'); -- miker + +CREATE TABLE biblio.peer_type ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL UNIQUE -- i18n +); + +CREATE TABLE biblio.peer_bib_copy_map ( + id SERIAL PRIMARY KEY, + peer_type INT NOT NULL REFERENCES biblio.peer_type (id), + peer_record BIGINT NOT NULL REFERENCES biblio.record_entry (id), + target_copy BIGINT NOT NULL -- can't use fkey because of acp subtables +); +CREATE INDEX peer_bib_copy_map_record_idx ON biblio.peer_bib_copy_map (peer_record); +CREATE INDEX peer_bib_copy_map_copy_idx ON biblio.peer_bib_copy_map (target_copy); + +DROP TABLE asset.opac_visible_copies; +CREATE TABLE asset.opac_visible_copies ( + id BIGSERIAL primary key, + copy_id BIGINT, + record BIGINT, + circ_lib INTEGER +); + +INSERT INTO biblio.peer_type (id,name) VALUES + (1,oils_i18n_gettext(1,'Bound Volume','bpt','name')), + (2,oils_i18n_gettext(2,'Bilingual','bpt','name')), + (3,oils_i18n_gettext(3,'Back-to-back','bpt','name')), + (4,oils_i18n_gettext(4,'Set','bpt','name')), + (5,oils_i18n_gettext(5,'e-Reader Preload','bpt','name')); + +SELECT SETVAL('biblio.peer_type_id_seq'::TEXT, 100); + +CREATE OR REPLACE FUNCTION search.query_parser_fts ( + + param_search_ou INT, + param_depth INT, + param_query TEXT, + param_statuses INT[], + param_locations INT[], + param_offset INT, + param_check INT, + param_limit INT, + metarecord BOOL, + staff BOOL + +) RETURNS SETOF search.search_result AS $func$ +DECLARE + + current_res search.search_result%ROWTYPE; + search_org_list INT[]; + + check_limit INT; + core_limit INT; + core_offset INT; + tmp_int INT; + + core_result RECORD; + core_cursor REFCURSOR; + core_rel_query TEXT; + + total_count INT := 0; + check_count INT := 0; + deleted_count INT := 0; + visible_count INT := 0; + excluded_count INT := 0; + +BEGIN + + check_limit := COALESCE( param_check, 1000 ); + core_limit := COALESCE( param_limit, 25000 ); + core_offset := COALESCE( param_offset, 0 ); + + -- core_skip_chk := COALESCE( param_skip_chk, 1 ); + + IF param_search_ou > 0 THEN + IF param_depth IS NOT NULL THEN + SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth ); + ELSE + SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou ); + END IF; + ELSIF param_search_ou < 0 THEN + SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou; + ELSIF param_search_ou = 0 THEN + -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure. + END IF; + + OPEN core_cursor FOR EXECUTE param_query; + + LOOP + + FETCH core_cursor INTO core_result; + EXIT WHEN NOT FOUND; + EXIT WHEN total_count >= core_limit; + + total_count := total_count + 1; + + CONTINUE WHEN total_count NOT BETWEEN core_offset + 1 AND check_limit + core_offset; + + check_count := check_count + 1; + + PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) ); + IF NOT FOUND THEN + -- RAISE NOTICE ' % were all deleted ... ', core_result.records; + deleted_count := deleted_count + 1; + CONTINUE; + END IF; + + PERFORM 1 + FROM biblio.record_entry b + JOIN config.bib_source s ON (b.source = s.id) + WHERE s.transcendant + AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) ); + + IF FOUND THEN + -- RAISE NOTICE ' % were all transcendant ... ', core_result.records; + visible_count := visible_count + 1; + + current_res.id = core_result.id; + current_res.rel = core_result.rel; + + tmp_int := 1; + IF metarecord THEN + SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id; + END IF; + + IF tmp_int = 1 THEN + current_res.record = core_result.records[1]; + ELSE + current_res.record = NULL; + END IF; + + RETURN NEXT current_res; + + CONTINUE; + END IF; + + PERFORM 1 + FROM asset.call_number cn + JOIN asset.uri_call_number_map map ON (map.call_number = cn.id) + JOIN asset.uri uri ON (map.uri = uri.id) + WHERE NOT cn.deleted + AND cn.label = '##URI##' + AND uri.active + AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL ) + AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) + AND cn.owning_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) + LIMIT 1; + + IF FOUND THEN + -- RAISE NOTICE ' % have at least one URI ... ', core_result.records; + visible_count := visible_count + 1; + + current_res.id = core_result.id; + current_res.rel = core_result.rel; + + tmp_int := 1; + IF metarecord THEN + SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id; + END IF; + + IF tmp_int = 1 THEN + current_res.record = core_result.records[1]; + ELSE + current_res.record = NULL; + END IF; + + RETURN NEXT current_res; + + CONTINUE; + END IF; + + IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN + + PERFORM 1 + FROM asset.call_number cn + JOIN asset.copy cp ON (cp.call_number = cn.id) + WHERE NOT cn.deleted + AND NOT cp.deleted + AND cp.status IN ( SELECT * FROM search.explode_array( param_statuses ) ) + AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) + AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) + LIMIT 1; + + IF NOT FOUND THEN + PERFORM 1 + FROM biblio.peer_bib_copy_map pr + JOIN asset.copy cp ON (cp.id = pr.target_copy) + WHERE NOT cp.deleted + AND cp.status IN ( SELECT * FROM search.explode_array( param_statuses ) ) + AND pr.peer_record IN ( SELECT * FROM search.explode_array( core_result.records ) ) + AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) + LIMIT 1; + + IF NOT FOUND THEN + -- RAISE NOTICE ' % and multi-home linked records were all status-excluded ... ', core_result.records; + excluded_count := excluded_count + 1; + CONTINUE; + END IF; + END IF; + + END IF; + + IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN + + PERFORM 1 + FROM asset.call_number cn + JOIN asset.copy cp ON (cp.call_number = cn.id) + WHERE NOT cn.deleted + AND NOT cp.deleted + AND cp.location IN ( SELECT * FROM search.explode_array( param_locations ) ) + AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) + AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) + LIMIT 1; + + IF NOT FOUND THEN + PERFORM 1 + FROM biblio.peer_bib_copy_map pr + JOIN asset.copy cp ON (cp.id = pr.target_copy) + WHERE NOT cp.deleted + AND cp.location IN ( SELECT * FROM search.explode_array( param_locations ) ) + AND pr.peer_record IN ( SELECT * FROM search.explode_array( core_result.records ) ) + AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) + LIMIT 1; + + IF NOT FOUND THEN + -- RAISE NOTICE ' % and multi-home linked records were all copy_location-excluded ... ', core_result.records; + excluded_count := excluded_count + 1; + CONTINUE; + END IF; + END IF; + + END IF; + + IF staff IS NULL OR NOT staff THEN + + PERFORM 1 + FROM asset.opac_visible_copies + WHERE circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) + AND record IN ( SELECT * FROM search.explode_array( core_result.records ) ) + LIMIT 1; + + IF NOT FOUND THEN + PERFORM 1 + FROM biblio.peer_bib_copy_map pr + JOIN asset.opac_visible_copies cp ON (cp.copy_id = pr.target_copy) + WHERE cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) + AND pr.peer_record IN ( SELECT * FROM search.explode_array( core_result.records ) ) + LIMIT 1; + + IF NOT FOUND THEN + + -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records; + excluded_count := excluded_count + 1; + CONTINUE; + END IF; + END IF; + + ELSE + + PERFORM 1 + FROM asset.call_number cn + JOIN asset.copy cp ON (cp.call_number = cn.id) + WHERE NOT cn.deleted + AND NOT cp.deleted + AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) + AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) + LIMIT 1; + + IF NOT FOUND THEN + + PERFORM 1 + FROM biblio.peer_bib_copy_map pr + JOIN asset.copy cp ON (cp.id = pr.target_copy) + WHERE NOT cp.deleted + AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) + AND pr.peer_record IN ( SELECT * FROM search.explode_array( core_result.records ) ) + LIMIT 1; + + IF NOT FOUND THEN + + PERFORM 1 + FROM asset.call_number cn + WHERE cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) + LIMIT 1; + + IF FOUND THEN + -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records; + excluded_count := excluded_count + 1; + CONTINUE; + END IF; + END IF; + + END IF; + + END IF; + + visible_count := visible_count + 1; + + current_res.id = core_result.id; + current_res.rel = core_result.rel; + + tmp_int := 1; + IF metarecord THEN + SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id; + END IF; + + IF tmp_int = 1 THEN + current_res.record = core_result.records[1]; + ELSE + current_res.record = NULL; + END IF; + + RETURN NEXT current_res; + + IF visible_count % 1000 = 0 THEN + -- RAISE NOTICE ' % visible so far ... ', visible_count; + END IF; + + END LOOP; + + current_res.id = NULL; + current_res.rel = NULL; + current_res.record = NULL; + current_res.total = total_count; + current_res.checked = check_count; + current_res.deleted = deleted_count; + current_res.visible = visible_count; + current_res.excluded = excluded_count; + + CLOSE core_cursor; + + RETURN NEXT current_res; + +END; +$func$ LANGUAGE PLPGSQL; + +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, include_xmlns BOOL DEFAULT TRUE) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name holdings, + XMLATTRIBUTES( + CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END 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', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'bre'), 'holdings_xml'), $3, $4, $6, $7, FALSE) 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',evergreen.array_remove_item_by_value(evergreen.array_remove_item_by_value('{acn,auri}'::TEXT[] || $5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE) + 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, FALSE) + FROM serial.subscription + WHERE record_entry = $1 + )x) + ) + ELSE NULL END, + CASE WHEN ('acp' = ANY ($5)) THEN + XMLELEMENT( + name foreign_copies, + (SELECT XMLAGG(acp) FROM ( + SELECT unapi.acp(p.target_copy,'xml','copy','{}'::TEXT[], $3, $4, $6, $7, FALSE) + FROM biblio.peer_bib_copy_map p + JOIN asset.copy c ON (p.target_copy = c.id) + WHERE NOT c.deleted AND peer_record = $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, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name copy, + XMLATTRIBUTES( + CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END 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', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE), + unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE), + unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8), + unapi.aou( circ_lib, $2, 'circlib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8), + CASE WHEN ('acn' = ANY ($4)) THEN unapi.acn( call_number, $2, 'call_number', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) ELSE NULL END, + XMLELEMENT( name copy_notes, + CASE + WHEN ('acpn' = ANY ($4)) THEN + XMLAGG((SELECT unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) 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', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) FROM asset.stat_cat_entry_copy_map WHERE owning_copy = cp.id)) + ELSE NULL + END + ), + XMLELEMENT( name foreign_records, + CASE + WHEN ('bre' = ANY ($4)) THEN + XMLAGG((SELECT unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE) FROM biblio.peer_bib_copy_map WHERE target_copy = cp.id)) + ELSE NULL + END + + ), + CASE + WHEN ('bmp' = ANY ($4)) THEN + XMLELEMENT( name monograph_parts, + XMLAGG((SELECT unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) 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 asset.refresh_opac_visible_copies_mat_view () RETURNS VOID AS $$ + + TRUNCATE TABLE asset.opac_visible_copies; + + INSERT INTO asset.opac_visible_copies (copy_id, circ_lib, record) + SELECT cp.id, cp.circ_lib, cn.record + FROM asset.copy cp + JOIN asset.call_number cn ON (cn.id = cp.call_number) + JOIN actor.org_unit a ON (cp.circ_lib = a.id) + JOIN asset.copy_location cl ON (cp.location = cl.id) + JOIN config.copy_status cs ON (cp.status = cs.id) + JOIN biblio.record_entry b ON (cn.record = b.id) + WHERE NOT cp.deleted + AND NOT cn.deleted + AND NOT b.deleted + AND cs.opac_visible + AND cl.opac_visible + AND cp.opac_visible + AND a.opac_visible + UNION + SELECT cp.id, cp.circ_lib, pbcm.peer_record AS record + FROM asset.copy cp + JOIN biblio.peer_bib_copy_map pbcm ON (pbcm.target_copy = cp.id) + JOIN actor.org_unit a ON (cp.circ_lib = a.id) + JOIN asset.copy_location cl ON (cp.location = cl.id) + JOIN config.copy_status cs ON (cp.status = cs.id) + WHERE NOT cp.deleted + AND cs.opac_visible + AND cl.opac_visible + AND cp.opac_visible + AND a.opac_visible; + +$$ LANGUAGE SQL; +COMMENT ON FUNCTION asset.refresh_opac_visible_copies_mat_view() IS $$ +Rebuild the copy OPAC visibility cache. Useful during migrations. +$$; + +SELECT asset.refresh_opac_visible_copies_mat_view(); +CREATE INDEX opac_visible_copies_idx1 on asset.opac_visible_copies (record, circ_lib); +CREATE INDEX opac_visible_copies_copy_id_idx on asset.opac_visible_copies (copy_id); +CREATE UNIQUE INDEX opac_visible_copies_once_per_record_idx on asset.opac_visible_copies (copy_id, record); + +CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$ +DECLARE + add_query TEXT; + remove_query TEXT; + do_add BOOLEAN := false; + do_remove BOOLEAN := false; +BEGIN + add_query := $$ + INSERT INTO asset.opac_visible_copies (copy_id, circ_lib, record) + SELECT id, circ_lib, record FROM ( + SELECT cp.id, cp.circ_lib, cn.record, cn.id AS call_number + FROM asset.copy cp + JOIN asset.call_number cn ON (cn.id = cp.call_number) + JOIN actor.org_unit a ON (cp.circ_lib = a.id) + JOIN asset.copy_location cl ON (cp.location = cl.id) + JOIN config.copy_status cs ON (cp.status = cs.id) + JOIN biblio.record_entry b ON (cn.record = b.id) + WHERE NOT cp.deleted + AND NOT cn.deleted + AND NOT b.deleted + AND cs.opac_visible + AND cl.opac_visible + AND cp.opac_visible + AND a.opac_visible + UNION + SELECT cp.id, cp.circ_lib, pbcm.peer_record AS record, NULL AS call_number + FROM asset.copy cp + JOIN biblio.peer_bib_copy_map pbcm ON (pbcm.target_copy = cp.id) + JOIN actor.org_unit a ON (cp.circ_lib = a.id) + JOIN asset.copy_location cl ON (cp.location = cl.id) + JOIN config.copy_status cs ON (cp.status = cs.id) + WHERE NOT cp.deleted + AND cs.opac_visible + AND cl.opac_visible + AND cp.opac_visible + AND a.opac_visible + ) AS x + + $$; + + remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE copy_id IN ( SELECT id FROM asset.copy WHERE $$; + + IF TG_TABLE_NAME = 'peer_bib_copy_map' THEN + IF TG_OP = 'INSERT' THEN + add_query := add_query || 'WHERE x.id = ' || NEW.target_copy || ' AND x.record = ' || NEW.peer_record || ';'; + EXECUTE add_query; + RETURN NEW; + ELSE + remove_query := 'DELETE FROM asset.opac_visible_copies WHERE copy_id = ' || OLD.target_copy || ' AND record = ' || OLD.peer_record || ';'; + EXECUTE remove_query; + RETURN OLD; + END IF; + END IF; + + IF TG_OP = 'INSERT' THEN + + IF TG_TABLE_NAME IN ('copy', 'unit') THEN + add_query := add_query || 'WHERE x.id = ' || NEW.id || ';'; + EXECUTE add_query; + END IF; + + RETURN NEW; + + END IF; + + -- handle items first, since with circulation activity + -- their statuses change frequently + IF TG_TABLE_NAME IN ('copy', 'unit') THEN + + IF OLD.location <> NEW.location OR + OLD.call_number <> NEW.call_number OR + OLD.status <> NEW.status OR + OLD.circ_lib <> NEW.circ_lib THEN + -- any of these could change visibility, but + -- we'll save some queries and not try to calculate + -- the change directly + do_remove := true; + do_add := true; + ELSE + + IF OLD.deleted <> NEW.deleted THEN + IF NEW.deleted THEN + do_remove := true; + ELSE + do_add := true; + END IF; + END IF; + + IF OLD.opac_visible <> NEW.opac_visible THEN + IF OLD.opac_visible THEN + do_remove := true; + ELSIF NOT do_remove THEN -- handle edge case where deleted item + -- is also marked opac_visible + do_add := true; + END IF; + END IF; + + END IF; + + IF do_remove THEN + DELETE FROM asset.opac_visible_copies WHERE id = NEW.id; + END IF; + IF do_add THEN + add_query := add_query || 'WHERE x.id = ' || NEW.id || ';'; + EXECUTE add_query; + END IF; + + RETURN NEW; + + END IF; + + IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column + + IF OLD.deleted AND NEW.deleted THEN -- do nothing + + RETURN NEW; + + ELSIF NEW.deleted THEN -- remove rows + + IF TG_TABLE_NAME = 'call_number' THEN + DELETE FROM asset.opac_visible_copies WHERE id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id); + ELSIF TG_TABLE_NAME = 'record_entry' THEN + DELETE FROM asset.opac_visible_copies WHERE record = NEW.id; + END IF; + + RETURN NEW; + + ELSIF OLD.deleted THEN -- add rows + + IF TG_TABLE_NAME IN ('copy','unit') THEN + add_query := add_query || 'WHERE x.id = ' || NEW.id || ';'; + ELSIF TG_TABLE_NAME = 'call_number' THEN + add_query := add_query || 'WHERE x.call_number = ' || NEW.id || ';'; + ELSIF TG_TABLE_NAME = 'record_entry' THEN + add_query := add_query || 'WHERE x.record = ' || NEW.id || ';'; + END IF; + + EXECUTE add_query; + RETURN NEW; + + END IF; + + END IF; + + IF TG_TABLE_NAME = 'call_number' THEN + + IF OLD.record <> NEW.record THEN + -- call number is linked to different bib + remove_query := remove_query || 'call_number = ' || NEW.id || ');'; + EXECUTE remove_query; + add_query := add_query || 'WHERE x.call_number = ' || NEW.id || ';'; + EXECUTE add_query; + END IF; + + RETURN NEW; + + END IF; + + IF TG_TABLE_NAME IN ('record_entry') THEN + RETURN NEW; -- don't have 'opac_visible' + END IF; + + -- actor.org_unit, asset.copy_location, asset.copy_status + IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing + + RETURN NEW; + + ELSIF NEW.opac_visible THEN -- add rows + + IF TG_TABLE_NAME = 'org_unit' THEN + add_query := add_query || 'AND cp.circ_lib = ' || NEW.id || ';'; + ELSIF TG_TABLE_NAME = 'copy_location' THEN + add_query := add_query || 'AND cp.location = ' || NEW.id || ';'; + ELSIF TG_TABLE_NAME = 'copy_status' THEN + add_query := add_query || 'AND cp.status = ' || NEW.id || ';'; + END IF; + + EXECUTE add_query; + + ELSE -- delete rows + + IF TG_TABLE_NAME = 'org_unit' THEN + remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';'; + ELSIF TG_TABLE_NAME = 'copy_location' THEN + remove_query := remove_query || 'location = ' || NEW.id || ');'; + ELSIF TG_TABLE_NAME = 'copy_status' THEN + remove_query := remove_query || 'status = ' || NEW.id || ');'; + END IF; + + EXECUTE remove_query; + + END IF; + + RETURN NEW; +END; +$func$ LANGUAGE PLPGSQL; +COMMENT ON FUNCTION asset.cache_copy_visibility() IS $$ +Trigger function to update the copy OPAC visiblity cache. +$$; + +CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR DELETE ON biblio.peer_bib_copy_map FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility(); + +CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$ +DECLARE + transformed_xml TEXT; + prev_xfrm TEXT; + normalizer RECORD; + xfrm config.xml_transform%ROWTYPE; + attr_value TEXT; + new_attrs HSTORE := ''::HSTORE; + attr_def config.record_attr_definition%ROWTYPE; +BEGIN + + IF NEW.deleted IS TRUE THEN -- If this bib is deleted + DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage + DELETE FROM metabib.record_attr WHERE id = NEW.id; -- Kill the attrs hash, useless on deleted records + DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible + DELETE FROM biblio.peer_bib_copy_map WHERE peer_record = NEW.id; -- Separate any multi-homed items + RETURN NEW; -- and we're done + END IF; + + IF TG_OP = 'UPDATE' THEN -- re-ingest? + PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled; + + IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change + RETURN NEW; + END IF; + END IF; + + -- Record authority linking + PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled; + IF NOT FOUND THEN + PERFORM biblio.map_authority_linking( NEW.id, NEW.marc ); + END IF; + + -- Flatten and insert the mfr data + PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled; + IF NOT FOUND THEN + PERFORM metabib.reingest_metabib_full_rec(NEW.id); + + -- Now we pull out attribute data, which is dependent on the mfr for all but XPath-based fields + PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled; + IF NOT FOUND THEN + FOR attr_def IN SELECT * FROM config.record_attr_definition ORDER BY format LOOP + + IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection + SELECT ARRAY_TO_STRING(ARRAY_ACCUM(value), COALESCE(attr_def.joiner,' ')) INTO attr_value + FROM (SELECT * FROM metabib.full_rec ORDER BY tag, subfield) AS x + WHERE record = NEW.id + AND tag LIKE attr_def.tag + AND CASE + WHEN attr_def.sf_list IS NOT NULL + THEN POSITION(subfield IN attr_def.sf_list) > 0 + ELSE TRUE + END + GROUP BY tag + ORDER BY tag + LIMIT 1; + + ELSIF attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field + attr_value := biblio.marc21_extract_fixed_field(NEW.id, attr_def.fixed_field); + + ELSIF attr_def.xpath IS NOT NULL THEN -- and xpath expression + + SELECT INTO xfrm * FROM config.xml_transform WHERE name = attr_def.format; + + -- See if we can skip the XSLT ... it's expensive + IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN + -- Can't skip the transform + IF xfrm.xslt <> '---' THEN + transformed_xml := oils_xslt_process(NEW.marc,xfrm.xslt); + ELSE + transformed_xml := NEW.marc; + END IF; + + prev_xfrm := xfrm.name; + END IF; + + IF xfrm.name IS NULL THEN + -- just grab the marcxml (empty) transform + SELECT INTO xfrm * FROM config.xml_transform WHERE xslt = '---' LIMIT 1; + prev_xfrm := xfrm.name; + END IF; + + attr_value := oils_xpath_string(attr_def.xpath, transformed_xml, COALESCE(attr_def.joiner,' '), ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]); + + ELSIF attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map + SELECT value::TEXT INTO attr_value + FROM biblio.marc21_physical_characteristics(NEW.id) + WHERE subfield = attr_def.phys_char_sf + LIMIT 1; -- Just in case ... + + END IF; + + -- apply index normalizers to attr_value + FOR normalizer IN + SELECT n.func AS func, + n.param_count AS param_count, + m.params AS params + FROM config.index_normalizer n + JOIN config.record_attr_index_norm_map m ON (m.norm = n.id) + WHERE attr = attr_def.name + ORDER BY m.pos LOOP + EXECUTE 'SELECT ' || normalizer.func || '(' || + quote_literal( attr_value ) || + CASE + WHEN normalizer.param_count > 0 + THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'') + ELSE '' + END || + ')' INTO attr_value; + + END LOOP; + + -- Add the new value to the hstore + new_attrs := new_attrs || hstore( attr_def.name, attr_value ); + + END LOOP; + + IF TG_OP = 'INSERT' OR OLD.deleted THEN -- initial insert OR revivication + INSERT INTO metabib.record_attr (id, attrs) VALUES (NEW.id, new_attrs); + ELSE + UPDATE metabib.record_attr SET attrs = attrs || new_attrs WHERE id = NEW.id; + END IF; + + END IF; + END IF; + + -- Gather and insert the field entry data + PERFORM metabib.reingest_metabib_field_entries(NEW.id); + + -- Located URI magic + IF TG_OP = 'INSERT' THEN + PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled; + IF NOT FOUND THEN + PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor ); + END IF; + ELSE + PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled; + IF NOT FOUND THEN + PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor ); + END IF; + END IF; + + -- (re)map metarecord-bib linking + IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag + PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled; + IF NOT FOUND THEN + PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint ); + END IF; + ELSE -- we're doing an update, and we're not deleted, remap + PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled; + IF NOT FOUND THEN + PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint ); + END IF; + END IF; + + RETURN NEW; +END; +$func$ LANGUAGE PLPGSQL; + +COMMIT; diff --git a/Open-ILS/web/opac/common/js/config.js b/Open-ILS/web/opac/common/js/config.js index 5e2b66b604..287a126f6d 100644 --- a/Open-ILS/web/opac/common/js/config.js +++ b/Open-ILS/web/opac/common/js/config.js @@ -381,8 +381,10 @@ var FETCH_MR_DESCRIPTORS = 'open-ils.search:open-ils.search.metabib.record_to_d var FETCH_HIGHEST_PERM_ORG = 'open-ils.actor:open-ils.actor.user.perm.highest_org.batch'; var FETCH_USER_NOTES = 'open-ils.actor:open-ils.actor.note.retrieve.all'; var FETCH_ORG_BY_SHORTNAME = 'open-ils.actor:open-ils.actor.org_unit.retrieve_by_shortname'; -var FETCH_BIB_ID_BY_BARCODE = 'open-ils.search:open-ils.search.bib_id.by_barcode'; +var FETCH_BIB_IDS_BY_BARCODE = 'open-ils.search:open-ils.search.multi_home.bib_ids.by_barcode'; var FETCH_ORG_SETTING = 'open-ils.actor:open-ils.actor.ou_setting.ancestor_default'; +var TEST_PEER_BIBS = 'open-ils.search:open-ils.search.peer_bibs.test'; +var FETCH_PEER_BIBS = 'open-ils.search:open-ils.search.peer_bibs'; /* ---------------------------------------------------------------------------- */ diff --git a/Open-ILS/web/opac/locale/en-US/lang.dtd b/Open-ILS/web/opac/locale/en-US/lang.dtd index e8bb029fb8..ef4c6cc567 100644 --- a/Open-ILS/web/opac/locale/en-US/lang.dtd +++ b/Open-ILS/web/opac/locale/en-US/lang.dtd @@ -286,6 +286,10 @@ + + + + @@ -2455,6 +2459,21 @@ + + + + + + + + + + + + + + + @@ -2471,6 +2490,8 @@ + + diff --git a/Open-ILS/web/opac/locale/en-US/opac.dtd b/Open-ILS/web/opac/locale/en-US/opac.dtd index c1ebf6bec9..b52813aee7 100644 --- a/Open-ILS/web/opac/locale/en-US/opac.dtd +++ b/Open-ILS/web/opac/locale/en-US/opac.dtd @@ -500,6 +500,7 @@ Please see a librarian to renew your account."> + @@ -549,6 +550,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/advanced.js b/Open-ILS/web/opac/skin/default/js/advanced.js index 12728c123c..0e898e42c3 100644 --- a/Open-ILS/web/opac/skin/default/js/advanced.js +++ b/Open-ILS/web/opac/skin/default/js/advanced.js @@ -149,22 +149,28 @@ function advGenericSearch() { function advFindBarcode(barcode) { - var req = new Request(FETCH_BIB_ID_BY_BARCODE, barcode); + var req = new Request(FETCH_BIB_IDS_BY_BARCODE, barcode); req.callback(advDrawBarcode); req.request.alertEvent = false; req.send(); } function advDrawBarcode(r) { - titleid = r.getResultObject(); - if(checkILSEvent(titleid)) { + var title_ids = r.getResultObject(); + if(checkILSEvent(title_ids)) { alertId('myopac.copy.not.found'); return; } - if(!titleid) return; + if(!title_ids) return; var args = {}; - args.page = RDETAIL; - args[PARAM_RID] = titleid; + if (title_ids.length == 1) { + args.page = RDETAIL; + args[PARAM_RID] = title_ids[0]; + } else { + args.page = RRESULT; + args[PARAM_RTYPE] = RTYPE_LIST; + args[PARAM_RLIST] = title_ids; + } location.href = buildOPACLink(args); } 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 04e8e416f5..50361b6641 100644 --- a/Open-ILS/web/opac/skin/default/js/copy_details.js +++ b/Open-ILS/web/opac/skin/default/js/copy_details.js @@ -9,7 +9,7 @@ var showDueDate = false; */ var showDueTime = false; -function cpdBuild( contextTbody, contextRow, record, callnumber, orgid, depth, copy_location ) { +function cpdBuild( contextTbody, contextRow, record, callnumber, orgid, depth, copy_location, already_fetched_copies, peer_types ) { var i = cpdCheckExisting(contextRow); if(i) return i; @@ -43,33 +43,51 @@ function cpdBuild( contextTbody, contextRow, record, callnumber, orgid, depth, c var print = $n(templateRow,'print'); print.onclick = function() { cpdBuildPrintPane( contextRow, record, callnumber, orgid, depth) }; + if (typeof callnumber == 'object') { + addCSSClass(print,'hide_me'); + } var mainTbody = $n(templateRow, 'copies_tbody'); var extrasRow = mainTbody.removeChild($n(mainTbody, 'copy_extras_row')); - var req = new Request(FETCH_COPIES_FROM_VOLUME, record.doc_id(), callnumber, orgid); - req.callback(cpdDrawCopies); + var request_args = { + peer_types : peer_types, /* indexed the same as already_fetched_copies */ + contextTbody : contextTbody, /* tbody that holds the contextrow */ + contextRow : contextRow, /* the row our new row will be inserted after */ + record : record, + callnumber : callnumber, + orgid : orgid, + depth : depth, + templateRow : templateRow, /* contains everything */ + copy_location : copy_location, + mainTbody : mainTbody, /* holds the copy rows */ + extrasRow : extrasRow, /* wrapper row for all extras */ + counter : counter + } - req.request.args = { - contextTbody : contextTbody, /* tbody that holds the contextrow */ - contextRow : contextRow, /* the row our new row will be inserted after */ - record : record, - callnumber : callnumber, - orgid : orgid, - depth : depth, - templateRow : templateRow, /* contains everything */ - copy_location : copy_location, - mainTbody : mainTbody, /* holds the copy rows */ - extrasRow : extrasRow, /* wrapper row for all extras */ - counter : counter - }; + if (! already_fetched_copies) { + var req = new Request(FETCH_COPIES_FROM_VOLUME, record.doc_id(), callnumber, orgid); + req.callback(cpdDrawCopies); + + req.request.args = request_args; + + req.send(); + } else { + setTimeout( + function() { + delete request_args['copy_location']; + cpdDrawCopies({ + 'args' : request_args, + 'getResultObject' : function() { return already_fetched_copies; } + }); + }, 0 + ); + } if( contextRow.nextSibling ) contextTbody.insertBefore( templateRow, contextRow.nextSibling ); else contextTbody.appendChild( templateRow ); - - req.send(); _debug('creating new details row with id ' + templateRow.id); cpdNodes[templateRow.id] = { templateRow : templateRow }; return templateRow.id; @@ -196,11 +214,27 @@ function cpdDrawCopies(r) { for( var i = 0; i < copies.length; i++ ) { var row = copyrow.cloneNode(true); var copyid = copies[i]; - var req = new Request(FETCH_FLESHED_COPY, copies[i]); - req.callback(cpdDrawCopy); - req.request.args = r.args; - req.request.row = row; - req.send(); + var pt; if (args.peer_types) pt = args.peer_types[i]; + if (typeof copyid != 'object') { + var req = new Request(FETCH_FLESHED_COPY, copyid); + req.callback(cpdDrawCopy); + req.request.args = r.args; + req.request.row = row; + req.send(); + } else { + setTimeout( + function(copy,row,pt) { + return function() { + cpdDrawCopy({ + 'getResultObject' : function() { return copy; }, + 'args' : r.args, + 'peer_type' : pt, + 'row' : row + }); + }; + }(copies[i],row,pt), 0 + ); + } copytbody.appendChild(row); } } @@ -208,6 +242,7 @@ function cpdDrawCopies(r) { function cpdDrawCopy(r) { var copy = r.getResultObject(); var row = r.row; + var pt = r.peer_type; var trow = r.args.templateRow; if (r.args.copy_location && copy.location().name() != r.args.copy_location) { @@ -215,7 +250,13 @@ function cpdDrawCopy(r) { return; } - $n(row, 'barcode').appendChild(text(copy.barcode())); + var b = $n(row, 'barcode').appendChild(text(copy.barcode())); + + /* show the peer type*/ + if (pt) { + $n(row, 'barcode').appendChild(text(' :: ' + pt)); + } + $n(row, 'location').appendChild(text(copy.location().name())); $n(row, 'status').appendChild(text(copy.status().name())); @@ -231,6 +272,20 @@ function cpdDrawCopy(r) { } } + /* show the other bibs link */ + if (copy.peer_record_maps().length > 0) { + var l = $n(row, 'copy_multi_home'); + unHideMe(l); + var link_args = {}; + link_args.page = RRESULT; + link_args[PARAM_RTYPE] = RTYPE_LIST; + link_args[PARAM_RLIST] = new Array(); + for (var i = 0; i < copy.peer_record_maps().length; i++) { + link_args[PARAM_RLIST].push( copy.peer_record_maps()[i].peer_record() ); + } + l.setAttribute('href',buildOPACLink(link_args)); + } + if(isXUL()) { /* show the hold link */ var l = $n(row, 'copy_hold_link'); diff --git a/Open-ILS/web/opac/skin/default/js/rdetail.js b/Open-ILS/web/opac/skin/default/js/rdetail.js index b1fc03b255..4b2984d6f2 100644 --- a/Open-ILS/web/opac/skin/default/js/rdetail.js +++ b/Open-ILS/web/opac/skin/default/js/rdetail.js @@ -237,6 +237,64 @@ function rdetailViewMarc(r,id) { $('rdetail_view_marc_box').insertBefore(span, $('rdetail_view_marc_box').firstChild); } +function rdetailForeignItems(r,id) { + hideMe($('rdetail_extras_loading')); + var tbody = $('rdetail_foreign_items_tbody'); + + var robj = r.getResultObject(); /* mvr list with foreign_copy_maps fleshed */ + + for (var i = 0; i < robj.length; i++) { + var args = {}; + args.page = RDETAIL; + args[PARAM_OFFSET] = 0; + args[PARAM_RID] = robj[i].doc_id(); + var row = elem('tr'); tbody.appendChild(row); + var td1 = elem('td'); row.appendChild(td1); + var title = elem( + 'a', + { + 'href' : buildOPACLink(args), + 'class' : 'classic_link' + }, + robj[i].title() + ); + td1.appendChild(title); + var td2 = elem('td',{},robj[i].author()); row.appendChild(td2); + var td3 = elem('td'); row.appendChild(td3); + var details = elem( + 'a', + { + 'href' : 'javascript:void(0)', + 'class' : 'classic_link' + }, + 'Copy Details' + ); + details.onclick = function(idx,context_row){ + return function() { + cpdBuild( + tbody, + context_row, + robj[idx], + null, + 1, + 0, + 1, + dojo.map( + robj[idx].foreign_copy_maps(), + function(x){ return x.target_copy(); } + ), + dojo.map( + robj[idx].foreign_copy_maps(), + function(x){ return x.peer_type().name(); } + ) + ); + }; + }(i,row); + td3.appendChild(details); + } +} + + function rdetailShowLocalCopies() { rdetailShowLocal = true; @@ -566,6 +624,16 @@ function _rdetailDraw(r) { if (novelist && currentISBN) { unHideMe($('rdetail_novelist_link')); } + + // Multi-Home / Foreign Items / Peer Bibs + var req = new Request( TEST_PEER_BIBS, record.doc_id() ); + req.callback(function(r){ + var test = r.getResultObject(); + if (test == "1") { + unHideMe($('rdetail_foreign_items_link')); + } + }); + req.send(); } @@ -635,6 +703,7 @@ function rdetailAddToBookbag() { var rdetailMarcFetched = false; +var rdetailForeignItemsFetched = false; function rdetailShowExtra(type, args) { hideMe($('rdetail_copy_info_div')); @@ -648,6 +717,7 @@ function rdetailShowExtra(type, args) { hideMe($('cn_browse')); hideMe($('rdetail_cn_browse_div')); hideMe($('rdetail_novelist_div')); + hideMe($('rdetail_foreign_items_div')); hideMe($('rdetail_notes_div')); removeCSSClass($('rdetail_copy_info_link'), 'rdetail_extras_selected'); @@ -661,6 +731,7 @@ function rdetailShowExtra(type, args) { removeCSSClass($('rdetail_annotation_link'), 'rdetail_extras_selected'); removeCSSClass($('rdetail_viewmarc_link'), 'rdetail_extras_selected'); removeCSSClass($('rdetail_novelist_link'), 'rdetail_extras_selected'); + removeCSSClass($('rdetail_foreign_items_link'), 'rdetail_extras_selected'); switch(type) { @@ -716,6 +787,17 @@ function rdetailShowExtra(type, args) { unHideMe($('rdetail_novelist_div')); break; + case "foreign_items": + addCSSClass($('rdetail_foreign_items_link'), 'rdetail_extras_selected'); + unHideMe($('rdetail_foreign_items_div')); + if(rdetailForeignItemsFetched) return; + unHideMe($('rdetail_extras_loading')); + rdetailForeignItemsFetched = true; + var req = new Request( FETCH_PEER_BIBS, record.doc_id() ); + req.callback(rdetailForeignItems); + req.send(); + break; + case 'cn': addCSSClass($('rdetail_viewcn_link'), 'rdetail_extras_selected'); unHideMe($('rdetail_cn_browse_div')); 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 45a5c85d5d..f937089394 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 @@ -32,6 +32,8 @@ href='javascript:void(0);'>&rdetail.cn.hold; &rdetail.cn.reserve; + &rdetail.cn.multi_home; diff --git a/Open-ILS/web/opac/skin/default/xml/rdetail/rdetail_extras.xml b/Open-ILS/web/opac/skin/default/xml/rdetail/rdetail_extras.xml index 4a2b94dcf5..424582496f 100644 --- a/Open-ILS/web/opac/skin/default/xml/rdetail/rdetail_extras.xml +++ b/Open-ILS/web/opac/skin/default/xml/rdetail/rdetail_extras.xml @@ -70,6 +70,12 @@ class='classic_link'>&rdetail.extras.novelist; + + &rdetail.extras.foreign_items; + + @@ -117,6 +123,16 @@ +
+ + + + + + +
&common.title; &common.authors;  
+
+
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 f675cbe029..f73f071d37 100644 --- a/Open-ILS/xul/staff_client/chrome/content/OpenILS/data.js +++ b/Open-ILS/xul/staff_client/chrome/content/OpenILS/data.js @@ -577,6 +577,27 @@ OpenILS.data.prototype = { } ); + this.chain.push( + function() { + var f = gen_fm_retrieval_func( + 'bpt', + [ + api.FM_BPT_PCRUD_SEARCH.app, + api.FM_BPT_PCRUD_SEARCH.method, + [ obj.session.key, {"id":{"!=":null}}, {"order_by":{"bpt":"id"}} ], + 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( 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 befb693aec..8250caedcd 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 @@ -595,3 +595,33 @@ return url; } + function widget_prompt(node,args) { + // args is an object that may contain the following keys: title, desc, ok_label, ok_accesskey, cancel_label, cancel_accesskey, access, method + // access may contain 'property' or 'attribute' or 'method' for retrieving the value from the node + // if 'method', then the method key will reference a function that returns the value + try { + if (!node) { return false; } + if (!args) { args = {}; } + args[ 'widget' ] = node; + + var url = location.protocol == 'chrome' + ? 'chrome://open_ils_staff_client/content/util/widget_prompt.xul' + : '/xul/server/util/widget_prompt.xul'; + + JSAN.use('util.window'); var win = new util.window(); + var my_xulG = win.open( + url, + args.title || 'widget_prompt', + 'chrome,modal', + args + ); + + if (my_xulG.status == 'incomplete') { + return false; + } else { + return my_xulG.value; + } + } catch(E) { + alert('Error in global_utils.js, widget_prompt(): ' + E); + } + } 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 678efd5ead..47b45ea6b2 100644 --- a/Open-ILS/xul/staff_client/chrome/content/cat/opac.js +++ b/Open-ILS/xul/staff_client/chrome/content/cat/opac.js @@ -3,6 +3,8 @@ var docid; var marc_html; var top_pane; var bottom_pane; var opac_frame; var opa var marc_view_reset = true; var marc_edit_reset = true; var copy_browser_reset = true; +var manage_parts_reset = true; +var manage_multi_home_reset = true; var hold_browser_reset = true; var serctrl_view_reset = true; @@ -674,10 +676,20 @@ function mark_for_overlay() { g.data.stash('marked_record_mvr'); if (g.data.marked_record_mvr) { alert(document.getElementById('offlineStrings').getFormattedString('cat.opac.record_marked_for_overlay.tcn.alert',[ g.data.marked_record_mvr.tcn() ])); - xulG.set_statusbar(1, $("offlineStrings").getFormattedString('staff.cat.z3950.marked_record_for_overlay_indicator.tcn.label',[g.data.marked_record_mvr.tcn()]) ); + xulG.set_statusbar( + 1, + $("offlineStrings").getFormattedString('staff.cat.z3950.marked_record_for_overlay_indicator.tcn.label',[g.data.marked_record_mvr.tcn()]), + $("offlineStrings").getFormattedString('staff.cat.z3950.marked_record_for_overlay_indicator.record_id.label',[g.data.marked_record]), + gen_statusbar_click_handler('marked_record') + ); } else { alert(document.getElementById('offlineStrings').getFormattedString('cat.opac.record_marked_for_overlay.record_id.alert',[ g.data.marked_record ])); - xulG.set_statusbar(1, $("offlineStrings").getFormattedString('staff.cat.z3950.marked_record_for_overlay_indicator.record_id.label',[g.data.marked_record]) ); + xulG.set_statusbar( + 1, + $("offlineStrings").getFormattedString('staff.cat.z3950.marked_record_for_overlay_indicator.record_id.label',[g.data.marked_record]), + '', + gen_statusbar_click_handler('marked_record') + ); } } @@ -694,10 +706,22 @@ function mark_for_hold_transfer() { g.data.stash('marked_record_for_hold_transfer_mvr'); if (g.data.marked_record_mvr) { var m = $("offlineStrings").getFormattedString('staff.cat.opac.marked_record_for_hold_transfer_indicator.tcn.label',[g.data.marked_record_for_hold_transfer_mvr.tcn()]); - alert(m); xulG.set_statusbar(1, m ); + alert(m); + xulG.set_statusbar( + 3, + m, + '', + gen_statusbar_click_handler('marked_record_for_hold_transfer') + ); } else { var m = $("offlineStrings").getFormattedString('staff.cat.opac.marked_record_for_hold_transfer_indicator.record_id.label',[g.data.marked_record_for_hold_transfer]); - alert(m); xulG.set_statusbar(1, m ); + alert(m); + xulG.set_statusbar( + 3, + m, + '', + gen_statusbar_click_handler('marked_record_for_hold_transfer') + ); } } @@ -764,6 +788,9 @@ function refresh_display(id) { marc_edit_reset = true; copy_browser_reset = true; hold_browser_reset = true; + manage_parts_reset = true; + manage_multi_home_reset = true; + serctrl_view_reset = true; while(top_pane.node.lastChild) top_pane.node.removeChild( top_pane.node.lastChild ); var children = bottom_pane.node.childNodes; for (var i = 0; i < children.length; i++) { @@ -845,17 +872,103 @@ function add_volumes() { function manage_parts() { try { - var title = document.getElementById('offlineStrings').getFormattedString('staff.cat.manage_parts.title', [docid]); + g.view = 'manage_parts'; 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 }, - {} - ); + if (manage_parts_reset) { + bottom_pane.reset_iframe( loc,{},xulG); + manage_parts_reset =false; + } else { + bottom_pane.set_iframe( loc,{},xulG); + } + opac_wrapper_set_help_context(); + bottom_pane.get_contentWindow().addEventListener('load',opac_wrapper_set_help_context,false); } catch(E) { alert('Error in chrome/content/cat/opac.js, manage_parts(): ' + E); } } +function manage_multi_home_items() { + try { + g.view = 'manage_multi_home'; + var loc = window.xulG.url_prefix(urls.MANAGE_MULTI_HOME_ITEMS); + if (manage_multi_home_reset) { + bottom_pane.reset_iframe( loc,{},{'docid':docid,'no_bib_summary':true,'url_prefix':xulG.url_prefix,'new_tab':xulG.new_tab}); + manage_multi_home_reset =false; + } else { + bottom_pane.set_iframe( loc,{},{'docid':docid,'no_bib_summary':true,'url_prefix':xulG.url_prefix,'new_tab':xulG.new_tab}); + } + opac_wrapper_set_help_context(); + bottom_pane.get_contentWindow().addEventListener('load',opac_wrapper_set_help_context,false); + } catch(E) { + alert('Error in chrome/content/cat/opac.js, manage_multi_home_items(): ' + E); + } +} + +function mark_for_multi_home() { + g.data.marked_multi_home_record = docid; + g.data.stash('marked_multi_home_record'); + var robj = g.network.simple_request('MODS_SLIM_RECORD_RETRIEVE.authoritative',[docid]); + if (typeof robj.ilsevent == 'undefined') { + g.data.marked_multi_home_record_mvr = robj; + } else { + g.data.marked_multi_home_record_mvr = null; + g.error.standard_unexpected_error_alert('in mark_for_multi_home',robj); + } + g.data.stash('marked_multi_home_record_mvr'); + + if (g.data.marked_multi_home_record_mvr) { + alert(document.getElementById('offlineStrings').getFormattedString('cat.opac.record_marked_for_multi_home.tcn.alert',[ g.data.marked_multi_home_record_mvr.tcn() ])); + xulG.set_statusbar( + 2, + $("offlineStrings").getFormattedString('staff.cat.copy_browser.marked_record_for_multi_home_indicator.tcn.label',[g.data.marked_multi_home_record_mvr.tcn()]), + $("offlineStrings").getFormattedString('staff.cat.copy_browser.marked_record_for_multi_home_indicator.record_id.label',[g.data.marked_multi_home_record]), + gen_statusbar_click_handler('marked_multi_home_record') + ); + } else { + alert(document.getElementById('offlineStrings').getFormattedString('cat.opac.record_marked_for_multi_home.record_id.alert',[ g.data.marked_multi_home_record ])); + xulG.set_statusbar( + 2, + $("offlineStrings").getFormattedString('staff.cat.copy_browser.marked_record_for_multi_home_indicator.record_id.label',[g.data.marked_multi_home_record]), + '', + gen_statusbar_click_handler('marked_multi_home_record') + ); + } +} + +function gen_statusbar_click_handler(data_key) { + return function (ev) { + + if (! g.data[data_key]) { + return; + } + + if (ev.button == 0 /* left click, spawn opac */) { + var opac_url = xulG.url_prefix( urls.opac_rdetail ) + '?r=' + g.data[data_key]; + var content_params = { + 'session' : ses(), + 'authtime' : ses('authtime'), + 'opac_url' : opac_url, + }; + xulG.new_tab( + xulG.url_prefix(urls.XUL_OPAC_WRAPPER), + {'tab_name':'Retrieving title...'}, + content_params + ); + } + + if (ev.button == 2 /* right click, remove mark */) { + if ( window.confirm( document.getElementById('offlineStrings').getString('cat.opac.clear_statusbar') ) ) { + g.data[data_key] = null; + g.data.stash(data_key); + ev.target.setAttribute('label',''); + if (ev.target.hasAttribute('tooltiptext')) { + ev.target.removeAttribute('tooltiptext'); + } + } + } + } +} + + 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 9e8549d53e..b7d517f9bf 100644 --- a/Open-ILS/xul/staff_client/chrome/content/cat/opac.xul +++ b/Open-ILS/xul/staff_client/chrome/content/cat/opac.xul @@ -48,6 +48,8 @@ + + @@ -59,14 +61,11 @@ - + - - - @@ -77,6 +76,9 @@ + + + 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 802592d185..56ed0591ef 100644 --- a/Open-ILS/xul/staff_client/chrome/content/main/constants.js +++ b/Open-ILS/xul/staff_client/chrome/content/main/constants.js @@ -203,6 +203,7 @@ var api = { 'FM_AUSP_PCRUD_UPDATE' : { 'app' : 'open-ils.pcrud', 'method' : 'open-ils.pcrud.update.ausp', 'secure' : false }, 'FM_AUSP_UPDATE_NOTE' : { 'app' : 'open-ils.actor', 'method' : 'open-ils.actor.user.penalty.note.update' }, 'FM_BOOKING_CREATE_BRT_AND_BRSRC' : { 'app' : 'open-ils.booking', 'method' : 'open-ils.booking.create_brt_and_brsrc_from_copies' }, + 'FM_BPT_PCRUD_SEARCH' : { 'app' : 'open-ils.pcrud', 'method' : 'open-ils.pcrud.search.bpt.atomic', 'secure' : false }, 'FM_BRESV_RETRIEVE_VIA_PCRUD' : { 'app' : 'open-ils.pcrud', 'method' : 'open-ils.pcrud.search.bresv.atomic' }, 'FM_BRSRC_RETRIEVE_VIA_PCRUD' : { 'app' : 'open-ils.pcrud', 'method' : 'open-ils.pcrud.search.brsrc.atomic' }, 'FM_BRT_RETRIEVE_VIA_PCRUD' : { 'app' : 'open-ils.pcrud', 'method' : 'open-ils.pcrud.search.brt.atomic' }, @@ -380,6 +381,7 @@ var urls = { 'AUDIO_event_ASSET_COPY_NOT_FOUND' : '/xul/server/skin/media/audio/redalert.wav', 'AUTHORITY_MANAGE' : '/eg/cat/authority/list', + 'MANAGE_MULTI_HOME_ITEMS' : '/xul/server/cat/manage_multi_home_items.xul', 'XUL_AUTH_SIMPLE' : '/xul/server/main/simple_auth.xul', 'XUL_BIB_BRIEF' : '/xul/server/cat/bib_brief.xul', 'XUL_BIB_BRIEF_VERTICAL' : '/xul/server/cat/bib_brief_vertical.xul', 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 54ea790d12..b8b9f8a940 100644 --- a/Open-ILS/xul/staff_client/chrome/content/main/menu.js +++ b/Open-ILS/xul/staff_client/chrome/content/main/menu.js @@ -1724,9 +1724,26 @@ commands: content_params.url_prefix = function(url) { return obj.url_prefix(url); }; content_params.network_meter = obj.network_meter; content_params.page_meter = obj.page_meter; - content_params.set_statusbar = function(slot,text) { + content_params.set_statusbar = function(slot,text,tooltiptext,click_handler) { var e = document.getElementById('statusbarpanel'+slot); - if (e) { e.setAttribute('label',text); } + if (e) { + var p = e.parentNode; + var sbp = document.createElement('statusbarpanel'); + sbp.setAttribute('id','statusbarpanel'+slot); + p.replaceChild(sbp,e); // destroy and replace the statusbarpanel as a poor man's way of clearing event handlers + + sbp.setAttribute('label',text); + if (tooltiptext) { + sbp.setAttribute('tooltiptext',tooltiptext); + } + if (click_handler) { + sbp.addEventListener( + 'click', + click_handler, + false + ); + } + } }; content_params.chrome_xulG = xulG; content_params._data = xulG._data; diff --git a/Open-ILS/xul/staff_client/chrome/content/util/exec.js b/Open-ILS/xul/staff_client/chrome/content/util/exec.js index 38f0ca3149..18e7d1ec0d 100644 --- a/Open-ILS/xul/staff_client/chrome/content/util/exec.js +++ b/Open-ILS/xul/staff_client/chrome/content/util/exec.js @@ -22,10 +22,7 @@ util.exec.prototype = { 'timer' : function(funcs,interval) { var obj = this; - if (obj._intervalId) { - obj.clear_timer(); - window.removeEventListener('unload',obj.clear_timer,false); - } + obj.clear_timer(); var intervalId = window.setInterval( function() { if (typeof obj.debug != 'undefined' && obj.debug) { dump('EXEC: ' + location.pathname + ': Running interval with id = ' + intervalId + '\n'); } @@ -40,6 +37,13 @@ util.exec.prototype = { window.addEventListener('unload',obj.clear_timer,false); return intervalId; }, + 'clear_timer' : function() { + var obj = this; + if (obj._intervalId) { + obj.clear_timer(); + window.removeEventListener('unload',obj.clear_timer,false); + } + }, // This executes a series of functions, but tries to give other events/functions a chance to // execute between each one. 'chain' : function () { diff --git a/Open-ILS/xul/staff_client/chrome/content/util/widget_prompt.js b/Open-ILS/xul/staff_client/chrome/content/util/widget_prompt.js new file mode 100644 index 0000000000..c51689b71b --- /dev/null +++ b/Open-ILS/xul/staff_client/chrome/content/util/widget_prompt.js @@ -0,0 +1,78 @@ +var xulG = {}; +var widget; + +function my_init() { + try { + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + if (typeof JSAN == 'undefined') { throw( "The JSAN library object is missing."); } + JSAN.errorLevel = "die"; // none, warn, or die + JSAN.addRepository('/xul/server/'); + JSAN.use('util.error'); g.error = new util.error(); + g.error.sdump('D_TRACE','my_init() for widget_prompt.xul'); + + widget = xul_param('widget',{'modal_xulG':true}); + if (widget) { + $('widget_prompt_main').appendChild(widget); + } + + var ok_label = xul_param('ok_label',{'modal_xulG':true}) || offlineStrings.getString('common.ok.label'); + $('ok_btn').setAttribute('label',ok_label); + + var ok_accesskey = xul_param('ok_accesskey',{'modal_xulG':true}) || offlineStrings.getString('common.ok.accesskey'); + $('ok_btn').setAttribute('accesskey',ok_accesskey); + + var cancel_label = xul_param('cancel_label',{'modal_xulG':true}) || offlineStrings.getString('common.cancel.label'); + $('cancel_btn').setAttribute('label',cancel_label); + + var cancel_accesskey = xul_param('cancel_accesskey',{'modal_xulG':true}) || offlineStrings.getString('common.cancel.accesskey'); + $('cancel_btn').setAttribute('accesskey',cancel_accesskey); + + var desc = xul_param('desc',{'modal_xulG':true}); + if (desc) { + $('desc').appendChild( document.createTextNode( desc ) ); + } + + $('ok_btn').addEventListener('command',widget_save,false); + $('cancel_btn').addEventListener('command',function(ev) { window.close(); },false); + + if (xul_param('title',{'modal_xulG':true})) { + try { window.title = xul_param('title',{'modal_xulG':true}); } catch(E) {} + try { document.title = xul_param('title',{'modal_xulG':true}); } catch(E) {} + } + + xulG[ 'status' ] = 'incomplete'; + update_modal_xulG(xulG); + + try { widget.focus(); } catch(E) {} + + } catch(E) { + alert('Error in widget_prompt.js, my_init(): ' + E); + } +} + +function widget_save(ev) { + try { + if (widget) { + switch( xul_param('access',{'modal_xulG':true}) ) { + case 'method' : + xulG[ 'value' ] = xulG[ 'method' ](); + break; + case 'attribute': + xulG[ 'value' ] = widget.getAttribute('value'); + break; + case 'property': + default: + xulG[ 'value' ] = widget.value; + break; + } + } + xulG[ 'status' ] = 'complete'; + + update_modal_xulG(xulG); + + window.close(); + } catch(E) { + alert('Error in widget_prompt.js, widget_save(): ' + E); + } +} + diff --git a/Open-ILS/xul/staff_client/chrome/content/util/widget_prompt.xul b/Open-ILS/xul/staff_client/chrome/content/util/widget_prompt.xul new file mode 100644 index 0000000000..303a4c3e90 --- /dev/null +++ b/Open-ILS/xul/staff_client/chrome/content/util/widget_prompt.xul @@ -0,0 +1,49 @@ + + + + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + +