Monograph Parts; Unified vol/copy wizard; Call Number affixes; Instant Detail
authormiker <miker@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Tue, 29 Mar 2011 15:24:44 +0000 (15:24 +0000)
committermiker <miker@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Tue, 29 Mar 2011 15:24:44 +0000 (15:24 +0000)
 * 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

69 files changed:
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Cat.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Cat/AssetCommon.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Holds.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Biblio.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Serial.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/asset.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/biblio.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/biblio.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/SuperCat.pm
Open-ILS/src/perlmods/lib/OpenILS/Const.pm
Open-ILS/src/sql/Pg/002.schema.config.sql
Open-ILS/src/sql/Pg/010.schema.biblio.sql
Open-ILS/src/sql/Pg/040.schema.asset.sql
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/990.schema.unapi.sql
Open-ILS/src/sql/Pg/upgrade/0504.schema.parts_and_cnaffix.sql [new file with mode: 0644]
Open-ILS/web/js/ui/default/acq/common/li_table.js
Open-ILS/web/js/ui/default/conify/global/config/acn_prefix.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/conify/global/config/acn_suffix.js [new file with mode: 0644]
Open-ILS/web/opac/locale/en-US/lang.dtd
Open-ILS/web/opac/locale/en-US/opac.dtd
Open-ILS/web/opac/skin/default/js/copy_details.js
Open-ILS/web/opac/skin/default/js/holds.js
Open-ILS/web/opac/skin/default/js/myopac.js
Open-ILS/web/opac/skin/default/js/rresult.js
Open-ILS/web/opac/skin/default/xml/common/holds.xml
Open-ILS/web/opac/skin/default/xml/myopac/myopac_holds.xml
Open-ILS/web/opac/skin/default/xml/rdetail/rdetail_cn_details.xml
Open-ILS/web/templates/default/conify/global/biblio/monograph_part.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/conify/global/config/acn_prefix.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/conify/global/config/acn_suffix.tt2 [new file with mode: 0644]
Open-ILS/xul/staff_client/chrome/content/OpenILS/data.js
Open-ILS/xul/staff_client/chrome/content/OpenILS/global_util.js
Open-ILS/xul/staff_client/chrome/content/cat/opac.js
Open-ILS/xul/staff_client/chrome/content/cat/opac.xul
Open-ILS/xul/staff_client/chrome/content/main/constants.js
Open-ILS/xul/staff_client/chrome/content/main/menu.js
Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
Open-ILS/xul/staff_client/chrome/content/util/browser.js
Open-ILS/xul/staff_client/chrome/content/util/list.js
Open-ILS/xul/staff_client/chrome/content/util/widgets.js
Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties
Open-ILS/xul/staff_client/server/cat/bib_brief.js
Open-ILS/xul/staff_client/server/cat/bib_brief.xul
Open-ILS/xul/staff_client/server/cat/copy_browser.js
Open-ILS/xul/staff_client/server/cat/copy_browser.xul
Open-ILS/xul/staff_client/server/cat/copy_editor.js
Open-ILS/xul/staff_client/server/cat/copy_editor.xul
Open-ILS/xul/staff_client/server/cat/spine_labels.js
Open-ILS/xul/staff_client/server/cat/util.js
Open-ILS/xul/staff_client/server/cat/volume_copy_creator.js
Open-ILS/xul/staff_client/server/cat/volume_copy_creator.xul
Open-ILS/xul/staff_client/server/cat/volume_copy_editor.js [new file with mode: 0644]
Open-ILS/xul/staff_client/server/cat/volume_copy_editor.xul [new file with mode: 0644]
Open-ILS/xul/staff_client/server/cat/volume_copy_editor_horiz.xul [new file with mode: 0644]
Open-ILS/xul/staff_client/server/cat/volume_editor.js [new file with mode: 0644]
Open-ILS/xul/staff_client/server/cat/volume_editor.xul
Open-ILS/xul/staff_client/server/circ/copy_status.js
Open-ILS/xul/staff_client/server/circ/util.js
Open-ILS/xul/staff_client/server/locale/en-US/cat.properties
Open-ILS/xul/staff_client/server/locale/en-US/circ.properties
Open-ILS/xul/staff_client/server/locale/en-US/common.properties
Open-ILS/xul/staff_client/server/patron/util.js

index 8836291..04b8504 100644 (file)
@@ -1814,14 +1814,57 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
             </actions>
         </permacrud>
        </class>
-    <class id="acnc" controller="open-ils.cstore" oils_obj:fieldmapper="asset::call_number_class" oils_persist:tablename="asset.call_number_class" reporter:label="Call number classification scheme">
+    <class id="acnc" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="asset::call_number_class" oils_persist:tablename="asset.call_number_class" reporter:label="Call number classification scheme">
         <fields oils_persist:primary="id" oils_persist:sequence="asset.call_number_class_id_seq">
             <field reporter:label="Call number class ID" name="id" reporter_datatype="id"/>
             <field reporter:label="Name" name="name" reporter:datatype="text"/>
             <field reporter:label="Normalizer function" name="normalizer" reporter:datatype="text"/>
             <field reporter:label="Call number fields" name="field" reporter:datatype="text"/>
         </fields>
+        <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+            <actions>
+                <retrieve/>
+            </actions>
+        </permacrud>
     </class>
+       <class id="acns" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="asset::call_number_suffix" oils_persist:tablename="asset.call_number_suffix" reporter:label="Call Number/Volume Suffix">
+               <fields oils_persist:primary="id" oils_persist:sequence="asset.call_number_suffix_id_seq">
+                       <field reporter:label="ID" name="id" reporter:datatype="id" />
+                       <field reporter:label="Label" name="label" reporter:datatype="text"/>
+                       <field reporter:label="Label Sort Key" name="label_sortkey" reporter:datatype="text"/>
+                       <field reporter:label="Owning Library" name="owning_lib"  reporter:datatype="org_unit"/>
+               </fields>
+               <links>
+                       <link field="owning_lib" reltype="has_a" key="id" map="" class="aou"/>
+               </links>
+        <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+            <actions>
+                <create permission="CREATE_VOLUME_SUFFIX" context_field="owning_lib"/>
+                <retrieve/>
+                <update permission="UPDATE_VOLUME_SUFFIX" context_field="owning_lib"/>
+                <delete permission="DELETE_VOLUME_SUFFIX" context_field="owning_lib"/>
+            </actions>
+        </permacrud>
+       </class>
+       <class id="acnp" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="asset::call_number_prefix" oils_persist:tablename="asset.call_number_prefix" reporter:label="Call Number/Volume Prefix">
+               <fields oils_persist:primary="id" oils_persist:sequence="asset.call_number_prefix_id_seq">
+                       <field reporter:label="ID" name="id" reporter:datatype="id" />
+                       <field reporter:label="Label" name="label" reporter:datatype="text"/>
+                       <field reporter:label="Label Sort Key" name="label_sortkey" reporter:datatype="text"/>
+                       <field reporter:label="Owning Library" name="owning_lib"  reporter:datatype="org_unit"/>
+               </fields>
+               <links>
+                       <link field="owning_lib" reltype="has_a" key="id" map="" class="aou"/>
+               </links>
+        <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+            <actions>
+                <create permission="CREATE_VOLUME_PREFIX" context_field="owning_lib"/>
+                <retrieve/>
+                <update permission="UPDATE_VOLUME_PREFIX" context_field="owning_lib"/>
+                <delete permission="DELETE_VOLUME_PREFIX" context_field="owning_lib"/>
+            </actions>
+        </permacrud>
+       </class>
        <class id="acn" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="asset::call_number" oils_persist:tablename="asset.call_number" reporter:label="Call Number/Volume">
                <fields oils_persist:primary="id" oils_persist:sequence="asset.call_number_id_seq">
                        <field reporter:label="Copies" name="copies" oils_persist:virtual="true" reporter:datatype="link"/>
@@ -1839,6 +1882,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                        <field reporter:label="URIs" name="uris" oils_persist:virtual="true" reporter:datatype="link"/>
                        <field reporter:label="Sort Key" name="label_sortkey" reporter:datatype="text"/>
                        <field reporter:label="Classification Scheme" name="label_class" reporter:datatype="link"/>
+                       <field reporter:label="Prefix" name="prefix" reporter:datatype="link"/>
+                       <field reporter:label="Suffix" name="suffix" reporter:datatype="link"/>
                </fields>
                <links>
                        <link field="editor" reltype="has_a" key="id" map="" class="au"/>
@@ -1850,6 +1895,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                        <link field="uris" reltype="has_many" key="call_number" map="uri" class="auricnm"/>
                        <link field="uri_maps" reltype="has_many" key="call_number" map="" class="auricnm"/>
                        <link field="label_class" reltype="has_a" key="id" map="" class="acnc"/>
+                       <link field="prefix" reltype="has_a" key="id" map="" class="acnp"/>
+                       <link field="suffix" reltype="has_a" key="id" map="" class="acns"/>
                </links>
         <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
             <actions>
@@ -2027,6 +2074,44 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
             </actions>
         </permacrud>
        </class>
+       <class id="bmp" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="biblio::monograph_part" oils_persist:tablename="biblio.monograph_part" reporter:label="Monograph Parts" oils_persist:field_safe="true">
+               <fields oils_persist:primary="id" oils_persist:sequence="biblio.monograph_part_id_seq">
+                       <field name="id" reporter:datatype="id" />
+                       <field name="record" reporter:datatype="link"/>
+                       <field name="label" reporter:datatype="text"/>
+                       <field name="label_sortkey" reporter:datatype="text"/>
+               </fields>
+               <links>
+                       <link field="record" reltype="has_a" key="id" map="" class="bre"/>
+               </links>
+        <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+            <actions>
+                <create permission="CREATE_MONOGRAPH_PART" global_required="true"/>
+                <retrieve/>
+                <update permission="UPDATE_MONOGRAPH_PART" global_required="true"/>
+                <delete permission="DELETE_MONOGRAPH_PART" global_required="true"/>
+            </actions>
+        </permacrud>
+       </class>
+       <class id="acpm" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="asset::copy_part_map" oils_persist:tablename="asset.copy_part_map" reporter:label="Copy Monograph Part Map">
+               <fields oils_persist:primary="id" oils_persist:sequence="asset.copy_part_map_id_seq">
+                       <field name="id" reporter:datatype="id" />
+                       <field name="target_copy" reporter:datatype="link" />
+                       <field name="part" reporter:datatype="link"/>
+               </fields>
+               <links>
+                       <link field="target_copy" reltype="has_a" key="id" map="" class="acp"/>
+                       <link field="part" reltype="has_a" key="id" map="" class="bmp"/>
+               </links>
+        <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+            <actions>
+                <create permission="MAP_MONOGRAPH_PART" global_required="true"/>
+                <retrieve/>
+                <update permission="MAP_MONOGRAPH_PART" global_required="true"/>
+                <delete permission="MAP_MONOGRAPH_PART" global_required="true"/>
+            </actions>
+        </permacrud>
+       </class>
        <class id="aoucd" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="actor::org_unit::closed_date" oils_persist:tablename="actor.org_unit_closed" reporter:label="Closed Dates">
                <fields oils_persist:primary="id" oils_persist:sequence="actor.org_unit_closed_id_seq">
                        <field name="close_end" reporter:datatype="timestamp" />
@@ -4684,6 +4769,7 @@ SELECT  usr,
                        <field reporter:label="Total Circulations" name="total_circ_count" oils_persist:virtual="true" reporter:datatype="link"/>
                        <field reporter:label="Holds" name="holds" oils_persist:virtual="true" reporter:datatype="link"/>
                        <field reporter:label="Statistical Category Entries" name="stat_cat_entries" oils_persist:virtual="true" reporter:datatype="link"/>
+                       <field reporter:label="Monograph Parts" name="parts" oils_persist:virtual="true" reporter:datatype="link"/>
                </fields>
                <links>
                        <link field="age_protect" reltype="has_a" key="id" map="" class="crahp"/>
@@ -4700,6 +4786,7 @@ SELECT  usr,
                        <link field="circulations" reltype="has_many" key="target_copy" map="" class="circ"/>
                        <link field="total_circ_count" reltype="might_have" key="id" map="" class="erfcc"/>
                        <link field="circ_modifier" reltype="has_a" key="code" map="" class="ccm"/>
+                       <link field="parts" reltype="has_many" key="target_copy" map="part" class="acpm"/>
                </links>
         <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
             <actions>
index ec5c56f..8b29701 100644 (file)
@@ -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 );
 }
 
index b787999..525871d 100644 (file)
@@ -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 };
 }
 
 
index b2dffc5..c24a9f3 100644 (file)
@@ -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);
index 67b2dc1..88af4f5 100644 (file)
@@ -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;
index 74957f1..428b986 100644 (file)
@@ -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']},
index 946677f..226ad44 100644 (file)
@@ -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 /],
                 }
@@ -2227,6 +2227,21 @@ sub fetch_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',
     signature => q/
@@ -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(
index efd3da1..2ee5cba 100644 (file)
@@ -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,
index d004f38..4cec39f 100644 (file)
@@ -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' );
 
index 39eb01b..b464de5 100644 (file)
@@ -21,6 +21,22 @@ __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/ );
 
 #-------------------------------------------------------------------------------
@@ -59,6 +75,14 @@ __PACKAGE__->columns( Essential => qw/call_number barcode creator create_date ed
                                   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/;
 
index 46fefaa..3df2ee5 100644 (file)
@@ -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;
 
index 5ca6fd0..623be5f 100644 (file)
@@ -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;
 
        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;
        
        asset::call_number_class->table( 'asset.call_number_class' );
index 0760fcb..cd0cdb8 100644 (file)
@@ -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
index 5b609be..7c7c830 100644 (file)
@@ -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
index b2419c2..68928a6 100644 (file)
@@ -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 .= '      <prefix ';
+    $xml .= 'ident="' . $self->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 ) .'</prefix>';
+    $xml .= "\n";
+
+    $xml .= '      <suffix ';
+    $xml .= 'ident="' . $self->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 ) .'</suffix>';
+    $xml .= "\n";
+
     $xml .= '      <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
     $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->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 .= "        <monograph_parts>\n";
+       if (ref($self->obj->parts) && $self->obj->parts) {
+               for my $part ( @{$self->obj->parts} ) {
+                       $xml .= sprintf('        <monograph_part record="%s" sortkey="%s">%s</monograph_part>',$part->record, $self->escape($part->label_sortkey), $self->escape($part->label));
+                       $xml .= "\n";
+               }
+       }
+
+       $xml .= "        </monograph_parts>\n";
        $xml .= "        <copy_notes>\n";
        if (ref($self->obj->notes) && $self->obj->notes) {
                for my $note ( @{$self->obj->notes} ) {
index 281f465..6da2ed6 100644 (file)
@@ -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';
index af39c34..72ce6c5 100644 (file)
@@ -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,
index 428b575..cf404f2 100644 (file)
@@ -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;
index 6509bca..dbe0c09 100644 (file)
@@ -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
index bad385a..549d034 100644 (file)
@@ -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'
+);
index e7abed5..f84b3fe 100644 (file)
@@ -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 (file)
index 0000000..5245e37
--- /dev/null
@@ -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;
+
index acb9673..044335e 100644 (file)
@@ -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 (file)
index 0000000..2d307a8
--- /dev/null
@@ -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 (file)
index 0000000..c79cd0e
--- /dev/null
@@ -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);
+
+
index f9fcd6e..e8bb029 100644 (file)
 <!ENTITY staff.cat.opac.copy_browse.accesskey "H">
 <!ENTITY staff.cat.opac.copy_browse.label "Holdings Maintenance">
 <!ENTITY staff.cat.opac.default.label "Set bottom interface as Default">
+<!ENTITY staff.cat.opac.manage_parts.accesskey "P">
+<!ENTITY staff.cat.opac.manage_parts.label "Manage Parts">
 <!ENTITY staff.cat.opac.marc_edit.accesskey "E">
 <!ENTITY staff.cat.opac.marc_edit.label "MARC Edit">
 <!ENTITY staff.cat.opac.marc_view.accesskey "V">
 <!ENTITY staff.main.menu.admin.server_admin.conify.copy_status.label "Copy Statuses">
 <!ENTITY staff.main.menu.admin.server_admin.conify.marc_record_attrs.label "MARC Record Attributes">
 <!ENTITY staff.main.menu.admin.server_admin.conify.coded_value_maps.label "MARC Coded Value Maps">
+<!ENTITY staff.main.menu.admin.server_admin.conify.acn_prefix.label "Call Number Prefixes">
+<!ENTITY staff.main.menu.admin.server_admin.conify.acn_suffix.label "Call Number Suffixes">
 <!ENTITY staff.main.menu.admin.server_admin.conify.billing_type.label "Billing Types">
 <!ENTITY staff.main.menu.admin.server_admin.conify.z3950_source.label "Z39.50 Servers">
 <!ENTITY staff.main.menu.admin.server_admin.conify.circulation_modifier.label "Circulation Modifiers">
 <!ENTITY staff.cat.bib_brief.title.label "Title:">
 <!ENTITY staff.cat.bib_brief.title.accesskey "">
 <!ENTITY staff.cat.bib_brief.view_marc "View MARC">
+<!ENTITY staff.cat.bib_brief.add_volumes "Add Volumes">
 <!ENTITY staff.cat.bib_brief.author.label "Author:">
 <!ENTITY staff.cat.bib_brief.author.accesskey "">
 <!ENTITY staff.cat.bib_brief.edition.label "Edition:">
 <!ENTITY staff.cat.copy_browser.actions.cmd_create_brt.accesskey "K">
 <!ENTITY staff.cat.copy_browser.actions.sel_patron.label "Show Last Few Circulations">
 <!ENTITY staff.cat.copy_browser.actions.sel_patron.accesskey "L">
-<!ENTITY staff.cat.copy_browser.actions.cmd_edit_items.label "Edit Item Attributes">
+<!ENTITY staff.cat.copy_browser.actions.cmd_edit_items.label "Edit Item Attributes / Call Numbers / Replace Barcodes">
 <!ENTITY staff.cat.copy_browser.actions.cmd_edit_items.accesskey "E">
 <!ENTITY staff.cat.copy_browser.actions.cmd_transfer_items.label "Transfer Items to Previously Marked Volume">
 <!ENTITY staff.cat.copy_browser.actions.cmd_transfer_items.accesskey "T">
 <!ENTITY staff.cat.copy_browser.actions.sel_mark_items_missing.accesskey "g">
 <!ENTITY staff.cat.copy_browser.actions.cmd_print_spine_labels.label "Print Item Spine Labels">
 <!ENTITY staff.cat.copy_browser.actions.cmd_print_spine_labels.accesskey "P">
-<!ENTITY staff.cat.copy_browser.actions.cmd_replace_barcode.label "Replace Barcode">
 <!ENTITY staff.cat.copy_browser.actions.save_columns.label "Save Columns">
 <!ENTITY staff.cat.copy_browser.actions.cmd_refresh_list.label "Refresh Listing">
 <!ENTITY staff.cat.copy_browser.actions.cmd_refresh_list.accesskey "R">
 
 <!ENTITY staff.cat.copy_browser.holdings_maintenance.sel_patron.label "Show Last Few Circulations">
 <!ENTITY staff.cat.copy_browser.holdings_maintenance.sel_patron.accesskey "L">
-<!ENTITY staff.cat.copy_browser.holdings_maintenance.cmd_edit_items.label "Edit Item Attributes">
+<!ENTITY staff.cat.copy_browser.holdings_maintenance.cmd_edit_items.label "Edit Item Attributes / Call Numbers / Replace Barcodes">
 <!ENTITY staff.cat.copy_browser.holdings_maintenance.cmd_edit_items.accesskey "E">
 <!ENTITY staff.cat.copy_browser.holdings_maintenance.cmd_transfer_items.label "Transfer Items to Previously Marked Volume">
 <!ENTITY staff.cat.copy_browser.holdings_maintenance.cmd_transfer_items.accesskey "T">
 <!ENTITY staff.cat.volume_copy_creator.print_labels.accesskey "P">
 <!ENTITY staff.cat.volume_copy_creator.library_label.value "Library">
 <!ENTITY staff.cat.volume_copy_creator.num_of_volumes_label.value "# of volumes">
+<!ENTITY staff.cat.volume_copy_creator.batch_bar "BATCH">
+<!ENTITY staff.cat.volume_copy_creator.batch_bar.call_number.classification "Classification:">
+<!ENTITY staff.cat.volume_copy_creator.batch_bar.call_number.prefix "Prefix:">
+<!ENTITY staff.cat.volume_copy_creator.batch_bar.call_number.label.label "Label:">
+<!ENTITY staff.cat.volume_copy_creator.batch_bar.call_number.label.accesskey "L">
+<!ENTITY staff.cat.volume_copy_creator.batch_bar.call_number.suffix "Suffix:">
 <!ENTITY staff.cat.volume_editor.title "Volumes">
 <!ENTITY staff.cat.volume_editor.caption.label "Volume Editor">
 <!ENTITY staff.cat.volume_editor.modify.label "Modify">
 <!ENTITY staff.cat.volume_editor.cancel.accesskey "C">
 <!ENTITY staff.cat.volume_editor.automerge.label "Auto-Merge on Volume Collision">
 <!ENTITY staff.cat.volume_editor.automerge.accesskey "A">
+<!ENTITY staff.cat.volume_editor.owning_lib "Owning lib">
+<!ENTITY staff.cat.volume_editor.classification "Classification">
+<!ENTITY staff.cat.volume_editor.prefix "Prefix">
+<!ENTITY staff.cat.volume_editor.label "Label">
+<!ENTITY staff.cat.volume_editor.suffix "Suffix">
 <!ENTITY staff.cat.z3950.marc_import.label "MARC Import via Z39.50">
 <!ENTITY staff.cat.z3950.marc_import.accesskey "I">
 <!ENTITY staff.cat.z3950.service_credentials.label "Service and Credentials">
index aef4bbb..c1ebf6b 100644 (file)
@@ -202,6 +202,7 @@ avoid using bookbags all together.  Thank you.">
 <!--   ================================================================= 
        MyOPAC Holds Page 
        ================================================================= -->
+
 <!ENTITY myopac.holds.formats "Formats">
 <!ENTITY myopac.holds.location "Pickup Location">
 <!ENTITY myopac.holds.edit "Edit">
@@ -488,6 +489,7 @@ Please see a librarian to renew your account.">
        Rdetail
        ================================================================= -->
 <!ENTITY rdetail.print "print these details">
+<!ENTITY rdetail.cn.part "Part">
 <!ENTITY rdetail.cn.barcode "Barcode">
 <!ENTITY rdetail.cn.location "Location">
 <!ENTITY rdetail.cn.hold.age "Age Hold Protection">
@@ -592,6 +594,7 @@ We recommend that you remove this title from any bookbags it may have been added
 <!ENTITY common.call.number.label "Call Number:">
 <!ENTITY common.isbn.label "ISBN:">
 <!ENTITY common.issn.label "ISSN:">
+<!ENTITY common.mono_parts.label "Monograph Parts:">
 <!ENTITY common.copy.barcode.label "Copy Barcode:">
 <!ENTITY common.issuance_label.label "Issuance Label:">
 <!ENTITY common.hold.place "Place hold for my account">
index e5c50c0..04e8e41 100644 (file)
@@ -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');
index 748f98c..73514c1 100644 (file)
@@ -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());
index 6fd49f9..819f8aa 100644 (file)
@@ -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'));
index ff94bb8..6dccc49 100644 (file)
@@ -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;
 
index 962a366..7d8712d 100644 (file)
                                        <td class='holds_cell' id='holds_physical_desc'> </td>
                                </tr>
 
+                               <tr class='hide_me' id='holds_parts_row'>
+                                       <td class='holds_cell'>&common.mono_parts.label;</td>
+                                       <td class='holds_cell'>
+                        <span class='hide_me' id='holds_parts_label'></span>
+                        <select id='holds_parts_selector' class='hide_me'>
+                            <option value=''></option>
+                        </select>
+                    </td>
+                               </tr>
+
                                <tr class='hide_me' id='holds_cn_row'>
                                        <td class='holds_cell'>&common.call.number.label;</td>
                                        <td class='holds_cell'><b id='holds_cn'/> </td>
index 4d5b46d..08d1690 100644 (file)
@@ -71,8 +71,9 @@
                                <td name='myopac_holds_title'>
                                        <a href='javascript:void(0);' name='myopac_holds_title_link'> </a>
                                        <div name='vol_copy' style='border: 1px solid #808080; width:98%; margin-top: 2px;' class='hide_me'>
-                                               <div style='font-size: 90%' name='volume'/>
-                                               <div style='font-size: 90%' name='copy'/>
+                                               <div style='font-size: 90%' name='part'></div>
+                                               <div style='font-size: 90%' name='volume'></div>
+                                               <div style='font-size: 90%' name='copy'></div>
                                        </div>
                                </td>
 
index 09765fd..45a5c85 100644 (file)
@@ -13,6 +13,7 @@
                                                                <td width='33%'>&rdetail.cn.barcode;</td>
                                                                <td>&common.status;</td>
                                                                <td>&rdetail.cn.location;</td>
+                                                               <td name='copy_part_label' class='hide_me'>&rdetail.cn.part;</td>
                                                                <td name='age_protect_label' class='hide_me'>&rdetail.cn.hold.age;</td>
                                                                <td name='create_date_label' class='hide_me'>&rdetail.cn.genesis;</td>
                                                                <td name='holdable_label' class='hide_me'>&rdetail.cn.holdable;</td>
@@ -35,6 +36,7 @@
 
                                                                <td name='status'> </td>
                                                                <td name='location'> </td>
+                                                               <td name='copy_part' class='hide_me'> </td>
                                                                <td name='age_protect_value' class='hide_me'>&rdetail.cn.disabled;</td>
                                                                <td name='create_date_value' class='hide_me'> </td>
 
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 (file)
index 0000000..ee1256b
--- /dev/null
@@ -0,0 +1,37 @@
+[% WRAPPER default/base.tt2 %]
+[% ctx.page_title = 'Configure Monograph Parts' %]
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+    <div dojoType="dijit.layout.ContentPane" layoutAlign="top" class='oils-header-panel'>
+        <div>Monograph Parts</div>
+        <div>
+            <button dojoType='dijit.form.Button' onClick='monoPartGrid.showCreateDialog()'>New Monograph Part</button>
+            <button dojoType='dijit.form.Button' onClick='monoPartGrid.deleteSelected()'>Delete Selected</button>
+        </div>
+    </div>
+    <div>
+    <table  jsId="monoPartGrid"
+            dojoType="openils.widget.AutoGrid"
+            autoHeight='true'
+            fieldOrder="['label']"
+            suppressFields="['id','record','label_sortkey']"
+            suppressEditFields="['id','label_sortkey']"
+            query="{id: null}"
+            fmClass='bmp'
+            editOnEnter='true'/>
+</div>
+
+<script type="text/javascript">
+    dojo.require('openils.CGI');
+    dojo.require('openils.Util');
+    dojo.require('openils.widget.AutoGrid');
+
+    var cgi = new openils.CGI();
+    openils.Util.addOnLoad( function() {
+        monoPartGrid.overrideEditWidgets.record = new dijit.form.TextBox({"disabled": true});
+        monoPartGrid.overrideEditWidgets.record.shove = { create : cgi.param('r') };
+        monoPartGrid.loadAll({order_by : {bmp : 'label'}}, {record : cgi.param('r')});
+    });
+</script>
+[% 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 (file)
index 0000000..5e7f640
--- /dev/null
@@ -0,0 +1,37 @@
+[% WRAPPER default/base.tt2 %]
+[% ctx.page_title = 'Call Number Prefixes' %]
+<script type="text/javascript" src='[% ctx.media_prefix %]/js/ui/default/conify/global/config/acn_prefix.js'> </script>
+
+<!-- grid -->
+
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+        <div dojoType="dijit.layout.ContentPane" layoutAlign="top" class='oils-header-panel'>
+            <div>Call Number Prefixes</div>
+            <div>
+                <button dojoType='dijit.form.Button' onClick='thingGrid.showCreateDialog()'>New Prefix</button>
+                <button dojoType='dijit.form.Button' onClick='thingGrid.deleteSelected()'>Delete Selected</button>
+            </div>
+        </div>
+        <div>
+            <span>Context Org Unit</span>
+            <select dojoType="openils.widget.OrgUnitFilteringSelect" jsId='thingContextOrgSelect'
+                searchAttr='shortname' labelAttr='shortname'> </select>
+        </div>
+        <table  jsId="thingGrid"
+                dojoType="openils.widget.AutoGrid"
+                fieldOrder="['id', 'label', 'owning_lib']"
+                suppressFields="['label_sortkey']"
+                suppressEditFields="['label_sortkey']"
+                query="{id: '*'}"
+                defaultCellWidth='20'
+                fmClass='acnp'
+                editOnEnter='true'>
+            <thead>
+                <tr><th field='owning_lib' get='getOrgInfo'/></tr>
+            </thead>
+        </table>
+    </div>
+</div>
+[% 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 (file)
index 0000000..40dcbb9
--- /dev/null
@@ -0,0 +1,37 @@
+[% WRAPPER default/base.tt2 %]
+[% ctx.page_title = 'Call Number Suffixes' %]
+<script type="text/javascript" src='[% ctx.media_prefix %]/js/ui/default/conify/global/config/acn_suffix.js'> </script>
+
+<!-- grid -->
+
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+        <div dojoType="dijit.layout.ContentPane" layoutAlign="top" class='oils-header-panel'>
+            <div>Call Number Suffixes</div>
+            <div>
+                <button dojoType='dijit.form.Button' onClick='thingGrid.showCreateDialog()'>New Suffix</button>
+                <button dojoType='dijit.form.Button' onClick='thingGrid.deleteSelected()'>Delete Selected</button>
+            </div>
+        </div>
+        <div>
+            <span>Context Org Unit</span>
+            <select dojoType="openils.widget.OrgUnitFilteringSelect" jsId='thingContextOrgSelect'
+                searchAttr='shortname' labelAttr='shortname'> </select>
+        </div>
+        <table  jsId="thingGrid"
+                dojoType="openils.widget.AutoGrid"
+                fieldOrder="['id', 'label', 'owning_lib']"
+                suppressFields="['label_sortkey']"
+                suppressEditFields="['label_sortkey']"
+                query="{id: '*'}"
+                defaultCellWidth='20'
+                fmClass='acns'
+                editOnEnter='true'>
+            <thead>
+                <tr><th field='owning_lib' get='getOrgInfo'/></tr>
+            </thead>
+        </table>
+    </div>
+</div>
+[% END %]
+
+
index 6db3c02..e66fd4a 100644 (file)
@@ -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(
@@ -598,6 +601,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(
                     'ahrcc',
                     [
                         api.FM_AHRCC_PCRUD_SEARCH.app,
@@ -827,7 +851,6 @@ OpenILS.data.prototype = {
             }
         );
 
-
         this.chain.push(
             function() {
                 var f = gen_fm_retrieval_func(
@@ -852,6 +875,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(
                     'cbt',
                     [
                         api.FM_CBT_RETRIEVE.app,
index 364d573..befb693 100644 (file)
 
     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') {
             }
             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') {
index b5b15ab..7c64f92 100644 (file)
@@ -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);
+    }
+}
+
index 5197d89..9e8549d 100644 (file)
@@ -59,6 +59,7 @@
                 <menuitem label="&staff.cat.copy_browser.holdings_maintenance.cmd_add_volumes.label;" accesskey="&staff.cat.copy_browser.holdings_maintenance.cmd_add_volumes.accesskey;" id="add_volumes" oncommand="add_volumes();"/>
                 <menuitem label="&staff.cat.opac.mark_for_hold_transfer.label;" accesskey="&staff.cat.opac.mark_for_hold_transfer.accesskey;" id="mark_for_hold_transfer" oncommand="mark_for_hold_transfer();"/>
                 <menuitem label="&staff.cat.opac.transfer_title_holds.label;" accesskey="&staff.cat.opac.transfer_title_holds.accesskey;" id="transfer_title_holds" oncommand="transfer_title_holds();"/>
+                <menuitem label="&staff.cat.opac.manage_parts.label;" accesskey="&staff.cat.opac.manage_parts.accesskey;" id="manage_parts" oncommand="manage_parts();"/>
                 <menuseparator/>
                 <menuitem label="&staff.cat.opac.bib_in_new_tab.label;" id="bib_in_new_tab" oncommand="bib_in_new_tab();"/>
                 <menuitem label="&staff.cat.opac.remove_me.label;" id="remove_me" oncommand="remove_me();"/>
index 8e8663e..802592d 100644 (file)
@@ -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',
index 28c83af..9a219a8 100644 (file)
@@ -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
         );
index 0e71b6e..40a23ff 100644 (file)
     <command id="cmd_server_admin_marc_code"/>
     <command id="cmd_server_admin_coded_value_map"/>
     <command id="cmd_server_admin_billing_type"/>
+    <command id="cmd_server_admin_acn_prefix"/>
+    <command id="cmd_server_admin_acn_suffix"/>
     <command id="cmd_server_admin_acq_invoice_item_type"/>
     <command id="cmd_server_admin_acq_invoice_payment_method"/>
     <command id="cmd_server_admin_acq_cancel_reason"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.grp_tree.label;" command="cmd_server_admin_grp_tree"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.perm_list.label;" command="cmd_server_admin_perm_list"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.copy_status.label;" command="cmd_server_admin_copy_status"/>
+                <menuitem label="&staff.main.menu.admin.server_admin.conify.acn_prefix.label;" command="cmd_server_admin_acn_prefix"/>
+                <menuitem label="&staff.main.menu.admin.server_admin.conify.acn_suffix.label;" command="cmd_server_admin_acn_suffix"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.marc_record_attrs.label;" command="cmd_server_admin_marc_code"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.coded_value_maps.label;" command="cmd_server_admin_coded_value_map"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.billing_type.label;" command="cmd_server_admin_billing_type"/>
index 41ff0d2..63b00be 100644 (file)
@@ -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');
index dc1570a..fb46564 100644 (file)
@@ -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)+'"; ';
                     }
index 358519a..2bad0b0 100644 (file)
@@ -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
index 6a125de..7de93f9 100644 (file)
@@ -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.
index b4e283e..db87e5b 100644 (file)
@@ -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);
+    }
+}
index 21c36c8..bce81b7 100644 (file)
@@ -22,7 +22,7 @@ vim: noet:sw=4:ts=4:
 <?xul-overlay href="/xul/server/cat/bib_brief_overlay.xul"?>
 
 <window id="cat_bib_brief_win" 
-    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
+    onload="try { my_init(); font_helper(); persist_helper(); unhide_add_volumes_button(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
@@ -39,7 +39,13 @@ vim: noet:sw=4:ts=4:
     <messagecatalog id="circStrings" src="/xul/server/locale/<!--#echo var='locale'-->/circ.properties"/>
 
     <groupbox id="groupbox" flex="1">
-        <caption id="caption"><label value="&staff.cat.bib_brief.record_summary;"/>(<label value="&staff.cat.bib_brief.view_marc;" class="click_link" onclick="view_marc();"/>)</caption>
+        <caption id="caption">
+            <label value="&staff.cat.bib_brief.record_summary;"/>
+            <label id="add_volumes_left_paren" value="(" hidden="true"/>
+            <label id="add_volumes" value="&staff.cat.bib_brief.add_volumes;" class="click_link" onclick="add_volumes();" hidden="true"/>
+            <label id="add_volumes_right_paren" value=")" hidden="true"/>
+            (<label value="&staff.cat.bib_brief.view_marc;" class="click_link" onclick="view_marc();"/>)
+        </caption>
         <grid id="bib_brief_grid" />
     </groupbox>
 
index e8d1644..e962dfd 100644 (file)
@@ -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');
index 2428043..319ae35 100644 (file)
@@ -86,7 +86,6 @@ vim:noet:sw=4:ts=4:
         <command id="cmd_add_items"/>
         <command id="cmd_add_items_to_buckets"/>
         <command id="cmd_edit_items"/>
-        <command id="cmd_replace_barcode"/>
         <command id="cmd_delete_items"/>
         <command id="cmd_transfer_items"/>
         <command id="cmd_print_spine_labels"/>
@@ -128,7 +127,6 @@ vim:noet:sw=4:ts=4:
             <menuitem command="sel_mark_items_missing" label="&staff.cat.copy_browser.actions.sel_mark_items_missing.label;" accesskey="&staff.cat.copy_browser.actions.sel_mark_items_missing.accesskey;"/>
             <menuseparator/>
             <menuitem command="cmd_print_spine_labels" label="&staff.cat.copy_browser.actions.cmd_print_spine_labels.label;" accesskey="&staff.cat.copy_browser.actions.cmd_print_spine_labels.accesskey;"/>
-            <menuitem command="cmd_replace_barcode" label="&staff.cat.copy_browser.actions.cmd_replace_barcode.label;" accesskey=""/>
             <menuitem command="save_columns" label="&staff.cat.copy_browser.actions.save_columns.label;"/>
             <menuitem command="cmd_refresh_list" label="&staff.cat.copy_browser.actions.cmd_refresh_list.label;" accesskey="&staff.cat.copy_browser.actions.cmd_refresh_list.accesskey;"/>
         </popup>
@@ -142,8 +140,6 @@ vim:noet:sw=4:ts=4:
             <label value="&staff.cat.copy_browser.holdings_maintenance.depth_filter_menu;" />
             <hbox id="x_depth_menu"/>
             <spacer flex="1"/>
-            <label value="&staff.cat.copy_browser.holdings_maintenance.consortial_total;"/><label id="consortial_total"/>
-            <label value="&staff.cat.copy_browser.holdings_maintenance.consortial_available;"/><label id="consortial_available"/>
         </hbox>
         <hbox>
             <checkbox id="show_acns" label="&staff.cat.copy_browser.holdings_maintenance.show_acns;" />
@@ -155,6 +151,8 @@ vim:noet:sw=4:ts=4:
             <button label="Show All Libs" command="cmd_show_all_libs" accesskey=""/>
             -->
             <spacer flex="1"/>
+            <label value="&staff.cat.copy_browser.holdings_maintenance.consortial_total;"/><label id="consortial_total"/>
+            <label value="&staff.cat.copy_browser.holdings_maintenance.consortial_available;"/><label id="consortial_available"/>
             <menubar>
                 <menu label="&staff.cat.copy_browser.holdings_maintenance.actions.label;" accesskey="&staff.cat.copy_browser.holdings_maintenance.actions.accesskey;">
                     <menupopup>
@@ -182,7 +180,6 @@ vim:noet:sw=4:ts=4:
                         <menuitem command="sel_mark_items_missing" label="&staff.cat.copy_browser.holdings_maintenance.sel_mark_items_missing.label;" accesskey="&staff.cat.copy_browser.holdings_maintenance.sel_mark_items_missing.accesskey;"/>
                         <menuseparator/>
                         <menuitem command="cmd_print_spine_labels" label="&staff.cat.copy_browser.holdings_maintenance.cmd_print_spine_labels.label;" accesskey="&staff.cat.copy_browser.holdings_maintenance.cmd_print_spine_labels.accesskey;"/>
-                        <menuitem command="cmd_replace_barcode" label="&staff.cat.copy_browser.holdings_maintenance.cmd_replace_barcode.label;" accesskey=""/>
                         <menuitem command="save_columns" label="&staff.cat.copy_browser.holdings_maintenance.save_columns.label;"/>
                         <menuitem command="cmd_refresh_list" label="&staff.cat.copy_browser.holdings_maintenance.cmd_refresh_list.label;" accesskey="&staff.cat.copy_browser.holdings_maintenance.cmd_refresh_list.accesskey;"/>
                     </menupopup>
index 8680351..7daa37c 100644 (file)
@@ -2,8 +2,6 @@
 var g = {};
 g.map_acn = {};
 
-var xulG = {};
-
 function $(id) { return document.getElementById(id); }
 
 function my_init() {
@@ -24,6 +22,10 @@ function my_init() {
         JSAN.use('OpenILS.data'); g.data = new OpenILS.data(); g.data.init({'via':'stash'});
         JSAN.use('util.network'); g.network = new util.network();
 
+        if (xulG.unified_interface) {
+            $('non_unified_buttons').hidden = true;
+        }
+
         g.docid = xul_param('docid',{'modal_xulG':true});
         g.handle_update = xul_param('handle_update',{'modal_xulG':true});
 
@@ -63,44 +65,50 @@ function my_init() {
 
         if (xul_param('edit',{'modal_xulG':true}) == '1') { 
 
-            // Editor desired, but let's check permissions
-            g.edit = false;
+            g.edit = true;
 
-            try {
-                var check = g.network.simple_request(
-                    'PERM_MULTI_ORG_CHECK',
-                    [ 
-                        ses(), 
-                        g.data.list.au[0].id(), 
-                        util.functional.map_list(
-                            g.copies,
-                            function (o) {
-                                var lib;
-                                var cn_id = o.call_number();
-                                if (cn_id == -1) {
-                                    lib = o.circ_lib(); // base perms on circ_lib instead of owning_lib if pre-cat
-                                } else {
-                                    if (! g.map_acn[ cn_id ]) {
-                                        var req = g.network.simple_request('FM_ACN_RETRIEVE.authoritative',[ cn_id ]);
-                                        if (typeof req.ilsevent == 'undefined') {
-                                            g.map_acn[ cn_id ] = req;
-                                            lib = g.map_acn[ cn_id ].owning_lib();
+            if (g.copies.length > 0) { // When loaded in the unified interface, there may be no copies yet (from the volum/item creator) 
+
+                // Editor desired, but let's check permissions
+                g.edit = false;
+
+                try {
+                    var check = g.network.simple_request(
+                        'PERM_MULTI_ORG_CHECK',
+                        [ 
+                            ses(), 
+                            g.data.list.au[0].id(), 
+                            util.functional.map_list(
+                                g.copies,
+                                function (o) {
+                                    var lib;
+                                    var cn_id = o.call_number();
+                                    if (cn_id == -1) {
+                                        lib = o.circ_lib(); // base perms on circ_lib instead of owning_lib if pre-cat
+                                    } else {
+                                        if (! g.map_acn[ cn_id ]) {
+                                            var req = g.network.simple_request('FM_ACN_RETRIEVE.authoritative',[ cn_id ]);
+                                            if (typeof req.ilsevent == 'undefined') {
+                                                g.map_acn[ cn_id ] = req;
+                                                lib = g.map_acn[ cn_id ].owning_lib();
+                                            } else {
+                                                lib = o.circ_lib();
+                                            }
                                         } else {
-                                            lib = o.circ_lib();
+                                            lib = g.map_acn[ cn_id ].owning_lib();
                                         }
-                                    } else {
-                                        lib = g.map_acn[ cn_id ].owning_lib();
                                     }
+                                    return typeof lib == 'object' ? lib.id() : lib;
                                 }
-                                return typeof lib == 'object' ? lib.id() : lib;
-                            }
-                        ),
-                        g.copies.length == 1 ? [ 'UPDATE_COPY' ] : [ 'UPDATE_COPY', 'UPDATE_BATCH_COPY' ]
-                    ]
-                );
-                g.edit = check.length == 0;
-            } catch(E) {
-                g.error.standard_unexpected_error_alert('batch permission check',E);
+                            ),
+                            g.copies.length == 1 ? [ 'UPDATE_COPY' ] : [ 'UPDATE_COPY', 'UPDATE_BATCH_COPY' ]
+                        ]
+                    );
+                    g.edit = check.length == 0;
+                } catch(E) {
+                    g.error.standard_unexpected_error_alert('batch permission check',E);
+                }
+
             }
 
             if (g.edit) {
@@ -110,6 +118,7 @@ function my_init() {
             } else {
                 $('top_nav').setAttribute('hidden','true');
             }
+
         } else {
             $('top_nav').setAttribute('hidden','true');
         }
@@ -162,6 +171,29 @@ function my_init() {
         g.render();
         g.check_for_unmet_required_fields();
 
+        if (xulG.unified_interface) {
+            xulG.refresh_copy_editor = function() {
+                try {
+                    g.copies = xulG.copies;
+                    g.original_copies = js2JSON( g.copies );
+                    for (var i = 0; i < g.applied_templates.length; i++) {
+                        g._apply_template( g.applied_templates[i] );
+                    }
+                    g.summarize( g.copies );
+                    g.render();
+                    g.check_for_unmet_required_fields();
+                } catch(E) {
+                    alert('Error in copy_editor.js, xulG.refresh_copy_editor(): ' + E);
+                }
+            };
+            xulG.unlock_copy_editor = function() {
+                oils_unlock_page();
+            };
+            xulG.notify_of_templatable_field_change = function(id,v) {
+                g.changed[ 'volume_copy_creator.'+id ] = { 'type' : 'volume_copy_creator', 'field' : id, 'value' : v };
+            }
+        }
+
     } catch(E) {
         var err_msg = $("commonStrings").getFormattedString('common.exception', ['cat/copy_editor.js', E]);
         try { g.error.sdump('D_ERROR',err_msg); } catch(E) { dump(err_msg); dump(js2JSON(E)); }
@@ -191,6 +223,25 @@ g.retrieve_templates = function() {
             function() { g.copy_editor_prefs[ 'template_menu' ] = { 'value' : g.template_menu.value }; g.save_attributes(); },
             false
         );
+
+        if (xulG.unified_interface) {
+            if (typeof xulG.update_unified_template_list == 'function') {
+                xulG.update_unified_template_list(list);
+                // functions the unified wrapper should use to let the item attribute editor do the heavy lifting for templates
+                xulG.update_item_editor_template_selection = function(new_value) {
+                    g.template_menu.value = new_value;
+                    g.copy_editor_prefs[ 'template_menu' ] = { 'value' : g.template_menu.value };
+                    g.save_attributes();
+                }
+                xulG.item_editor_apply_template = function() { g.apply_template(); };
+                xulG.item_editor_delete_template = function() { g.delete_template(); };
+                xulG.item_editor_save_template = function() { g.save_template(); };
+                xulG.item_editor_import_templates = function() { g.import_templates(); };
+                xulG.item_editor_export_templates = function() { g.export_templates(); };
+                xulG.item_editor_reset = function() { g.reset(); };
+            }
+        }
+
     } catch(E) {
         g.error.standard_unexpected_error_alert($('catStrings').getString('staff.cat.copy_editor.retrieve_templates.error'), E);
     }
@@ -199,10 +250,26 @@ g.retrieve_templates = function() {
 /******************************************************************************************************/
 /* Apply Template */
 
+g.applied_templates = [];
+
 g.apply_template = function() {
     try {
         var name = g.template_menu.value;
         if (g.templates[ name ] != 'undefined') {
+            g.applied_templates.push( name );
+            g._apply_template(name);
+            g.summarize( g.copies );
+            g.render();
+            g.check_for_unmet_required_fields();
+        }
+    } catch(E) {
+        g.error.standard_unexpected_error_alert($('catStrings').getString('staff.cat.copy_editor.apply_templates.error'), E);
+    }
+}
+
+g._apply_template = function(name) {
+    try {
+        if (g.templates[ name ] != 'undefined') {
             var template = g.templates[ name ];
             for (var i in template) {
                 g.changed[ i ] = template[ i ];
@@ -216,14 +283,16 @@ g.apply_template = function() {
                     case 'owning_lib' :
                         g.apply_owning_lib(template[i].value);
                     break;
+                    case 'volume_copy_creator' :
+                        if (xulG.unified_interface) {
+                            xulG.apply_template_to_batch(template[i].field,template[i].value);
+                        }
+                    break;
                 }
             }
-            g.summarize( g.copies );
-            g.render();
-            g.check_for_unmet_required_fields();
         }
     } catch(E) {
-        g.error.standard_unexpected_error_alert($('catStrings').getString('staff.cat.copy_editor.apply_templates.error'), E);
+        alert('Error in copy_editor.js, g._apply_template('+name+'): ' + E);
     }
 }
 
@@ -383,12 +452,16 @@ g.import_templates = function() {
 /* Restore backup copies */
 
 g.reset = function() {
+    g.applied_templates = [];
     g.changed = {};
     g.copies = JSON2js( g.original_copies );
     g.summarize( g.copies );
     g.render();
     g.check_for_unmet_required_fields();
     oils_unlock_page();
+    if (xulG.unified_interface) {
+        xulG.reset_batch_menus();
+    }
 }
 
 /******************************************************************************************************/
@@ -459,6 +532,8 @@ g.apply_stat_cat = function(sc_id,entry_id) {
 
 g.apply_owning_lib = function(ou_id) {
     g.error.sdump('D_TRACE','ou_id = ' + ou_id + '\n');
+    // but don't allow this when bundled with the volume/copy creator UI, or if we're editing pre-cats
+    if (! g.safe_to_change_owning_lib() ) { return; }
     for (var i = 0; i < g.copies.length; i++) {
         var copy = g.copies[i];
         try {
@@ -471,15 +546,15 @@ g.apply_owning_lib = function(ou_id) {
                 g.map_acn[copy.call_number()] = volume;
             }
             var old_volume = g.map_acn[copy.call_number()];
-            var acn_id = g.network.simple_request(
+            var acn_blob = g.network.simple_request(
                 'FM_ACN_FIND_OR_CREATE',
-                [ses(),old_volume.label(),old_volume.record(),ou_id]
+                [ses(),old_volume.label(),old_volume.record(),ou_id,old_volume.prefix(),old_volume.suffix(),old_volume.label_class()]
             );
-            if (typeof acn_id.ilsevent != 'undefined') {
-                g.error.standard_unexpected_error_alert($('catStrings').getFormattedString('staff.cat.copy_editor.apply_owning_lib.call_number.error', [copy.barcode()]), acn_id);
+            if (typeof acn_blob.ilsevent != 'undefined') {
+                g.error.standard_unexpected_error_alert($('catStrings').getFormattedString('staff.cat.copy_editor.apply_owning_lib.call_number.error', [copy.barcode()]), acn_blob);
                 continue;
             }
-            copy.call_number(acn_id);
+            copy.call_number(acn_blob.acn_id);
             copy.ischanged('1');
         } catch(E) {
             g.error.standard_unexpected_error_alert('apply_stat_cat',E);
@@ -490,10 +565,11 @@ g.apply_owning_lib = function(ou_id) {
 }
 
 /******************************************************************************************************/
-/* This returns true if none of the copies being edited are pre-cats */
+/* This returns false if any of the copies being edited are pre-cats, or if we're embedded in the unified volume/copy UI */
 
 g.safe_to_change_owning_lib = function() {
     try {
+        if (xulG.unified_interface) { return false; }
         var safe = true;
         for (var i = 0; i < g.copies.length; i++) {
             var cn = g.copies[i].call_number();
index a456da4..5105ec1 100644 (file)
@@ -20,7 +20,6 @@
 
 <window id="cat_copy_editor_win" 
     onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
-    width="800" height="580" oils_persist="width height"
     title="&staff.cat.copy_editor.window.label;"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
@@ -42,7 +41,7 @@
         <caption id="caption" label="&staff.cat.copy_editor.groupbox1.label;"/>
 
         <hbox id="top_nav">
-            <hbox style="background: grey">
+            <hbox id="template_bar" style="background: grey" flex="1">
                 <vbox><spacer flex="1"/><label value="&staff.cat.copy_editor.templates.label;" style="font-weight: bold"/><spacer flex="1"/></vbox>
                 <hbox id="template_placeholder"/>
                 <button id="apply_template" label="&staff.cat.copy_editor.templates.apply_template.label;" accesskey="&staff.cat.copy_editor.templates.apply_template.accesskey;" oncommand="g.apply_template()"/>
@@ -50,9 +49,9 @@
                 <button id="import_templates" label="&staff.cat.copy_editor.templates.import_template.label;" oncommand="g.import_templates()"/>
                 <button id="export_templates" label="&staff.cat.copy_editor.templates.export_template.label;" oncommand="g.export_templates()"/>
                 <button id="save_template" label="&staff.cat.copy_editor.templates.save_template.label;" oncommand="g.save_template()"/>
+                <spacer flex="1"/>
+                <button label="&staff.cat.copy_editor.templates.reset.label;" accesskey="&staff.cat.copy_editor.templates.reset.accesskey;" oncommand="g.reset()"/>
             </hbox>
-            <spacer flex="1"/>
-            <button label="&staff.cat.copy_editor.templates.reset.label;" accesskey="&staff.cat.copy_editor.templates.reset.accesskey;" oncommand="g.reset()"/>
         </hbox>
 
         <hbox flex="1" style="overflow: scroll">
         <hbox id="nav">
             <spacer flex="1"/>
             <button id="copy_notes" label="&staff.cat.copy_editor.copy_notes.label;" accesskey="&staff.cat.copy_editor.copy_notes.accesskey;" oncommand="g.copy_notes();"/>
-            <button id="save" label="&staff.cat.copy_editor.save.label;" hidden="true" accesskey="&staff.cat.copy_editor.save.accesskey;" oncommand="g.stash_and_close();"/>
-            <button id="cancel" label="&staff.cat.copy_editor.cancel.label;" accesskey="&staff.cat.copy_editor.cancel.accesskey;" oncommand="JSAN.use('util.widgets'); util.widgets.dispatch('close',window);"/>
+            <hbox id="non_unified_buttons">
+                <button id="save" label="&staff.cat.copy_editor.save.label;" hidden="true" accesskey="&staff.cat.copy_editor.save.accesskey;" oncommand="g.stash_and_close();"/>
+                <button id="cancel" label="&staff.cat.copy_editor.cancel.label;" accesskey="&staff.cat.copy_editor.cancel.accesskey;" oncommand="JSAN.use('util.widgets'); util.widgets.dispatch('close',window);"/>
+            </hbox>
         </hbox>
 
         <spacer/>
index 35d5d28..2db3a6e 100644 (file)
                         var record = g.network.simple_request('MODS_SLIM_RECORD_RETRIEVE.authoritative', [ volume.record() ]);
                         volume.record( record );
 
-                        /* Jam the prefixes and suffixes into the volume object */
-                        volume.prefix = label_prefix;
-                        volume.suffix = label_suffix;
+                        /* The volume object has native prefix and suffixes now, so affix the ones coming from copy locations */
+                        var temp_prefix = label_prefix + ' ' + (typeof volume.prefix() == 'object' ? volume.prefix().label() : volume.prefix());
+                        var temp_suffix = (typeof volume.suffix() == 'object' ? volume.suffix().label() : volume.suffix()) + ' ' + label_suffix;
+
+                        /* And assume that leading and trailing spaces can be trimmed */
+                        temp_prefix = temp_prefix.replace(/\s+$/,'').replace(/^\s+/,'');
+                        temp_suffix = temp_suffix.replace(/\s+$/,'').replace(/^\s+/,'');
+
+                        volume.prefix( temp_prefix );
+                        volume.suffix( temp_suffix );
 
                         g.volumes[ volume.id() ] = volume;
                     }
 
             /* Only add the prefixes and suffixes once */
             if (!override || volume.id() != override.acn) {
-                if (volume.prefix) {
-                    callnum = volume.prefix + ' ' + callnum;
+                if (volume.prefix()) {
+                    callnum = volume.prefix() + ' ' + callnum;
                 }
-                if (volume.suffix) {
-                    callnum += ' ' + volume.suffix;
+                if (volume.suffix()) {
+                    callnum += ' ' + volume.suffix();
                 }
             }
 
index 5e70b3d..818917c 100644 (file)
@@ -522,13 +522,13 @@ cat.util.fast_item_add = function(doc_id,cn_label,cp_barcode) {
         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($("catStrings").getFormattedString('staff.cat.volume_copy_creator.stash_and_close.problem_with_volume', [cn]), acn_id);
+        if (typeof acn_blob.ilsevent != 'undefined') {
+            error.standard_unexpected_error_alert($("catStrings").getFormattedString('staff.cat.volume_copy_creator.stash_and_close.problem_with_volume', [cn]), acn_blob);
             return;
         }
 
@@ -536,7 +536,7 @@ cat.util.fast_item_add = function(doc_id,cn_label,cp_barcode) {
         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);
index e82366a..9bc22bd 100644 (file)
@@ -1,5 +1,16 @@
 const g_max_copies_that_can_be_added_at_a_time_per_volume = 999;
+const rel_vert_pos_volume_count = 1;
+const rel_vert_pos_call_number_classification = 2;
+const rel_vert_pos_call_number_prefix = 3;
+const rel_vert_pos_call_number = 4;
+const rel_vert_pos_call_number_suffix = 5;
+const rel_vert_pos_copy_count = 6;
+const rel_vert_pos_barcode = 7;
+const rel_vert_pos_part = 8;
+const update_timer = 1000;
 var g = {};
+g.use_defaults = true;
+g.acn_map = {}; // store retrieved acn objects here by id
 
 function my_init() {
     try {
@@ -15,12 +26,38 @@ function my_init() {
         g.error.sdump('D_TRACE','my_init() for cat/volume_copy_creator.xul');
 
         JSAN.use('OpenILS.data'); g.data = new OpenILS.data(); g.data.init({'via':'stash'});
-        JSAN.use('util.widgets'); JSAN.use('util.functional');
+        JSAN.use('util.widgets'); JSAN.use('util.functional'); JSAN.use('util.fm_utils');
 
         JSAN.use('util.network'); g.network = new util.network();
 
         g.refresh = xul_param('onrefresh');
 
+        if (xulG.unified_interface) {
+            $('non_unified_buttons').hidden = true;
+            xulG.reset_batch_menus = function() {
+                $('batch_class_menulist').value = false;
+                util.widgets.dispatch('command',$('batch_class_menulist'));
+                $('batch_prefix_menulist').value = false;
+                util.widgets.dispatch('command',$('batch_prefix_menulist'));
+                $('batch_suffix_menulist').value = false;
+                util.widgets.dispatch('command',$('batch_suffix_menulist'));
+            }
+            xulG.apply_template_to_batch = function(id,value) {
+                if (!isNaN(Number(value))) {
+                    $(id).value = value;
+                    util.widgets.dispatch('command',$(id));
+                }
+                setTimeout(
+                    function() {
+                        // TODO:  Only apply batch to columns that haven't been adjusted manually?
+                        util.widgets.dispatch('command',$('batch_button'));
+                    },0
+                );
+            }
+        } else {
+            $('Create').hidden = true;
+        }
+
         /***********************************************************************************************************/
         /* Am I adding just copies or copies and volumes?  Or am I rebarcoding existing copies? */
 
@@ -43,11 +80,15 @@ function my_init() {
             set_attr('EditThenCreate','accesskey','staff.cat.volume_copy_creator.edit_then_rebarcode.btn.accesskey');
             set_attr('CreateWithDefaults','label','staff.cat.volume_copy_creator.rebarcode.btn.label');
             set_attr('CreateWithDefaults','accesskey','staff.cat.volume_copy_creator.rebarcode.btn.accesskey');
+            set_attr('Create','label','staff.cat.volume_copy_creator.rebarcode.btn.label');
+            set_attr('Create','accesskey','staff.cat.volume_copy_creator.rebarcode.btn.accesskey');
         } else {
             set_attr('EditThenCreate','label','staff.cat.volume_copy_creator.edit_then_create.btn.label');
             set_attr('EditThenCreate','accesskey','staff.cat.volume_copy_creator.edit_then_create.btn.accesskey');
             set_attr('CreateWithDefaults','label','staff.cat.volume_copy_creator.create_with_defaults.btn.label');
             set_attr('CreateWithDefaults','accesskey','staff.cat.volume_copy_creator.create_with_defaults.btn.accesskey');
+            set_attr('Create','label','staff.cat.volume_copy_creator.create.btn.label');
+            set_attr('Create','accesskey','staff.cat.volume_copy_creator.create.btn.accesskey');
         }
 
         //g.error.sdump('D_ERROR','location.href = ' + location.href + '\n\ncopy_short cut = ' + g.copy_shortcut + '\n\nou_ids = ' + xul_param('ou_ids'));
@@ -56,18 +97,19 @@ function my_init() {
 
         // Get the default callnumber classification scheme from OU settings
         dojo.require('fieldmapper.OrgUtils');
-        var label_class = g.data.hash.aous['cat.default_classification_scheme']; //fieldmapper.aou.fetchOrgSettingDefault(ses('ws_ou'), 'cat.default_classification_scheme');
+        //fieldmapper.aou.fetchOrgSettingDefault(ses('ws_ou'), 'cat.default_classification_scheme');
+        g.label_class = g.data.hash.aous['cat.default_classification_scheme'];
 
-        // Assign a default value if none was returned 
-        if (!label_class) {
-            label_class = 1;
+        // Assign a default value if none was returned
+        if (!g.label_class) {
+            g.label_class = g.data.list.acnc[0].id();
         }
 
         /***********************************************************************************************************/
-        /* If we're passed existing_copies, rig up a copy_shortcut object to leverage existing code for rendering the volume labels, etc. 
-         * Also make a lookup object for existing copies keyed on org id and callnumber label, and another keyed on copy id. */
+        /* If we're passed existing_copies, rig up a copy_shortcut object to leverage existing code for rendering the volume labels, etc.
+         * Also make a lookup object for existing copies keyed on org id and callnumber composite key, and another keyed on copy id. */
 
-        // g.org_label_existing_copy_map = { ou_id : { callnumber_label : [ copy1, copy2, ... ] }, ... }
+        // g.org_label_existing_copy_map = { ou_id : { callnumber_composite_key : [ copy1, copy2, ... ] }, ... }
         g.org_label_existing_copy_map = {};
         // g.id_copy_map = { acp_id : acp, ... }
         g.id_copy_map = {};
@@ -82,11 +124,17 @@ function my_init() {
                 g.copy_shortcut[ call_number.owning_lib() ] = {};
                 g.org_label_existing_copy_map[ call_number.owning_lib() ] = {};
             }
-            g.copy_shortcut[ call_number.owning_lib() ][ call_number.label() ] = call_number.id();
-            if (! g.org_label_existing_copy_map[ call_number.owning_lib() ][ call_number.label() ]) {
-                g.org_label_existing_copy_map[ call_number.owning_lib() ][ call_number.label() ] = [];
+            var acnc_id = call_number.label_class() ?
+                ( typeof call_number.label_class() == 'object' ? call_number.label_class().id() : call_number.label_class() )
+                : g.label_class;
+            var acnp_id = typeof call_number.prefix() == 'object' ? call_number.prefix().id() : call_number.prefix();
+            var acns_id = typeof call_number.suffix() == 'object' ? call_number.suffix().id() : call_number.suffix();
+            var callnumber_composite_key = acnc_id + ':' + acnp_id + ':' + call_number.label() + ':' + acns_id;
+            g.copy_shortcut[ call_number.owning_lib() ][ callnumber_composite_key ] = call_number.id();
+            if (! g.org_label_existing_copy_map[ call_number.owning_lib() ][ callnumber_composite_key ]) {
+                g.org_label_existing_copy_map[ call_number.owning_lib() ][ callnumber_composite_key ] = [];
             }
-            g.org_label_existing_copy_map[ call_number.owning_lib() ][ call_number.label() ].push( copy );
+            g.org_label_existing_copy_map[ call_number.owning_lib() ][ callnumber_composite_key ].push( copy );
         }
 
         /***********************************************************************************************************/
@@ -104,18 +152,26 @@ function my_init() {
         get_contentWindow(summary).xulG = { 'docid' : g.doc_id };
 
         /***********************************************************************************************************/
-        /* For the call number drop down */
+        /* Setup pcrud and fetch the monographic parts for this bib */
 
-        if (g.existing_copies.length > 0 || !g.copy_shortcut) {
-            g.list_callnumbers(g.doc_id, label_class);
-        }
+        dojo.require('openils.PermaCrud');
+        g.pcrud = new openils.PermaCrud({'authtoken':ses()});
+        g.parts = g.pcrud.search('bmp',{'record':g.doc_id},{'order_by': { 'bmp' : 'label_sortkey' } });
+        g.parts_hash = util.functional.convert_object_list_to_hash( g.parts );
+
+        /***********************************************************************************************************/
+        /* For the batch drop downs */
+
+        g.list_classes();
+        g.list_callnumbers(g.doc_id, g.label_class);
+        g.render_batch_button();
 
         /***********************************************************************************************************/
         /* render the orgs and volumes/input */
 
         var rows = document.getElementById('rows');
 
-        var node_id = 0;
+        g.ou_ids = [];
         for (var i = 0; i < ou_ids.length; i++) {
             try {
                 var org = g.data.hash.aou[ ou_ids[i] ];
@@ -123,14 +179,34 @@ function my_init() {
                     var row = document.createElement('row'); rows.appendChild(row); row.setAttribute('ou_id',ou_ids[i]);
                     g.render_library_label(row,ou_ids[i]);
                     g.render_volume_count_entry( row, ou_ids[i] );
+                    g.ou_ids.push( ou_ids[i] );
                 }
             } catch(E) {
                 g.error.sdump('D_ERROR',E);
             }
         }
+        g.common_ancestor_ou_ids = util.fm_utils.find_common_aou_ancestors( g.ou_ids ).reverse();
+
+        /***********************************************************************************************************/
+        /* For the remainder batch drop downs */
+
+        g.list_prefixes();
+        g.list_suffixes();
+
+        /************/
 
         g.load_prefs();
 
+        if (g.existing_copies.length > 0) {
+            g.gather_copies_soon();
+        }
+
+        try {
+            $('main').parentNode.scrollLeft = 9999;
+        } catch(E) {
+            dump('Error in volume_copy_creator.js, my_init(), trying to auto-scroll to the far right: ' + E + '\n');
+        }
+
     } catch(E) {
         var err_msg = $("commonStrings").getFormattedString('common.exception', ['cat/volume_copy_creator.js', E]);
         try { g.error.sdump('D_ERROR',err_msg); } catch(E) { dump(err_msg); dump(js2JSON(E)); }
@@ -147,8 +223,12 @@ g.render_library_label = function(row,ou_id) {
 g.render_volume_count_entry = function(row,ou_id) {
     var hb = document.createElement('vbox'); row.appendChild(hb);
     var tb = document.createElement('textbox'); hb.appendChild(tb);
+    if (g.use_defaults) {
+        tb.value = 1; // default to 1 volume per org
+        tb.select();
+    }
     tb.setAttribute('ou_id',ou_id); tb.setAttribute('size','3'); tb.setAttribute('cols','3');
-    tb.setAttribute('rel_vert_pos','1'); 
+    tb.setAttribute('rel_vert_pos',rel_vert_pos_volume_count);
     if ( (!g.copy_shortcut) && (!g.last_focus) ) { tb.focus(); g.last_focus = tb; }
     var node;
     function render_copy_count_entry(ev) {
@@ -161,15 +241,16 @@ g.render_volume_count_entry = function(row,ou_id) {
                 return;
             }
             if (node) { row.removeChild(node); node = null; }
-            //ev.target.disabled = true;
             node = g.render_callnumber_copy_count_entry(row,ou_id,ev.target.value);
         }
     }
-    util.widgets.apply_vertical_tab_on_enter_handler( 
-        tb, 
-        function() { render_copy_count_entry({'target':tb}); setTimeout(function(){util.widgets.vertical_tab(tb);},0); }
+    util.widgets.apply_vertical_tab_on_enter_handler(
+        tb,
+        function() { render_copy_count_entry({'target':tb}); setTimeout(function(){util.widgets.vertical_tab(tb);},0); },
+        g.delay_gather_copies_soon
     );
     tb.addEventListener( 'change', render_copy_count_entry, false);
+    tb.addEventListener( 'change', g.gather_copies_soon, false);
     tb.addEventListener( 'focus', function(ev) { g.last_focus = ev.target; }, false );
     setTimeout(
         function() {
@@ -184,6 +265,14 @@ g.render_volume_count_entry = function(row,ou_id) {
                     ).length;
                     render_copy_count_entry({'target':tb});
                     tb.disabled = true;
+                } else if (tb.value) {
+                    // since we're now supplying a default
+                    render_copy_count_entry({'target':tb});
+                    setTimeout(
+                        function() {
+                            util.widgets.vertical_tab(tb);
+                        }, 0
+                    );
                 }
             } catch(E) {
                 alert(E);
@@ -199,14 +288,35 @@ g.render_callnumber_copy_count_entry = function(row,ou_id,count) {
     var rows = grid.lastChild;
     var r = document.createElement('row'); rows.appendChild( r );
     var x = document.createElement('label'); r.appendChild(x);
-    x.setAttribute('value', $("catStrings").getString('staff.cat.volume_copy_creator.render_callnumber_copy_count_entry.call_nums')); x.setAttribute('style','font-weight: bold');
+        x.setAttribute('value', $("catStrings").getString('staff.cat.volume_copy_creator.render_callnumber_copy_count_entry.classification'));
+        x.setAttribute('style','font-weight: bold');
     x = document.createElement('label'); r.appendChild(x);
-    x.setAttribute('value',$("catStrings").getString('staff.cat.volume_copy_creator.render_callnumber_copy_count_entry.num_of_copies')); x.setAttribute('style','font-weight: bold');
-    x.setAttribute('size','3'); x.setAttribute('cols','3');
-
-    function handle_change(call_number_column_textbox,number_of_copies_column_textbox,barcode_column_box) {
-        if (call_number_column_textbox.value == '') return;
-        if (isNaN( Number( number_of_copies_column_textbox.value ) )) return;
+        x.setAttribute('value', $("catStrings").getString('staff.cat.volume_copy_creator.render_callnumber_copy_count_entry.prefix'));
+        x.setAttribute('style','font-weight: bold');
+    x = document.createElement('label'); r.appendChild(x);
+        x.setAttribute('value', $("catStrings").getString('staff.cat.volume_copy_creator.render_callnumber_copy_count_entry.call_nums'));
+        x.setAttribute('style','font-weight: bold');
+    x = document.createElement('label'); r.appendChild(x);
+        x.setAttribute('value', $("catStrings").getString('staff.cat.volume_copy_creator.render_callnumber_copy_count_entry.suffix'));
+        x.setAttribute('style','font-weight: bold');
+    x = document.createElement('label'); r.appendChild(x);
+        x.setAttribute('value',$("catStrings").getString('staff.cat.volume_copy_creator.render_callnumber_copy_count_entry.num_of_copies'));
+        x.setAttribute('style','font-weight: bold');
+    x = document.createElement('label'); r.appendChild(x);
+        x.setAttribute('value',$("catStrings").getString('staff.cat.volume_copy_creator.render_callnumber_copy_count_entry.barcodes_and_parts'));
+        x.setAttribute('style','font-weight: bold');
+
+    function handle_change_precipitating_barcode_rendering(
+        callnumber_composite_key,
+        number_of_copies_column_textbox,
+        barcode_column_box
+    ) {
+        dump('handle_change_precipitating_barcode_rendering\n');
+
+        if (isNaN( Number( number_of_copies_column_textbox.value ) )) {
+            dump('1:handle_change_precipitating_barcode_rendering early return\n');
+            return;
+        }
         if ( Number( number_of_copies_column_textbox.value ) > g_max_copies_that_can_be_added_at_a_time_per_volume ) {
             g.error.yns_alert($("catStrings").getFormattedString('staff.cat.volume_copy_creator.render_volume_count_entry.message', [g_max_copies_that_can_be_added_at_a_time_per_volume]),
                 $("catStrings").getString('staff.cat.volume_copy_creator.render_volume_count_entry.title'),
@@ -219,59 +329,177 @@ g.render_callnumber_copy_count_entry = function(row,ou_id,count) {
         }
         g.render_barcode_entry(
             barcode_column_box,
-            call_number_column_textbox.value,
+            callnumber_composite_key,
             Number(number_of_copies_column_textbox.value),
             ou_id
         );
 
-        document.getElementById("EditThenCreate").disabled = false;
-        document.getElementById("CreateWithDefaults").disabled = false;
+        if (! xulG.unified_interface) {
+            document.getElementById("EditThenCreate").disabled = false;
+            document.getElementById("CreateWithDefaults").disabled = false;
+        } else {
+            document.getElementById("Create").disabled = false;
+        }
     }
 
-    function handle_change_call_number_column_textbox(ev) {
-        var _call_number_column_textbox = ev.target;    
+    function handle_change_to_callnumber_data(ev) {
+        dump('handle_change_to_callnumber_data\n');
+        var _call_number_column_textbox = ev.target;
         var _call_number_column_box = _call_number_column_textbox.parentNode;
-        var _number_of_copies_column_box = _call_number_column_box.nextSibling;
+
+        var _classification_column_box = _call_number_column_box.previousSibling.previousSibling; /* two over to the left */
+        var _classification_column_menulist = _classification_column_box.firstChild;
+
+        var _prefix_column_box = _call_number_column_box.previousSibling; /* one over to the left */
+        var _prefix_column_menulist = _prefix_column_box.firstChild;
+
+        var _suffix_column_box = _call_number_column_box.nextSibling; /* one over to the right */
+        var _suffix_column_menulist = _suffix_column_box.firstChild;
+
+        var _number_of_copies_column_box = _call_number_column_box.nextSibling.nextSibling; /* two over to the right */
         var _number_of_copies_column_textbox = _number_of_copies_column_box.firstChild;
+
         var _barcode_column_box = _number_of_copies_column_box.nextSibling;
-        handle_change(_call_number_column_textbox,_number_of_copies_column_textbox,_barcode_column_box);
+
+        var acn_label = _call_number_column_textbox.value;
+        var acnc_id = _classification_column_menulist.value;
+        var acnp_id = _prefix_column_menulist.value;
+        var acns_id = _suffix_column_menulist.value;
+        var callnumber_composite_key = acnc_id + ':' + acnp_id + ':' + acn_label + ':' + acns_id;
+        dump('\tcomposite_key = ' + callnumber_composite_key + '\n');
+
+        _call_number_column_textbox.setAttribute('callkey',callnumber_composite_key);
+        _call_number_column_textbox.setAttribute('acnc_id',acnc_id);
+        _call_number_column_textbox.setAttribute('acnp_id',acnp_id);
+        _call_number_column_textbox.setAttribute('acns_id',acns_id);
+
+        handle_change_precipitating_barcode_rendering(
+            callnumber_composite_key,
+            _number_of_copies_column_textbox,
+            _barcode_column_box
+        );
     }
 
     function handle_change_number_of_copies_column_textbox(ev) {
-        var _number_of_copies_column_textbox = ev.target;    
+        dump('handle_change_number_of_copies_column_textbox\n');
+        var _number_of_copies_column_textbox = ev.target;
         var _number_of_copies_column_box = _number_of_copies_column_textbox.parentNode;
-        var _call_number_column_box = _number_of_copies_column_box.previousSibling;
+        var _call_number_column_box = _number_of_copies_column_box.previousSibling.previousSibling; /* two over */
         var _call_number_column_textbox = _call_number_column_box.firstChild;
-        var _barcode_column_box = _number_of_copies_column_box.nextSibling;
-        handle_change(_call_number_column_textbox,_number_of_copies_column_textbox,_barcode_column_box);
+        handle_change_to_callnumber_data({'target':_call_number_column_textbox}); // let this guy do the work
     }
 
     for (var i = 0; i < count; i++) {
         var r = document.createElement('row'); rows.appendChild(r);
-        var call_number_column_box = document.createElement('vbox'); r.appendChild(call_number_column_box);
-        var number_of_copies_column_box = document.createElement('vbox'); r.appendChild(number_of_copies_column_box);
-        var barcode_column_box = document.createElement('vbox'); r.appendChild(barcode_column_box);
-        var call_number_column_textbox = document.createElement('textbox'); call_number_column_box.appendChild(call_number_column_textbox);
-        call_number_column_textbox.setAttribute('rel_vert_pos','2');
-        call_number_column_textbox.setAttribute('ou_id',ou_id);
-        util.widgets.apply_vertical_tab_on_enter_handler( 
-            call_number_column_textbox, 
-            function() { handle_change_call_number_column_textbox({'target':call_number_column_textbox}); setTimeout(function(){util.widgets.vertical_tab(call_number_column_textbox);},0); }
-        );
-        var number_of_copies_column_textbox = document.createElement('textbox'); number_of_copies_column_box.appendChild(number_of_copies_column_textbox);
-        number_of_copies_column_textbox.setAttribute('size','3'); number_of_copies_column_textbox.setAttribute('cols','3');
-        number_of_copies_column_textbox.setAttribute('rel_vert_pos','3');
-        number_of_copies_column_textbox.setAttribute('ou_id',ou_id);
-        util.widgets.apply_vertical_tab_on_enter_handler( 
-            number_of_copies_column_textbox, 
-            function() { handle_change_number_of_copies_column_textbox({'target':number_of_copies_column_textbox}); setTimeout(function(){util.widgets.vertical_tab(number_of_copies_column_textbox);},0); }
-        );
 
-        call_number_column_textbox.addEventListener( 'change', handle_change_call_number_column_textbox, false);
-        call_number_column_textbox.addEventListener( 'focus', function(ev) { g.last_focus = ev.target; }, false );
-        number_of_copies_column_textbox.addEventListener( 'change', handle_change_number_of_copies_column_textbox, false);
-        number_of_copies_column_textbox.addEventListener( 'focus', function(ev) { g.last_focus = ev.target; }, false );
-        if ( !g.last_focus ) { number_of_copies_column_textbox.focus(); g.last_focus = number_of_copies_column_textbox; }
+            /**** CLASSIFICATION COLUMN ****/
+            var classification_column_box = document.createElement('vbox');
+            r.appendChild(classification_column_box);
+
+            /**** PREFIX COLUMN ****/
+            var prefix_column_box = document.createElement('vbox');
+            r.appendChild(prefix_column_box);
+
+            /**** CALLNUMBER COLUMN ****/
+            var call_number_column_box = document.createElement('vbox');
+            r.appendChild(call_number_column_box);
+                var call_number_column_textbox = document.createElement('textbox');
+                call_number_column_box.appendChild(call_number_column_textbox);
+                    if (g.use_defaults && $('marc_cn').firstChild) {
+                        // default to first real value from batch callnumber menu
+                        var menupopup = $('marc_cn').firstChild.firstChild;
+                        if (menupopup.childNodes.length > 1) {
+                            call_number_column_textbox.value = menupopup.childNodes[1].getAttribute('label');
+                            call_number_column_textbox.select();
+                        }
+                    }
+                    call_number_column_textbox.setAttribute('rel_vert_pos',rel_vert_pos_call_number);
+                    call_number_column_textbox.setAttribute('ou_id',ou_id);
+                    util.widgets.apply_vertical_tab_on_enter_handler(
+                        call_number_column_textbox,
+                        function() {
+                            handle_change_to_callnumber_data({'target':call_number_column_textbox});
+                            setTimeout(
+                                function(){
+                                    util.widgets.vertical_tab(call_number_column_textbox);
+                                },0
+                            );
+                        },
+                        g.delay_gather_copies_soon
+                    );
+                    call_number_column_textbox.addEventListener( 'change', handle_change_to_callnumber_data, false);
+                    call_number_column_textbox.addEventListener( 'change', g.gather_copies_soon, false);
+                    call_number_column_textbox.addEventListener( 'focus', function(ev) { g.last_focus = ev.target; }, false );
+
+                    /**** CLASSIFICATION COLUMN revisited ****/
+                    var classification_column_menulist = g.render_class_menu(call_number_column_textbox);
+                    classification_column_menulist.addEventListener(
+                        'command',
+                        function() {
+                            handle_change_to_callnumber_data({'target':call_number_column_textbox});
+                        }
+                        ,false
+                    );
+                    classification_column_box.appendChild(classification_column_menulist);
+
+                    /**** PREFIX COLUMN revisited ****/
+                    var prefix_column_menulist = g.render_prefix_menu(call_number_column_textbox);
+                    prefix_column_menulist.addEventListener(
+                        'command',
+                        function() {
+                            handle_change_to_callnumber_data({'target':call_number_column_textbox});
+                        }
+                        ,false
+                    );
+
+                    prefix_column_box.appendChild(prefix_column_menulist);
+
+            /**** SUFFIX COLUMN ****/
+            var suffix_column_box = document.createElement('vbox');
+            r.appendChild(suffix_column_box);
+                var suffix_column_menulist = g.render_suffix_menu(call_number_column_textbox);
+                suffix_column_menulist.addEventListener(
+                    'command',
+                    function() {
+                        handle_change_to_callnumber_data({'target':call_number_column_textbox});
+                    }
+                    ,false
+                );
+                suffix_column_box.appendChild(suffix_column_menulist);
+
+            /**** NUMBER OF COPIES COLUMN ****/
+            var number_of_copies_column_box = document.createElement('vbox');
+            r.appendChild(number_of_copies_column_box);
+                var number_of_copies_column_textbox = document.createElement('textbox');
+                number_of_copies_column_box.appendChild(number_of_copies_column_textbox);
+                    if (g.use_defaults) {
+                        // default to one copy per call number
+                        number_of_copies_column_textbox.value = 1;
+                        number_of_copies_column_textbox.select();
+                    }
+                    number_of_copies_column_textbox.setAttribute('size','3'); number_of_copies_column_textbox.setAttribute('cols','3');
+                    number_of_copies_column_textbox.setAttribute('rel_vert_pos',rel_vert_pos_copy_count);
+                    number_of_copies_column_textbox.setAttribute('ou_id',ou_id);
+                    util.widgets.apply_vertical_tab_on_enter_handler(
+                        number_of_copies_column_textbox,
+                        function() {
+                            handle_change_number_of_copies_column_textbox({'target':number_of_copies_column_textbox});
+                            setTimeout(
+                                function(){
+                                    util.widgets.vertical_tab(number_of_copies_column_textbox);
+                                },0
+                            );
+                        },
+                        g.delay_gather_copies_soon
+                    );
+                    number_of_copies_column_textbox.addEventListener( 'change', handle_change_number_of_copies_column_textbox, false);
+                    number_of_copies_column_textbox.addEventListener( 'change', g.gather_copies_soon, false);
+                    number_of_copies_column_textbox.addEventListener( 'focus', function(ev) { g.last_focus = ev.target; }, false );
+                    if ( !g.last_focus ) { number_of_copies_column_textbox.focus(); g.last_focus = number_of_copies_column_textbox; }
+
+            /**** BARCODE COLUMN ****/
+            var barcode_column_box = document.createElement('vbox');
+            r.appendChild(barcode_column_box);
 
         setTimeout(
             function(idx,call_number_column_textbox,number_of_copies_column_textbox){
@@ -279,23 +507,36 @@ g.render_callnumber_copy_count_entry = function(row,ou_id,count) {
                     try {
                         JSAN.use('util.functional');
                         if (g.copy_shortcut) {
-                            var label = util.functional.map_object_to_list(
+                            var callnumber_composite_key = util.functional.map_object_to_list(
                                 g.copy_shortcut[ou_id],
                                 function(o,i) {
                                     return i;
                                 }
                             )[idx];
                             if (g.org_label_existing_copy_map[ou_id]) {
-                                var num_of_copies = g.org_label_existing_copy_map[ou_id][label].length;
+                                var num_of_copies = g.org_label_existing_copy_map[ou_id][callnumber_composite_key].length;
                                 if (num_of_copies>0) {
                                     number_of_copies_column_textbox.value = num_of_copies;
                                     number_of_copies_column_textbox.disabled = true;
                                 }
                             }
-                            call_number_column_textbox.value = label; 
-                            handle_change_call_number_column_textbox({'target':call_number_column_textbox});
-                            if (g.existing_copies.length < 1) {
-                                call_number_column_textbox.disabled = true;
+                            var acn_label = callnumber_composite_key.split(/:/).slice(2,-1).join(':');
+                            var acnc_id = callnumber_composite_key.split(/:/)[0];
+                            var acnp_id = callnumber_composite_key.split(/:/)[1];
+                            var acns_id = callnumber_composite_key.split(/:/).slice(-1)[0];
+                            call_number_column_textbox.value = acn_label;
+                            classification_column_menulist.value = acnc_id;
+                            prefix_column_menulist.value = acnp_id;
+                            suffix_column_menulist.value = acns_id;
+                            handle_change_to_callnumber_data({'target':call_number_column_textbox});
+                        } else {
+
+                            // if we're providing defaults, keep on rendering
+                            if (call_number_column_textbox.value) {
+                                util.widgets.dispatch('change',call_number_column_textbox);
+                            }
+                            if (number_of_copies_column_textbox.value) {
+                                util.widgets.dispatch('change',number_of_copies_column_textbox);
                             }
                         }
                     } catch(E) {
@@ -309,36 +550,150 @@ g.render_callnumber_copy_count_entry = function(row,ou_id,count) {
     return grid;
 }
 
-g.render_barcode_entry = function(node,callnumber,count,ou_id) {
+g.render_part_menu = function(barcode_tb) {
+    var hbox = document.createElement('hbox');
+    var menulist = document.createElement('menulist');
+        menulist.setAttribute('editable','true');
+        hbox.appendChild(menulist);
+    var button = document.createElement('button');
+        button.setAttribute('label',$('catStrings').getString('staff.cat.volume_copy_creator.create_part.btn.label'));
+        button.hidden = true;
+        hbox.appendChild(button);
+
+    var menupopup = document.createElement('menupopup');
+        menulist.appendChild(menupopup);
+        g.render_part_menuitems(menupopup);
+
+    button.addEventListener(
+        'command',
+        function(ev) {
+            var new_part = new bmp();
+                new_part.isnew(1);
+                new_part.label(menulist.value);
+                new_part.record(g.doc_id);
+            g.pcrud.create(new_part, {
+                "oncomplete": function (r, objs) {
+                    var db_part = objs[0];
+                    if (!db_part) { return; }
+                    g.parts.push( db_part );
+                    g.parts_hash[ db_part.id() ] = db_part;
+                    g.render_part_menuitems(menupopup);
+                    if (menulist.selectedItem) {
+                        barcode_tb.setAttribute('bmp_id',menulist.selectedItem.value);
+                        button.hidden = true;
+                    }
+                    g.gather_copies_soon();
+                }
+            });
+        },
+        false
+    );
+
+    menulist.addEventListener(
+        'change',
+        function(ev) {
+            if (! ev.target.selectedItem) {
+                button.hidden = false;
+            }
+        },
+        false
+    );
+    menulist.addEventListener('change',g.gather_copies_soon,false);
+    menulist.addEventListener(
+        'command',
+        function(ev) {
+            barcode_tb.setAttribute('bmp_id',menulist.selectedItem.value);
+            button.hidden = true;
+        },
+        false
+    );
+    menulist.addEventListener('command',g.gather_copies_soon,false);
+
+    return hbox;
+}
+
+g.render_part_menuitems = function(menupopup) {
+    util.widgets.remove_children(menupopup);
+    var menuitem = document.createElement('menuitem');
+    menuitem.setAttribute('label','');
+    menuitem.setAttribute('value','');
+    menupopup.appendChild(menuitem);
+    for (var i = 0; i < g.parts.length; i++) {
+        var menuitem = document.createElement('menuitem');
+        menuitem.setAttribute('label',g.parts[i].label());
+        menuitem.setAttribute('value',g.parts[i].id());
+        menupopup.appendChild(menuitem);
+    }
+
+}
+
+g.render_barcode_entry = function(node,callnumber_composite_key,count,ou_id) {
     try {
+        dump('g.render_barcode_entry(node,'+callnumber_composite_key+','+count+','+ou_id+'\n');
         function ready_to_create(ev) {
-            document.getElementById("EditThenCreate").disabled = false;
-            document.getElementById("CreateWithDefaults").disabled = false;
+            if (! xulG.unified_interface) {
+                document.getElementById("EditThenCreate").disabled = false;
+                document.getElementById("CreateWithDefaults").disabled = false;
+            } else {
+                document.getElementById("Create").disabled = false;
+            }
         }
 
-        JSAN.use('util.barcode'); 
+        JSAN.use('util.barcode');
 
         for (var i = 0; i < count; i++) {
-            var tb; var set_handlers = false;
+            var tb_part_box;
+            var tb;
+            var part_menu;
+            var set_handlers = false;
             if (typeof node.childNodes[i] == 'undefined') {
-                tb = document.createElement('textbox'); node.appendChild(tb);
+                tb_part_box = document.createElement('hbox');
+                node.appendChild(tb_part_box);
+                tb = document.createElement('textbox');
+                tb_part_box.appendChild(tb);
+                part_menu = g.render_part_menu(tb);
+                tb_part_box.appendChild(part_menu);
                 set_handlers = true;
             } else {
-                tb = node.childNodes[i];
+                tb_part_box = node.childNodes[i];
+                tb = tb_part_box.firstChild;
+                part_menu = tb_part_box.lastChild;
             }
             tb.setAttribute('ou_id',ou_id);
-            tb.setAttribute('callnumber',callnumber);
-            tb.setAttribute('rel_vert_pos','4');
+            tb.setAttribute('callkey',callnumber_composite_key);
+            tb.setAttribute('rel_vert_pos',rel_vert_pos_barcode);
+            part_menu.firstChild.setAttribute('rel_vert_pos',rel_vert_pos_part);
             if (!tb.value && g.org_label_existing_copy_map[ ou_id ]) {
-                tb.value = g.org_label_existing_copy_map[ ou_id ][ callnumber ][i].barcode();
-                tb.setAttribute('acp_id', g.org_label_existing_copy_map[ ou_id ][ callnumber ][i].id());
+                tb.value = g.org_label_existing_copy_map[ ou_id ][ callnumber_composite_key ][i].barcode();
+                tb.setAttribute('acp_id', g.org_label_existing_copy_map[ ou_id ][ callnumber_composite_key ][i].id());
+                var temp_parts = g.org_label_existing_copy_map[ ou_id ][ callnumber_composite_key ][i].parts();
+                temp_parts = util.functional.filter_list(
+                    temp_parts,
+                    function(p) {
+                        return p.record() == g.doc_id; // filter out foreign parts
+                    }
+                );
+                if (temp_parts.length > 0) {
+                    tb.setAttribute('bmp_id',temp_parts[0].id());
+                    part_menu.firstChild.value = g.parts_hash[ temp_parts[0].id() ].label();
+                }
                 tb.select();
                 if (! g.first_focus) { g.first_focus = tb; }
             }
+            if (g.use_defaults && ! g.first_focus) {
+                g.first_focus = tb;
+                tb.focus();
+            }
             if (set_handlers) {
-                util.widgets.apply_vertical_tab_on_enter_handler( 
-                    tb, 
-                    function() { ready_to_create({'target':tb}); setTimeout(function(){util.widgets.vertical_tab(tb);},0); }
+                util.widgets.apply_vertical_tab_on_enter_handler(
+                    tb,
+                    function() { ready_to_create({'target':tb}); setTimeout(function(){util.widgets.vertical_tab(tb);},0); },
+                    g.delay_gather_copies_soon
+                );
+                util.widgets.apply_vertical_tab_on_enter_handler(
+                    part_menu.firstChild,
+                    function() { setTimeout(function(){util.widgets.vertical_tab(part_menu.firstChild);},0); },
+                    g.delay_gather_copies_soon
                 );
                 tb.addEventListener('change', function(ev) {
                     var barcode = String( ev.target.value ).replace(/\s/g,'');
@@ -351,11 +706,13 @@ g.render_barcode_entry = function(node,callnumber,count,ou_id) {
                         setTimeout( function() { ev.target.select(); ev.target.focus(); }, 0);
                     }
                 }, false);
+                tb.addEventListener('change', g.gather_copies_soon, false);
                 tb.addEventListener( 'focus', function(ev) { g.last_focus = ev.target; }, false );
             }
         }
-        
-        setTimeout( function() { if (g.first_focus) { g.first_focus.focus(); } }, 0 ); 
+
+        g.gather_copies_soon();
+        setTimeout( function() { if (g.first_focus) { g.first_focus.focus(); } }, 0 );
 
     } catch(E) {
         g.error.sdump('D_ERROR','g.render_barcode_entry: ' + E);
@@ -364,7 +721,7 @@ g.render_barcode_entry = function(node,callnumber,count,ou_id) {
 
 g.generate_barcodes = function() {
     try {
-        var nodes = document.getElementsByAttribute('rel_vert_pos','4');
+        var nodes = document.getElementsByAttribute('rel_vert_pos',rel_vert_pos_barcode);
         if (nodes.length < 1) { return; }
         var first_barcode = nodes[0].value;
 
@@ -387,50 +744,167 @@ g.generate_barcodes = function() {
         for (var i = 0; i < barcodes.length; i++) {
             nodes[i+1].value = barcodes[i];
             nodes[i+1].select();
+            util.widgets.dispatch('change',nodes[i+1]);
         }
 
+        setTimeout(
+            function() {
+                g.gather_copies_soon();
+            },0
+        );
+
     } catch(E) {
         g.error.sdump('D_ERROR','g.generate_barcodes: ' + E);
     }
 }
 
-g.new_node_id = -1;
-
-g.stash_and_close = function(param) {
+g.delay_gather_copies_soon = function() {
+    if (xulG.unified_interface) {
+        dump('g.delay_gather_copies_soon()\n');
+        g.gather_copies_soon();
+    }
+}
 
+g.gather_copies_soon = function() {
     try {
+        if (!xulG.unified_interface) { return; }
+        dump('g.gather_copies_soon()\n');
+        document.getElementById("Create").disabled = true;
+        if (g.update_copy_editor_timeoutID) {
+            clearTimeout(g.update_copy_editor_timeoutID);
+        }
+        // This function is expensive when it comes to keeping the UI responsive, so let's give it a delay
+        // that quick entry of consecutive fields can override
+        g.update_copy_editor_timeoutID = setTimeout(
+            function() {
+                try {
+                    g.gather_copies();
+                    xulG.refresh_copy_editor();
+                    document.getElementById("Create").disabled = false;
+                } catch(E) {
+                    alert('Error in volume_copy_editor.js with g.gather_copies_soon setTimeout func(): ' + E);
+                }
+            }, update_timer
+        );
+    } catch(E) {
+        alert('Error in volume_copy_creator.js, g.gather_copies_soon(): ' + E);
+    }
+}
 
+g.new_acp_id = -1;
+g.new_acn_id = -1;
+
+g.gather_copies = function() {
+    dump('g.gather_copies()\n');
+    try {
         var nl = document.getElementsByTagName('textbox');
 
-        var volumes_hash = {};
+        g.volumes_scaffold = {};
+        /*
+            g.volumes_scaffold = {
+                '#ou_id' : {
+                    '#class_id:#prefix_id:#callnumber label:#suffix_id' : {
+                        'callnumber_data' : {
+                            'acn_id' : '#callnumber id',
+                            'acn_label' : '#callnumber label',
+                            'acnc_id' : '#classification_id',
+                            'acnp_id' : '#prefix_id',
+                            'acns_id' : '#suffix_id'
+                        },
+                        'barcode_data' :
+                            [
+                                {
+                                    'barcode' : '#barcode',
+                                    'acp_id' : '#copy_id',
+                                    'bmp_id' : '#part_id'
+                                }, ...
+                            ]
+                    }
+                }, ...
+            }
+        */
 
         var barcodes = [];
-        
+        var v_count = 0;
         for (var i = 0; i < nl.length; i++) {
-            if ( nl[i].getAttribute('rel_vert_pos') == 4 ) barcodes.push( nl[i] );
-            if ( nl[i].getAttribute('rel_vert_pos') == 2 )  {
+            if ( nl[i].getAttribute('rel_vert_pos') == rel_vert_pos_barcode ) barcodes.push( nl[i] );
+            if ( nl[i].getAttribute('rel_vert_pos') == rel_vert_pos_call_number )  {
+                v_count++;
                 var ou_id = nl[i].getAttribute('ou_id');
+                var acn_id = nl[i].getAttribute('acn_id');
+                if (!acn_id) {
+                    acn_id = g.new_acn_id--;
+                    nl[i].setAttribute('acn_id',acn_id);
+                }
+                var acnc_id = nl[i].getAttribute('acnc_id') || g.label_class;
+                var acnp_id = nl[i].getAttribute('acnp_id') || -1;
+                var acns_id = nl[i].getAttribute('acns_id') || -1;
                 var callnumber = nl[i].value;
-                if (typeof volumes_hash[ou_id] == 'undefined') { volumes_hash[ou_id] = {} }
-                if (typeof volumes_hash[ou_id][callnumber] == 'undefined') { volumes_hash[ou_id][callnumber] = [] }
+                if (typeof g.volumes_scaffold[ou_id] == 'undefined') {
+                    g.volumes_scaffold[ou_id] = {}
+                }
+                var composite_key = acnc_id + ':' + acnp_id + ':' + callnumber + ':' + acns_id;
+                if (typeof g.volumes_scaffold[ou_id][composite_key] == 'undefined') {
+                    g.volumes_scaffold[ou_id][composite_key] = {
+                        //'node' : nl[i],
+                        'callnumber_data' : {
+                            'acn_id' : acn_id,
+                            'acn_label' : callnumber,
+                            'acnc_id' : acnc_id,
+                            'acnp_id' : acnp_id,
+                            'acns_id' : acns_id
+                        },
+                        'barcode_data' : []
+                    }
+                    dump('fleshing volumes scaffold with ou_id = ' + ou_id + ' composite_key = ' + composite_key + ' acn_id = ' + acn_id + '\n');
+                }
             }
         };
-    
+        dump('volume_copy_creator: processed ' + nl.length + ' textbox nodes, consisting of ' + barcodes.length + ' barcodes and ' + v_count + 'volumes\n');
+        dump('volume scaffold = ' + js2JSON(g.volumes_scaffold) + '\n');
+
         for (var i = 0; i < barcodes.length; i++) {
-            var acp_id = barcodes[i].getAttribute('acp_id') || g.new_node_id--;
+            var acp_id = barcodes[i].getAttribute('acp_id') || g.new_acp_id--;
             var ou_id = barcodes[i].getAttribute('ou_id');
-            var callnumber = barcodes[i].getAttribute('callnumber');
+            var callnumber_composite_key = barcodes[i].getAttribute('callkey');
             var barcode = barcodes[i].value;
+            var bmp_id = barcodes[i].getAttribute('bmp_id');
 
-            if (typeof volumes_hash[ou_id] == 'undefined') { volumes_hash[ou_id] = {} }
-            if (typeof volumes_hash[ou_id][callnumber] == 'undefined') { volumes_hash[ou_id][callnumber] = [] }
+            dump('placing ' + barcode + ' for ou = ' + ou_id + ' into composite_key bin ' + callnumber_composite_key + '\n');
 
-            if (barcode != '') volumes_hash[ou_id][callnumber].push( { 'barcode' : barcode, 'acp_id' : acp_id } );
+            if (typeof g.volumes_scaffold[ou_id] == 'undefined') {
+                dump('1: I want to remove this soon, so alert me if it is getting used, ou_id = ' + ou_id + '\n');
+                g.volumes_scaffold[ou_id] = {}
+            }
+            if (typeof g.volumes_scaffold[ou_id][callnumber_composite_key] == 'undefined') {
+                dump('2: when does this happen, and why? ou_id = ' + ou_id + ' callnumber_composite_key = ' + callnumber_composite_key + '\n');
+                // one way this can happen, race condition between this function and editing a widget
+                g.volumes_scaffold[ou_id][callnumber_composite_key] = {
+                    'callnumber_data' : {
+                        // not ideal, but hey...
+                        'acn_label' : callnumber_composite_key.split(/:/).slice(2,-1).join(':'),
+                        'acnc_id' : callnumber_composite_key.split(/:/)[0],
+                        'acnp_id' : callnumber_composite_key.split(/:/)[1],
+                        'acns_id' : callnumber_composite_key.split(/:/).slice(-1)[0]
+                    },
+                    'barcode_data' : []
+                }
+            }
+
+            if (barcode != '') {
+                g.volumes_scaffold[ou_id][callnumber_composite_key].barcode_data.push(
+                    {
+                        'barcode' : barcode,
+                        'acp_id' : acp_id,
+                        'bmp_id' : bmp_id
+                    }
+                );
+            }
         }
 
         var volumes = [];
         var copies = [];
-        var volume_labels = {};
+        var volume_data = {};
 
         function new_copy(acp_id,ou_id,acn_id,barcode) {
             var copy = new acp();
@@ -454,43 +928,163 @@ g.stash_and_close = function(param) {
             return copy;
         }
 
-        for (var ou_id in volumes_hash) {
-            for (var cn_label in volumes_hash[ou_id]) {
+        for (var ou_id in g.volumes_scaffold) {
+            for (var composite_key in g.volumes_scaffold[ou_id]) {
+                for (var i = 0; i < g.volumes_scaffold[ou_id][composite_key].barcode_data.length; i++) {
+                    var barcode = g.volumes_scaffold[ou_id][composite_key].barcode_data[i].barcode;
+                    var acp_id = g.volumes_scaffold[ou_id][composite_key].barcode_data[i].acp_id;
+                    var bmp_id = g.volumes_scaffold[ou_id][composite_key].barcode_data[i].bmp_id;
+                    var acn_id = g.volumes_scaffold[ou_id][composite_key].callnumber_data.acn_id;
+                    dump('gather_copies(): barcode = ' + barcode + ' acp_id = ' + acp_id + ' bmp_id = ' + bmp_id + ' acn_id = ' + acn_id + ' composite_key = ' + composite_key + '\n');
+                    var copy = g.id_copy_map[ acp_id ];
+                    if (!copy) {
+                        copy = new_copy(acp_id,ou_id,acn_id,barcode);
+                        g.id_copy_map[ acp_id ] = copy;
+                    } else {
+                        copy.ischanged( get_db_true() );
+                    }
+                    copy.barcode( barcode );
+                    copy.call_number( acn_id );
+                    var temp_parts = util.functional.filter_list(
+                        copy.parts() || [],
+                        function(p) {
+                            return (p.record() != g.doc_id); // filter out parts for this bib
+                        }
+                    );
+                    if (bmp_id) {
+                        temp_parts.push( g.parts_hash[ bmp_id ] );
+                    }
+                    copy.parts( temp_parts );
+                    copies.push( copy );
+                }
+            }
+        }
+
+        xulG.copies = copies;
+        return copies;
 
-                var acn_id = g.network.simple_request(
-                    'FM_ACN_FIND_OR_CREATE',
-                    [ ses(), cn_label, g.doc_id, ou_id ]
-                );
+    } catch(E) {
+        alert('Error in volume_copy_creator.js, g.gather_copies():' + E);
+    }
+}
 
-                if (typeof acn_id.ilsevent != 'undefined') {
-                    g.error.standard_unexpected_error_alert($("catStrings").getFormattedString('staff.cat.volume_copy_creator.stash_and_close.problem_with_volume', [cn]), acn_id);
-                    continue;
-                }
+g.vivicate_update_volumes = function() {
+    try {
+        var volumes = [];
+        for (var ou_id in g.volumes_scaffold) {
+            for (var composite_key in g.volumes_scaffold[ou_id]) {
 
-                volume_labels[ acn_id ] = { 'label' : cn_label, 'owning_lib' : ou_id };
+                var callnumber_data = g.volumes_scaffold[ou_id][composite_key].callnumber_data;
+                var acn_id = callnumber_data.acn_id;
+                var acnp_id = callnumber_data.acnp_id;
+                var acns_id = callnumber_data.acns_id;
+                var acnc_id = callnumber_data.acnc_id;
 
-                for (var i = 0; i < volumes_hash[ou_id][cn_label].length; i++) {
-                    var barcode = volumes_hash[ou_id][cn_label][i].barcode;
-                    var acp_id = volumes_hash[ou_id][cn_label][i].acp_id;
-                    var copy;
-                    if (acp_id < 0) {
-                        copy = new_copy(acp_id,ou_id,acn_id,barcode);
-                    } else {
-                        copy = g.id_copy_map[ acp_id ];
-                        copy.barcode( barcode );
-                        copy.call_number( acn_id );
-                        copy.ischanged('1');
+                if (acn_id < 0) {
+
+                    var acn_blob = g.network.simple_request(
+                        'FM_ACN_FIND_OR_CREATE',
+                        [ ses(), callnumber_data.acn_label, g.doc_id, ou_id, acnp_id, acns_id, acnc_id ]
+                    );
+                    dump('FM_ACN_FIND_OR_CREATE: label = ' + callnumber_data.acn_label
+                        + ' doc = ' + g.doc_id + ' ou = ' + ou_id + ' acnp = ' + acnp_id + ' acns = ' + acns_id + ' acnc = ' + acnc_id + '\n');
+
+                    if (typeof acn_blob.ilsevent != 'undefined') {
+                        alert('Error in g.vivicate_update_volumes, acn_id = ' + acn_id + ' acn_blob = ' + js2JSON(acn_blob));
+                        continue;
                     }
-                    copies.push( copy );
+
+                    acn_id = acn_blob.acn_id;
+
+                    if (typeof g.acn_map[ acn_id ] == 'undefined') {
+                        var temp_acn = g.network.simple_request(
+                            'FM_ACN_RETRIEVE.authoritative',
+                            [ acn_id ]
+                        );
+                        if (typeof temp_acn.ilsevent != 'undefined') {
+                            alert('Error in g.vivicate_update_volumes, acn_id = ' + acn_id + ' temp_acn = ' + js2JSON(temp_acn));
+                            continue;
+                        }
+                        g.acn_map[ acn_id ] = temp_acn;
+                        if (callnumber_data.acn_id < 0) {
+                            g.acn_map[ callnumber_data.acn_id ] = temp_acn;
+                        }
+                    }
+
+                }
+/*
+                var my_acn = g.acn_map[ acn_id ];
+
+                var node = g.volumes_scaffold[ou_id][composite_key].node;
+                var class_menulist = node.parentNode.previousSibling.previousSibling.firstChild;
+                var prefix_menulist = node.parentNode.previousSibling.firstChild;
+                var suffix_menulist = node.parentNode.nextSibling.firstChild;
+
+                if ( String(class_menulist.value) != String(my_acn.label_class()) {
+                    my_acn.label_class( class_menulist.value );
+                    my_acn.ischanged( get_db_true() );
+                }
+                if ( String(prefix_menulist.value) != String(my_acn.prefix()) {
+                    my_acn.prefix( prefix_menulist.value );
+                    my_acn.ischanged( get_db_true() );
+                }
+                if ( String(suffix_menulist.value) != String(my_acn.suffix()) {
+                    my_acn.suffix( suffix_menulist.value );
+                    my_acn.ischanged( get_db_true() );
+                }
+
+                if (get_bool( my_acn.ischanged() )) {
+                    volumes.push( my_acn );
                 }
+*/
             }
         }
+        if (volumes.length > 0) {
+            if (typeof xul_param('update_volume') == 'function') {
+                xul_param('update_volume')(volumes);
+            } else {
+                 var r = g.network.simple_request(
+                    'FM_ACN_TREE_UPDATE',
+                    [ ses(),volumes, false, { 'auto_merge_vols' : false } ]
+                );
+                if (typeof r.ilsevent != 'undefined') {
+                    alert('error with volume update: ' + js2JSON(r));
+                }
+            }
+        }
+    } catch(E) {
+        alert('Error in volume_copy_creator.js, vivicate_volumes(): ' + E);
+    }
+}
+
+g.stash_and_close = function(param) {
+
+    try {
+
+        var copies;
+        if (xulG.unified_interface) {
+            copies = xulG.copies;
+        } else {
+            copies = g.gather_copies();
+            copies = blob.copies;
+        }
 
         var dont_close = false;
-        JSAN.use('util.window'); var win = new util.window();
+
+        g.vivicate_update_volumes();
+        for (var i = 0; i < copies.length; i++) {
+            var acn_id = copies[i].call_number();
+            if (typeof g.acn_map[acn_id] != 'undefined') {
+                // handle vivicated-callnumbers
+                copies[i].call_number( g.acn_map[acn_id].id() );
+            } else {
+                alert('error in stash and close, acn_id = ' + acn_id);
+            }
+        }
+
         if (copies.length > 0) {
-            JSAN.use('cat.util');
             if (param == 'edit') {
+                JSAN.use('cat.util');
                 copies = cat.util.spawn_copy_editor( { 'edit' : true, 'docid' : g.doc_id, 'copies' : copies, 'caller_handles_update' : true });
             }
             if (typeof xul_param('update_copy') == 'function') {
@@ -501,7 +1095,7 @@ g.stash_and_close = function(param) {
                     [ ses(),copies, true ]
                 );
                 if (typeof r.ilsevent != 'undefined') {
-                    g.error.standard_unexpected_error_alert('copy update',r);
+                    alert('error with copy update:' + js2JSON(r));
                 }
             }
             try {
@@ -513,22 +1107,26 @@ g.stash_and_close = function(param) {
                         urls.XUL_SPINE_LABEL,
                         { 'tab_name' : $("catStrings").getString('staff.cat.util.spine_editor.tab_name') },
                         {
-                            'barcodes' : util.functional.map_list( copies, function(o){return o.barcode();}) 
+                            'barcodes' : util.functional.map_list( copies, function(o){return o.barcode();})
                         }
                     );
                 }
             } catch(E) {
-                g.error.standard_unexpected_error_alert($(catStrings).getString('staff.cat.volume_copy_creator.stash_and_close.tree_err2'),E);
+                alert('2: Error in volume_copy_creator.js with g.stash_and_close(): ' + E);
             }
         }
 
         try { if (typeof window.refresh == 'function') { window.refresh(); } } catch(E) { dump(E+'\n'); }
         try { if (typeof g.refresh == 'function') { g.refresh(); } } catch(E) { dump(E+'\n'); }
 
+        if (typeof xulG.unlock_copy_editor == 'function') {
+            xulG.unlock_copy_editor();
+        }
+
         if (! dont_close) { xulG.close_tab(); }
 
     } catch(E) {
-        g.error.standard_unexpected_error_alert($(catStrings).getString('staff.cat.volume_copy_creator.stash_and_close.tree_err3'),E);
+        alert('3: Error in volume_copy_creator.js with g.stash_and_close(): ' + E);
     }
 }
 
@@ -559,8 +1157,7 @@ g.load_prefs = function() {
 
         }
     } catch(E) {
-        g.error.standard_unexpected_error_alert($(catStrings).getString('staff.cat.volume_copy_creator.load_prefs.err_retrieving_prefs'),E);
-        
+        alert('Error in volume_copy_creator.js with g.load_prefs(): ' + E);
     }
 }
 
@@ -576,10 +1173,143 @@ g.save_prefs = function () {
         );
         file.close();
     } catch(E) {
-        g.error.standard_unexpected_error_alert($(catStrings).getString('staff.cat.volume_copy_creator.save_prefs.err_storing_prefs'),E);
+        alert('Error in volume_copy_creator.js with g.save_prefs(): ' + E);
+    }
+}
+
+g.render_class_menu = function(call_number_tb) {
+    var ml = util.widgets.make_menulist(
+        util.functional.map_list(
+            g.data.list.acnc,
+            function(o) {
+                return [ o.name(), o.id() ];
+            }
+        )
+    );
+    ml.setAttribute('rel_vert_pos',rel_vert_pos_call_number_classification);
+    ml.addEventListener(
+        'command',
+        function() {
+            call_number_tb.setAttribute('acnc_id',ml.value);
+        },
+        false
+    );
+    return ml;
+}
+
+g.render_prefix_menu = function(call_number_tb) {
+    var ou_id = call_number_tb.getAttribute('ou_id');
+    var org = g.data.hash.aou[ ou_id ];
+    var menulist = document.createElement('menulist');
+        var menupopup = document.createElement('menupopup');
+        menulist.appendChild(menupopup);
+        var org_list = []; // order from top of consortium to owning lib
+        while(org) {
+            org_list.unshift(org.id());
+            org = org.parent_ou();
+            if (org && typeof org != 'object') {
+                org = g.data.hash.aou[ org ];
+            }
+        }
+        for (var i = 0; i < org_list.length; i++) {
+            g.render_prefix_menu_items(menupopup,org_list[i]);
+        }
+
+    menulist.setAttribute('rel_vert_pos',rel_vert_pos_call_number_prefix);
+    menulist.addEventListener(
+        'command',
+        function() {
+            call_number_tb.setAttribute('acnp_id',menulist.value);
+        },
+        false
+    );
+    return menulist;
+}
+
+g.render_prefix_menu_items = function(menupopup,ou_id) {
+    if (typeof g.data.list['acnp_for_lib_'+ou_id] == 'undefined') {
+        g.data.list['acnp_for_lib_'+ou_id] = g.network.simple_request(
+            'FM_ACNP_RETRIEVE_VIA_PCRUD',
+            [ ses(), {"owning_lib":{"=":ou_id}}, {"order_by":{"acnp":"label_sortkey"}} ]
+        );
+        g.data.stash('list');
+    }
+    for (var i = 0; i < g.data.list['acnp_for_lib_'+ou_id].length; i++) {
+        var my_acnp = g.data.list['acnp_for_lib_'+ou_id][i];
+        var menuitem = document.createElement('menuitem');
+        menupopup.appendChild(menuitem);
+            menuitem.setAttribute(
+                'label',
+                my_acnp.id() == -1 ? '' :
+                $('catStrings').getFormattedString(
+                    'staff.cat.volume_copy_creator.call_number_prefix.menuitem_label',
+                    [
+                        my_acnp.label(),
+                        g.data.hash.aou[ ou_id ].shortname()
+                    ]
+                )
+            );
+            menuitem.setAttribute('value',my_acnp.id());
     }
 }
 
+g.render_suffix_menu = function(call_number_tb) {
+    var ou_id = call_number_tb.getAttribute('ou_id');
+    var org = g.data.hash.aou[ ou_id ];
+    var menulist = document.createElement('menulist');
+        var menupopup = document.createElement('menupopup');
+        menulist.appendChild(menupopup);
+        var org_list = []; // order from top of consortium to owning lib
+        while(org) {
+            org_list.unshift(org.id());
+            org = org.parent_ou();
+            if (org && typeof org != 'object') {
+                org = g.data.hash.aou[ org ];
+            }
+        }
+        for (var i = 0; i < org_list.length; i++) {
+            g.render_suffix_menu_items(menupopup,org_list[i]);
+        }
+
+    menulist.setAttribute('rel_vert_pos',rel_vert_pos_call_number_suffix);
+    menulist.addEventListener(
+        'command',
+        function() {
+            call_number_tb.setAttribute('acns_id',menulist.value);
+        },
+        false
+    );
+    return menulist;
+}
+
+g.render_suffix_menu_items = function(menupopup,ou_id) {
+    if (typeof g.data.list['acns_for_lib_'+ou_id] == 'undefined') {
+        g.data.list['acns_for_lib_'+ou_id] = g.network.simple_request(
+            'FM_ACNS_RETRIEVE_VIA_PCRUD',
+            [ ses(), {"owning_lib":{"=":ou_id}}, {"order_by":{"acns":"label_sortkey"}} ]
+        );
+        g.data.stash('list');
+    }
+    for (var i = 0; i < g.data.list['acns_for_lib_'+ou_id].length; i++) {
+        var my_acns = g.data.list['acns_for_lib_'+ou_id][i];
+        var menuitem = document.createElement('menuitem');
+        menupopup.appendChild(menuitem);
+            menuitem.setAttribute(
+                'label',
+                my_acns.id() == -1 ? '' :
+                $('catStrings').getFormattedString(
+                    'staff.cat.volume_copy_creator.call_number_suffix.menuitem_label',
+                    [
+                        my_acns.label(),
+                        g.data.hash.aou[ ou_id ].shortname()
+                    ]
+                )
+            );
+            menuitem.setAttribute('value',my_acns.id());
+    }
+}
+
+
 g.list_callnumbers = function(doc_id, label_class) {
     var cn_blob;
     try {
@@ -589,18 +1319,113 @@ g.list_callnumbers = function(doc_id, label_class) {
     }
     var hbox = document.getElementById('marc_cn');
     var ml = util.widgets.make_menulist(
-        util.functional.map_list(
-            cn_blob,
-            function(o) {
-                for (var i in o) {
-                    return [ o[i], i ];
+        [
+            [ '', '' ]
+        ].concat(
+            util.functional.map_list(
+                cn_blob,
+                function(o) {
+                    for (var i in o) {
+                        return [ o[i], i ];
+                    }
                 }
-            }
+            )
         )
     ); hbox.appendChild(ml);
     ml.setAttribute('editable','true');
     ml.setAttribute('width', '200');
+    ml.setAttribute('id', 'marc_cn_menulist');
+}
+
+g.list_classes = function() {
+    var hbox = $('batch_class');
+    var ml = util.widgets.make_menulist(
+        [
+            [ '<No Change>', false ]
+        ].concat(
+            util.functional.map_list(
+                g.data.list.acnc,
+                function(o) {
+                    return [ o.name(), o.id() ];
+                }
+            )
+        )
+    ); hbox.appendChild(ml);
+    ml.setAttribute('id','batch_class_menulist');
+    ml.addEventListener(
+        'command',
+        function() {
+            if (!isNaN(Number(ml.value))) {
+                addCSSClass(hbox,'copy_editor_field_changed');
+                if (xulG.unified_interface) {
+                    xulG.notify_of_templatable_field_change('batch_class_menulist',ml.value);
+                }
+            } else {
+                removeCSSClass(hbox,'copy_editor_field_changed');
+            }
+        },
+        false
+    );
+}
+
+g.list_prefixes = function() {
+    var hbox = $('batch_prefix');
+    var ml = util.widgets.make_menulist(
+        [
+            [ '<No Change>', false ]
+        ]
+    ); hbox.appendChild(ml);
+    for (var i = 0; i < g.common_ancestor_ou_ids.length; i++) {
+        g.render_prefix_menu_items(ml.firstChild,g.common_ancestor_ou_ids[i]);
+    }
+    ml.setAttribute('id','batch_prefix_menulist');
+    ml.addEventListener(
+        'command',
+        function() {
+            if (!isNaN(Number(ml.value))) {
+                addCSSClass(hbox,'copy_editor_field_changed');
+                if (xulG.unified_interface) {
+                    xulG.notify_of_templatable_field_change('batch_prefix_menulist',ml.value);
+                }
+            } else {
+                removeCSSClass(hbox,'copy_editor_field_changed');
+            }
+        },
+        false
+    );
+}
+
+g.list_suffixes = function() {
+    var hbox = $('batch_suffix');
+    var ml = util.widgets.make_menulist(
+        [
+            [ '<No Change>', false ]
+        ]
+    ); hbox.appendChild(ml);
+    for (var i = 0; i < g.common_ancestor_ou_ids.length; i++) {
+        g.render_suffix_menu_items(ml.firstChild,g.common_ancestor_ou_ids[i]);
+    }
+    ml.setAttribute('id','batch_suffix_menulist');
+    ml.addEventListener(
+        'command',
+        function() {
+            if (!isNaN(Number(ml.value))) {
+                addCSSClass(hbox,'copy_editor_field_changed');
+                if (xulG.unified_interface) {
+                    xulG.notify_of_templatable_field_change('batch_suffix_menulist',ml.value);
+                }
+            } else {
+                removeCSSClass(hbox,'copy_editor_field_changed');
+            }
+        },
+        false
+    );
+}
+
+g.render_batch_button = function() {
+    var hbox = $('batch_button_box');
     var btn = document.createElement('button');
+    btn.setAttribute('id','batch_button');
     btn.setAttribute('label',$('catStrings').getString('staff.cat.volume_copy_creator.my_init.btn.label'));
     btn.setAttribute('accesskey',$('catStrings').getString('staff.cat.volume_copy_creator.my_init.btn.accesskey'));
     btn.setAttribute('image','/xul/server/skin/media/images/down_arrow.gif');
@@ -610,15 +1435,49 @@ g.list_callnumbers = function(doc_id, label_class) {
         function() {
             var nl = document.getElementsByTagName('textbox');
             for (var i = 0; i < nl.length; i++) {
-                if (nl[i].getAttribute('rel_vert_pos')==2 
-                    && !nl[i].disabled) 
-                {
-                    nl[i].value = ml.value;
-                    util.widgets.dispatch('change',nl[i]);
+                /* label */
+                if (nl[i].getAttribute('rel_vert_pos')==rel_vert_pos_call_number && !nl[i].disabled) {
+                    var label =  $('marc_cn').firstChild.value;
+                    if (label != '') {
+                        nl[i].value = label;
+                        util.widgets.dispatch('change',nl[i]);
+                    }
+                }
+            }
+            nl = document.getElementsByTagName('menulist');
+            for (var i = 0; i < nl.length; i++) {
+                /* classification */
+                if (nl[i].getAttribute('rel_vert_pos')==rel_vert_pos_call_number_classification && !nl[i].disabled) {
+                    var value =  $('batch_class_menulist').value;
+                    if (!isNaN( Number(value) )) {
+                        nl[i].value = value;
+                        util.widgets.dispatch('command',nl[i]);
+                    }
+                }
+                /* prefix */
+                if (nl[i].getAttribute('rel_vert_pos')==rel_vert_pos_call_number_prefix && !nl[i].disabled) {
+                    var value =  $('batch_prefix_menulist').value;
+                    if (!isNaN( Number(value) )) {
+                        nl[i].value = value;
+                        util.widgets.dispatch('command',nl[i]);
+                    }
+                }
+                /* suffix */
+                if (nl[i].getAttribute('rel_vert_pos')==rel_vert_pos_call_number_suffix && !nl[i].disabled) {
+                    var value =  $('batch_suffix_menulist').value;
+                    if (!isNaN( Number(value) )) {
+                        nl[i].value = value;
+                        util.widgets.dispatch('command',nl[i]);
+                    }
                 }
             }
+            setTimeout(
+                function() {
+                    g.gather_copies_soon();
+                },0
+            );
             if (g.last_focus) setTimeout( function() { g.last_focus.focus(); }, 0 );
-        }, 
+        },
         false
     );
 }
index 0c50587..9e88c1a 100644 (file)
@@ -6,6 +6,7 @@
 <!-- STYLESHEETS -->
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 <?xml-stylesheet href="/xul/server/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="/xul/server/skin/cat.css" type="text/css"?>
 
 <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
 <!-- LOCALIZATION -->
@@ -19,7 +20,6 @@
 
 <window id="cat_volume_copy_creator_win" 
     onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
-    width="800" height="580" oils_persist="height width sizemode"
     title="&staff.cat.volume_copy_creator.title;"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <messagecatalog id="catStrings" src="/xul/server/locale/<!--#echo var='locale'-->/cat.properties" />
     <messagecatalog id="circStrings" src="/xul/server/locale/<!--#echo var='locale'-->/circ.properties" />
 
-    <vbox id="summary_box"/>
-    <hbox>
-        <hbox id="marc_cn"/>
-        <spacer flex="1" />
-        <button id="generate_barcodes" label="&staff.cat.volume_copy_creator.generate_barcodes.label;" oncommand="g.generate_barcodes();" accesskey="&staff.cat.volume_copy_creator.generate_barcodes.accesskey;"/>
-        <checkbox id="check_barcodes" label="&staff.cat.volume_copy_creator.check_barcodes.label;" oncommand="g.save_prefs();" accesskey="&staff.cat.volume_copy_creator.check_barcodes.accesskey;"/>
-        <checkbox id="print_labels" label="&staff.cat.volume_copy_creator.print_labels.label;"  oncommand="g.save_prefs();" accesskey="&staff.cat.volume_copy_creator.print_labels.accesskey;"/>
-    </hbox>
-    <groupbox flex="1" class="my_overflow">
-        <caption id="caption" label="&staff.cat.volume_copy_creator.label;"/>
-        <grid flex="1">
-            <columns> <column flex="0"/> <column flex="0"/> <column flex="1"/> </columns>
-            <rows id="rows">
-                <row>
-                    <label value="&staff.cat.volume_copy_creator.library_label.value;" style="font-weight: bold"/>
-                    <label value="&staff.cat.volume_copy_creator.num_of_volumes_label.value;" style="font-weight: bold"/>
-                </row>
-            </rows>
-        </grid>
-    </groupbox>
-    <hbox style="border-bottom: solid black thin">
-        <spacer flex="1" />
-        <button id="CreateWithDefaults" disabled="true" oncommand="g.stash_and_close('noedit');"/>
-        <button id="EditThenCreate" disabled="true" oncommand="g.stash_and_close('edit');"/>
-    </hbox>
+<vbox flex="1" class="my_overflow">
+    <vbox id="summary_box" oils_persist="height"/>
+    <splitter
+        collapse="before"
+        resize_before="flex"
+        resize_after="flex"
+        oils_persist="state hidden"
+        oils_persist_peers="summary_box main">
+        <grippy/>
+    </splitter>
+    <vbox id="main" oils_persist="height" flex="1">
+        <hbox flex="0">
+            <hbox id="batch_bar">
+                <label value="&staff.cat.volume_copy_creator.batch_bar;"/>
+                <label value="&staff.cat.volume_copy_creator.batch_bar.call_number.classification;"/>
+                <hbox id="batch_class"/>
+                <label value="&staff.cat.volume_copy_creator.batch_bar.call_number.prefix;"/>
+                <hbox id="batch_prefix"/>
+                <label value="&staff.cat.volume_copy_creator.batch_bar.call_number.label.label;"
+                    accesskey="&staff.cat.volume_copy_creator.batch_bar.call_number.label.accesskey;" control="marc_cn_menulist"/>
+                <hbox id="marc_cn"/>
+                <label value="&staff.cat.volume_copy_creator.batch_bar.call_number.suffix;"/>
+                <hbox id="batch_suffix"/>
+                <hbox id="batch_button_box"/>
+            </hbox>
+            <spacer flex="1" />
+        </hbox>
+        <groupbox flex="1" class="my_overflow">
+            <caption id="caption" label="&staff.cat.volume_copy_creator.label;"/>
+            <grid flex="1">
+                <columns> <column flex="0"/> <column flex="0"/> <column flex="1"/> </columns>
+                <rows id="rows">
+                    <row>
+                        <label value="&staff.cat.volume_copy_creator.library_label.value;" style="font-weight: bold"/>
+                        <label value="&staff.cat.volume_copy_creator.num_of_volumes_label.value;" style="font-weight: bold"/>
+                    </row>
+                </rows>
+            </grid>
+        </groupbox>
+        <hbox style="border-bottom: solid black thin" flex="0">
+            <hbox id="misc_control_bar">
+                <button id="generate_barcodes"
+                    label="&staff.cat.volume_copy_creator.generate_barcodes.label;"
+                    oncommand="g.generate_barcodes();"
+                    accesskey="&staff.cat.volume_copy_creator.generate_barcodes.accesskey;"/>
+                <checkbox id="check_barcodes"
+                    label="&staff.cat.volume_copy_creator.check_barcodes.label;"
+                    oncommand="g.save_prefs();"
+                    accesskey="&staff.cat.volume_copy_creator.check_barcodes.accesskey;"/>
+                <checkbox id="print_labels"
+                    label="&staff.cat.volume_copy_creator.print_labels.label;"
+                    oncommand="g.save_prefs();"
+                    accesskey="&staff.cat.volume_copy_creator.print_labels.accesskey;"/>
+            </hbox>
+            <spacer flex="1"/>
+            <hbox id="non_unified_buttons">
+                <button id="CreateWithDefaults" disabled="true" oncommand="g.stash_and_close('noedit');"/>
+                <button id="EditThenCreate" disabled="true" oncommand="g.stash_and_close('edit');"/>
+            </hbox>
+            <button id="Create" disabled="true" oncommand="g.stash_and_close('unified_interface');"/>
+        </hbox>
+    </vbox>
+</vbox>
 
 </window>
 
diff --git a/Open-ILS/xul/staff_client/server/cat/volume_copy_editor.js b/Open-ILS/xul/staff_client/server/cat/volume_copy_editor.js
new file mode 100644 (file)
index 0000000..e15c395
--- /dev/null
@@ -0,0 +1,111 @@
+var error;
+var g = {};
+
+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'); error = new util.error();
+        error.sdump('D_TRACE','my_init() for main_test.xul')