Merge remote-tracking branch 'eg-working/user/berick/hold-current-shelf-lib'
authorMike Rylander <mrylander@gmail.com>
Mon, 9 Jan 2012 18:44:16 +0000 (13:44 -0500)
committerMike Rylander <mrylander@gmail.com>
Mon, 9 Jan 2012 18:44:45 +0000 (13:44 -0500)
Signed-off-by: Mike Rylander <mrylander@gmail.com>
13 files changed:
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/c-apps/oils_sql.c
Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Holds.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Validator.pm
Open-ILS/src/perlmods/lib/OpenILS/SIP/Patron.pm
Open-ILS/src/sql/Pg/090.schema.action.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.hold-current-shelf-lib.sql [new file with mode: 0644]
Open-ILS/src/templates/opac/parts/hold_status.tt2
Open-ILS/web/opac/skin/default/js/myopac.js
Open-ILS/xul/staff_client/server/circ/util.js
Open-ILS/xul/staff_client/server/locale/en-US/circ.properties

index 57ef409..07b9815 100644 (file)
@@ -4534,6 +4534,7 @@ SELECT  usr,
                        <field reporter:label="Is Mint Condition" name="mint_condition" reporter:datatype="bool" />
                        <field reporter:label="Shelf Expire Time" name="shelf_expire_time" reporter:datatype="timestamp"/>
                        <field reporter:label="Notes" name="notes" reporter:datatype="link" oils_persist:virtual="true"/>
+                       <field reporter:label="Current Shelf Lib" name="current_shelf_lib" reporter:datatype="org_unit"/>
                </fields>
                <links>
                        <link field="fulfillment_lib" reltype="has_a" key="id" map="" class="aou"/>
@@ -4550,6 +4551,7 @@ SELECT  usr,
                        <link field="bib_rec" reltype="might_have" key="id" map="" class="rhrr"/>
                        <link field="cancel_cause" reltype="might_have" key="id" map="" class="ahrcc"/>
                        <link field="notes" reltype="has_many" key="hold" map="" class="ahrn"/>
+                       <link field="current_shelf_lib" reltype="has_a" key="id" map="" class="aou"/>
                </links>
        </class>
        <class id="alhr" controller="open-ils.cstore" oils_obj:fieldmapper="action::last_hold_request" reporter:label="Last Captured Hold Request" oils_persist:readonly="true">
@@ -4594,6 +4596,7 @@ SELECT  usr,
                        <field reporter:label="Is Mint Condition" name="mint_condition" reporter:datatype="bool" />
                        <field reporter:label="Shelf Expire Time" name="shelf_expire_time" reporter:datatype="timestamp"/>
                        <field reporter:label="Notes" name="notes" reporter:datatype="link" oils_persist:virtual="true"/>
+                       <field reporter:label="Current Shelf Lib" name="current_shelf_lib" reporter:datatype="org_unit"/>
                </fields>
                <links>
                        <link field="fulfillment_lib" reltype="has_a" key="id" map="" class="aou"/>
@@ -4610,6 +4613,7 @@ SELECT  usr,
                        <link field="bib_rec" reltype="might_have" key="id" map="" class="rhrr"/>
                        <link field="cancel_cause" reltype="might_have" key="id" map="" class="ahrcc"/>
                        <link field="notes" reltype="has_many" key="hold" map="" class="ahrn"/>
+                       <link field="current_shelf_lib" reltype="has_a" key="id" map="" class="aou"/>
                </links>
        </class>
 
index e0b545c..99918d5 100644 (file)
@@ -4950,6 +4950,8 @@ static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* or
                const char* field =
                        jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
 
+               jsonObject* compare_to = jsonObjectGetKeyConst( order_spec, "compare" );
+
                if( !field || !class_alias ) {
                        osrfLogError( OSRF_LOG_MARK,
                                "%s: Missing class or field name in field specification of ORDER BY clause",
@@ -5028,6 +5030,24 @@ static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* or
 
                        OSRF_BUFFER_ADD( order_buf, transform_str );
                        free( transform_str );
+               } else if( compare_to ) {
+                       char* compare_str = searchPredicate( order_class_info, field_def, compare_to, ctx );
+                       if( ! compare_str ) {
+                               if( ctx )
+                                       osrfAppSessionStatus(
+                                               ctx->session,
+                                               OSRF_STATUS_INTERNALSERVERERROR,
+                                               "osrfMethodException",
+                                               ctx->request,
+                                               "Severe query error in ORDER BY clause -- "
+                                               "see error log for more details"
+                                       );
+                               buffer_free( order_buf );
+                               return NULL;
+                       }
+
+                       buffer_fadd( order_buf, "(%s)", compare_str );
+                       free( compare_str );
                }
                else
                        buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
index c54debf..93f1a75 100644 (file)
@@ -1840,7 +1840,7 @@ sub hold_request_count {
     }
 
     my $holds = $e->json_query({
-        select => {ahr => ['shelf_time']},
+        select => {ahr => ['pickup_lib', 'current_shelf_lib']},
         from => 'ahr',
         where => {
             usr => $user_id,
@@ -1851,7 +1851,12 @@ sub hold_request_count {
 
        return { 
         total => scalar(@$holds), 
-        ready => scalar(grep { $_->{shelf_time} } @$holds) 
+        ready => scalar(
+            grep { 
+                $_->{current_shelf_lib} and # avoid undef warnings
+                $_->{pickup_lib} eq $_->{current_shelf_lib} 
+            } @$holds
+        ) 
     };
 }
 
index 50ef6d6..999ac68 100644 (file)
@@ -1655,6 +1655,7 @@ sub handle_checkout_holds {
         $hold->clear_capture_time;
         $hold->clear_shelf_time;
         $hold->clear_shelf_expire_time;
+           $hold->clear_current_shelf_lib;
 
         return $self->bail_on_event($e->event)
             unless $e->update_action_hold_request($hold);
@@ -2798,23 +2799,42 @@ sub checkin_build_copy_transit {
     my $self            = shift;
     my $dest            = shift;
     my $copy       = $self->copy;
-   my $transit    = Fieldmapper::action::transit_copy->new;
+    my $transit    = Fieldmapper::action::transit_copy->new;
+
+    # if we are transiting an item to the shelf shelf, it's a hold transit
+    if (my $hold = $self->remote_hold) {
+        $transit = Fieldmapper::action::hold_transit_copy->new;
+        $transit->hold($hold->id);
+
+        # the item is going into transit, remove any shelf-iness
+        if ($hold->current_shelf_lib or $hold->shelf_time) {
+            $hold->clear_current_shelf_lib;
+            $hold->clear_shelf_time;
+            return $self->bail_on_events($self->editor->event)
+                unless $self->editor->update_action_hold_request($hold);
+        }
+    }
 
     #$dest  ||= (ref($copy->circ_lib)) ? $copy->circ_lib->id : $copy->circ_lib;
     $logger->info("circulator: transiting copy to $dest");
 
-   $transit->source($self->circ_lib);
-   $transit->dest($dest);
-   $transit->target_copy($copy->id);
-   $transit->source_send_time('now');
-   $transit->copy_status( $U->copy_status($copy->status)->id );
+    $transit->source($self->circ_lib);
+    $transit->dest($dest);
+    $transit->target_copy($copy->id);
+    $transit->source_send_time('now');
+    $transit->copy_status( $U->copy_status($copy->status)->id );
 
     $logger->debug("circulator: setting copy status on transit: ".$transit->copy_status);
 
-    return $self->bail_on_events($self->editor->event)
-        unless $self->editor->create_action_transit_copy($transit);
+    if ($self->remote_hold) {
+        return $self->bail_on_events($self->editor->event)
+            unless $self->editor->create_action_hold_transit_copy($transit);
+    } else {
+        return $self->bail_on_events($self->editor->event)
+            unless $self->editor->create_action_transit_copy($transit);
+    }
 
-   $copy->status(OILS_COPY_STATUS_IN_TRANSIT);
+    $copy->status(OILS_COPY_STATUS_IN_TRANSIT);
     $self->update_copy;
     $self->checkin_changed(1);
 }
@@ -3169,35 +3189,9 @@ sub process_received_transit {
 # ------------------------------------------------------------------
 sub put_hold_on_shelf {
     my($self, $hold) = @_;
-
     $hold->shelf_time('now');
-
-    my $shelf_expire = $U->ou_ancestor_setting_value(
-        $self->circ_lib, 'circ.holds.default_shelf_expire_interval', $self->editor);
-
-    return undef unless $shelf_expire;
-
-    my $seconds = OpenSRF::Utils->interval_to_seconds($shelf_expire);
-    my $expire_time = DateTime->now->add(seconds => $seconds);
-
-    # if the shelf expire time overlaps with a pickup lib's 
-    # closed date, push it out to the first open date
-    my $dateinfo = $U->storagereq(
-        'open-ils.storage.actor.org_unit.closed_date.overlap', 
-        $hold->pickup_lib, $expire_time);
-
-    if($dateinfo) {
-        my $dt_parser = DateTime::Format::ISO8601->new;
-        $expire_time = $dt_parser->parse_datetime(cleanse_ISO8601($dateinfo->{end}));
-
-        # TODO: enable/disable time bump via setting?
-        $expire_time->set(hour => '23', minute => '59', second => '59');
-
-        $logger->info("circulator: shelf_expire_time overlaps".
-            " with closed date, pushing expire time to $expire_time");
-    }
-
-    $hold->shelf_expire_time($expire_time->strftime('%FT%T%z'));
+    $hold->current_shelf_lib($self->circ_lib);
+    $holdcode->set_hold_shelf_expire_time($hold, $self->editor);
     return undef;
 }
 
index 6705d61..38f269f 100644 (file)
@@ -530,7 +530,17 @@ sub retrieve_holds {
     } else {
 
         # order non-cancelled holds by ready-for-pickup, then active, followed by suspended
-        $holds_query->{order_by} = {ahr => ['shelf_time', 'frozen', 'request_time']};
+        # "compare" sorts false values to the front.  testing pickup_lib != current_shelf_lib
+        # will sort by pl = csl > pl != csl > followed by csl is null;
+        $holds_query->{order_by} = [
+            {   class => 'ahr', 
+                field => 'pickup_lib', 
+                compare => {'!='  => {'+ahr' => 'current_shelf_lib'}}},
+            {class => 'ahr', field => 'shelf_time'},
+            {class => 'ahr', field => 'frozen'},
+            {class => 'ahr', field => 'request_time'}
+
+        ];
         $holds_query->{where}->{cancel_time} = undef;
     }
 
@@ -672,6 +682,7 @@ sub uncancel_hold {
     $hold->clear_capture_time;
     $hold->clear_prev_check_time;
     $hold->clear_shelf_expire_time;
+       $hold->clear_current_shelf_lib;
 
     $e->update_action_hold_request($hold) or return $e->die_event;
     $e->commit;
@@ -951,20 +962,23 @@ sub update_hold_impl {
             $transit->dest($hold->pickup_lib);
             $e->update_action_hold_transit_copy($transit) or return $e->die_event;
 
-        } elsif($hold_status == 4) { # on holds shelf
+        } elsif($hold_status == 4 or $hold_status == 8) { # on holds shelf
 
             return $e->die_event unless $e->allowed('UPDATE_PICKUP_LIB_FROM_HOLDS_SHELF', $orig_hold->pickup_lib);
             return $e->die_event unless $e->allowed('UPDATE_PICKUP_LIB_FROM_HOLDS_SHELF', $hold->pickup_lib);
 
             $logger->info("updating pickup lib for hold ".$hold->id." while on holds shelf");
 
-            # create the new transit
-            my $evt = transit_hold($e, $orig_hold, $hold, $e->retrieve_asset_copy($hold->current_copy));
-            return $evt if $evt;
+            if ($hold->pickup_lib eq $orig_hold->current_shelf_lib) {
+                # This can happen if the pickup lib is changed while the hold is 
+                # on the shelf, then changed back to the original pickup lib.
+                # Restore the original shelf_expire_time to prevent abuse.
+                set_hold_shelf_expire_time(undef, $hold, $e, $hold->shelf_time);
 
-            # hold is leaving the shelf  
-            $hold->clear_shelf_time;
-            $hold->clear_shelf_expire_time;
+            } else {
+                # clear to prevent premature shelf expiration
+                $hold->clear_shelf_expire_time;
+            }
         }
     } 
 
@@ -980,6 +994,49 @@ sub update_hold_impl {
     return $hold->id;
 }
 
+# this does not update the hold in the DB.  It only 
+# sets the shelf_expire_time field on the hold object.
+# start_time is optional and defaults to 'now'
+sub set_hold_shelf_expire_time {
+    my ($class, $hold, $editor, $start_time) = @_;
+
+    my $shelf_expire = $U->ou_ancestor_setting_value( 
+        $hold->pickup_lib,
+        'circ.holds.default_shelf_expire_interval', 
+        $editor
+    );
+
+    return undef unless $shelf_expire;
+
+    $start_time = ($start_time) ? 
+        DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($start_time)) :
+        DateTime->now;
+
+    my $seconds = OpenSRF::Utils->interval_to_seconds($shelf_expire);
+    my $expire_time = $start_time->add(seconds => $seconds);
+
+    # if the shelf expire time overlaps with a pickup lib's 
+    # closed date, push it out to the first open date
+    my $dateinfo = $U->storagereq(
+        'open-ils.storage.actor.org_unit.closed_date.overlap', 
+        $hold->pickup_lib, $expire_time);
+
+    if($dateinfo) {
+        my $dt_parser = DateTime::Format::ISO8601->new;
+        $expire_time = $dt_parser->parse_datetime(cleanse_ISO8601($dateinfo->{end}));
+
+        # TODO: enable/disable time bump via setting?
+        $expire_time->set(hour => '23', minute => '59', second => '59');
+
+        $logger->info("circulator: shelf_expire_time overlaps".
+            " with closed date, pushing expire time to $expire_time");
+    }
+
+    $hold->shelf_expire_time($expire_time->strftime('%FT%T%z'));
+    return undef;
+}
+
+
 sub transit_hold {
     my($e, $orig_hold, $hold, $copy) = @_;
     my $src  = $orig_hold->pickup_lib;
@@ -1089,6 +1146,7 @@ Returns event on error or:
  5 for 'hold-shelf-delay'
  6 for 'canceled'
  7 for 'suspended'
+ 8 for 'captured, on wrong hold shelf'
 END_OF_DESC
         }
     }
@@ -1118,6 +1176,9 @@ sub _hold_status {
     if ($U->is_true($hold->frozen)) {
         return 7;
     }
+    if ($hold->current_shelf_lib and $hold->current_shelf_lib ne $hold->pickup_lib) {
+        return 8;
+    }
        return 1 unless $hold->current_copy;
        return 2 unless $hold->capture_time;
 
@@ -1796,6 +1857,7 @@ sub _reset_hold {
        $hold->clear_current_copy;
        $hold->clear_shelf_time;
        $hold->clear_shelf_expire_time;
+       $hold->clear_current_shelf_lib;
 
        $e->update_action_hold_request($hold) or return $e->die_event;
        $e->commit;
@@ -1943,14 +2005,11 @@ sub fetch_captured_holds {
                 capture_time     => { "!=" => undef },
                 current_copy     => { "!=" => undef },
                 fulfillment_time => undef,
-                pickup_lib       => $org,
-#                cancel_time      => undef,
-              }
+                current_shelf_lib => $org
+            }
         }
     };
     if($self->api_name =~ /expired/) {
-#       $query->{'where'}->{'+ahr'}->{'shelf_expire_time'} = {'<' => 'now'};
-        $query->{'where'}->{'+alhr'}->{'shelf_time'} = {'!=' => undef};
         $query->{'where'}->{'+alhr'}->{'-or'} = {
                 shelf_expire_time => { '<' => 'now'},
                 cancel_time => { '!=' => undef },
index 54847b3..57563e0 100644 (file)
@@ -75,11 +75,13 @@ sub HoldIsAvailable {
 
     return 1 if 
         !$hold->cancel_time and
-        $hold->capture_time and 
-        $hold->current_copy and
-        $hold->shelf_time and
         !$hold->fulfillment_time and
-        $hold->current_copy->status == OILS_COPY_STATUS_ON_HOLDS_SHELF;
+        $hold->current_shelf_lib and
+        $hold->current_shelf_lib eq $hold->pickup_lib and
+        $hold->capture_time and # redundant
+        $hold->current_copy and # redundant
+        $hold->shelf_time and   # redundant
+        $hold->current_copy->status == OILS_COPY_STATUS_ON_HOLDS_SHELF; # redundant
 
     return 0;
 }
index d2b2085..68d8c13 100644 (file)
@@ -624,16 +624,27 @@ sub recall_items {
 sub unavail_holds {
      my ($self, $start, $end) = @_;
      syslog('LOG_DEBUG', 'OILS: Patron->unavail_holds()');
+
+     my $ids = $self->{editor}->json_query({
+        select => {ahr => ['id']},
+        from => 'ahr',
+        where => {
+            usr => $self->{user}->id,
+            fulfillment_time => undef,
+            cancel_time => undef,
+            '-or' => [
+                {current_shelf_lib => undef},
+                {current_shelf_lib => {'!=' => {'+ahr' => 'pickup_lib'}}}
+            ]
+        }
+    });
  
      my @holds_sip_output = map {
         OpenILS::SIP::clean_text($self->__hold_to_title($_))
      } @{
-        $self->{editor}->search_action_hold_request({
-            usr              => $self->{user}->id,
-            fulfillment_time => undef,
-            cancel_time      => undef,
-            shelf_time       => undef
-        })
+        $self->{editor}->search_action_hold_request(
+            {id => [map {$_->{id}} @$ids]}
+        )
      };
  
      return (defined $start and defined $end) ?
index eae256a..0ead480 100644 (file)
@@ -399,7 +399,8 @@ CREATE TABLE action.hold_request (
        shelf_time              TIMESTAMP WITH TIME ZONE,
     cut_in_line     BOOL,
        mint_condition  BOOL NOT NULL DEFAULT TRUE,
-       shelf_expire_time TIMESTAMPTZ
+       shelf_expire_time TIMESTAMPTZ,
+       current_shelf_lib INT REFERENCES actor.org_unit DEFERRABLE INITIALLY DEFERRED
 );
 
 CREATE INDEX hold_request_target_idx ON action.hold_request (target);
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.hold-current-shelf-lib.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.hold-current-shelf-lib.sql
new file mode 100644 (file)
index 0000000..cec449c
--- /dev/null
@@ -0,0 +1,28 @@
+-- Evergreen DB patch XXXX.schema.hold-current-shelf-lib.sql
+--
+-- FIXME: insert description of change, if needed
+--
+BEGIN;
+
+
+-- check whether patch can be applied
+SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+-- add the new column
+ALTER TABLE action.hold_request ADD COLUMN current_shelf_lib 
+    INT REFERENCES actor.org_unit DEFERRABLE INITIALLY DEFERRED;
+
+-- set the value for current_shelf_lib on existing shelved holds
+UPDATE action.hold_request ahr
+    SET current_shelf_lib = pickup_lib
+    FROM asset.copy acp
+    WHERE 
+            ahr.shelf_time IS NOT NULL 
+        AND ahr.capture_time IS NOT NULL
+        AND ahr.current_copy IS NOT NULL
+        AND ahr.fulfillment_time IS NULL
+        AND ahr.cancel_time IS NULL
+        AND acp.id = ahr.current_copy
+        AND acp.status = 8; -- on holds shelf
+
+COMMIT;
index 1480665..0cabbec 100644 (file)
@@ -14,7 +14,7 @@
             SET hwait = POSIX.ceil(hold.hold.estimated_wait / 86400);
             l("Estimated wait: [quant,_1,day,days]", hwait) | html;
 
-        ELSIF hold.hold.status == 3;
+        ELSIF hold.hold.status == 3 OR hold.hold.status == 8;
             l("In Transit") | html;
 
         ELSIF hold.hold.status < 3;
index 92e4b12..302f0ec 100644 (file)
@@ -492,7 +492,7 @@ function myOShowHoldStatus(r) {
            if( qstats.status < 3 )
                    unHideMe($n(row, 'hold_status_waiting'));
     
-           if( qstats.status == 3 )
+           if( qstats.status == 3 || qstats.status == 8 )
                    unHideMe($n(row, 'hold_status_transit'));
     }
 }
index e8213d9..3b16945 100644 (file)
@@ -1884,11 +1884,8 @@ circ.util.hold_columns = function(modify,params) {
             'primary' : false,
             'hidden' : false,
             'editable' : false, 'render' : function(my) {
-                if (my.ahr.transit() && my.ahr.transit().dest_recv_time()) {
-                    return util.date.formatted_date( my.ahr.transit().dest_recv_time(), '%{localized}' );
-                }
-                if (!my.ahr.transit() && my.ahr.capture_time()) {
-                    return util.date.formatted_date( my.ahr.capture_time(), '%{localized}' );
+                if (my.ahr.current_shelf_lib() == my.ahr.pickup_lib()) {
+                    return util.date.formatted_date( my.ahr.shelf_time(), '%{localized}' );
                 }
                 return "";
             }
@@ -1944,6 +1941,9 @@ circ.util.hold_columns = function(modify,params) {
                     case 7:
                         return document.getElementById('circStrings').getString('staff.circ.utils.hold_status.7');
                         break;
+                    case 8:
+                        return document.getElementById('circStrings').getString('staff.circ.utils.hold_status.8');
+                        break;
                     default:
                         return my.status;
                         break;
index af45237..a8d2ce3 100644 (file)
@@ -324,6 +324,7 @@ staff.circ.utils.hold_status.4=Ready for pickup
 staff.circ.utils.hold_status.5=Reserved/Pending
 staff.circ.utils.hold_status.6=Canceled
 staff.circ.utils.hold_status.7=Suspended
+staff.circ.utils.hold_status.8=Wrong Shelf
 staff.circ.utils.hold_post_clear_shelf_action.label=Post-Clear
 staff.circ.utils.hold_post_clear_shelf_action.hold=Need for Hold
 staff.circ.utils.hold_post_clear_shelf_action.transit=Need for Transit