Middle Layer work to enable Issuance (type I) holds; also cleaned up support for...
authormiker <miker@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Wed, 8 Sep 2010 20:46:27 +0000 (20:46 +0000)
committermiker <miker@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Wed, 8 Sep 2010 20:46:27 +0000 (20:46 +0000)
git-svn-id: svn://svn.open-ils.org/ILS/trunk@17527 dcc99617-32d9-48b4-a31d-7c20da2025e4

Open-ILS/src/perlmods/OpenILS/Application/Circ/Holds.pm
Open-ILS/src/perlmods/OpenILS/Const.pm
Open-ILS/src/sql/Pg/002.schema.config.sql
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/upgrade/0389.data.issuance-holds.sql [new file with mode: 0644]

index 4333f4c..1d2817e 100644 (file)
@@ -177,8 +177,14 @@ sub create_hold {
         return $e->event unless $e->allowed('TITLE_HOLDS',  $porg);
     } elsif ( $t eq OILS_HOLD_TYPE_VOLUME ) {
         return $e->event unless $e->allowed('VOLUME_HOLDS', $porg);
+    } elsif ( $t eq OILS_HOLD_TYPE_ISSUANCE ) {
+        return $e->event unless $e->allowed('ISSUANCE_HOLDS', $porg);
     } elsif ( $t eq OILS_HOLD_TYPE_COPY ) {
         return $e->event unless $e->allowed('COPY_HOLDS',   $porg);
+    } elsif ( $t eq OILS_HOLD_TYPE_FORCE ) {
+        return $e->event unless $e->allowed('COPY_HOLDS',   $porg);
+    } elsif ( $t eq OILS_HOLD_TYPE_RECALL ) {
+        return $e->event unless $e->allowed('COPY_HOLDS',   $porg);
     }
 
     if( @events ) {
@@ -1526,7 +1532,7 @@ __PACKAGE__->register_method(
                Returns a list ids of un-fulfilled holds for a given title id
                @param authtoken The login session key
                @param id the id of the item whose holds we want to retrieve
-               @param type The hold type - M, T, V, C
+               @param type The hold type - M, T, I, V, C, F, R
        /
 );
 
@@ -1739,11 +1745,12 @@ The named fields in the hash are:
  depth        - hold range depth          (default 0)
  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
  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
  mrid         - required for Meta-record level hold
- hold_type    - T,C,V or M for Title, Copy, Volume or Meta-record  (default "T")
+ hold_type    - T, C (or R or F), I, V or M for Title, Copy, Issuance, Volume or Meta-record  (default "T")
 
 All key/value pairs are passed on to do_possibility_checks.
 
@@ -1829,6 +1836,7 @@ sub check_title_hold {
 sub do_possibility_checks {
     my($e, $patron, $request_lib, $depth, %params) = @_;
 
+    my $issuanceid   = $params{issuance}      || "";
     my $titleid      = $params{titleid}      || "";
     my $volid        = $params{volume_id};
     my $copyid       = $params{copy_id};
@@ -1842,7 +1850,7 @@ sub do_possibility_checks {
        my $volume;
        my $title;
 
-       if( $hold_type eq OILS_HOLD_TYPE_COPY ) {
+       if( $hold_type eq OILS_HOLD_TYPE_FORCE || $hold_type eq OILS_HOLD_TYPE_RECALL || $hold_type eq OILS_HOLD_TYPE_COPY ) {
 
         return $e->event unless $copy   = $e->retrieve_asset_copy($copyid);
         return $e->event unless $volume = $e->retrieve_asset_call_number($copy->call_number);
@@ -1867,6 +1875,12 @@ sub do_possibility_checks {
                        $titleid, $depth, $request_lib, $patron, $e->requestor, $pickup_lib, $selection_ou
         );
 
+       } elsif( $hold_type eq OILS_HOLD_TYPE_ISSUANCE ) {
+
+               return _check_issuance_hold_is_possible(
+                       $issuanceid, $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});
@@ -2016,7 +2030,132 @@ sub _check_title_hold_is_possible {
 
          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 => { acn => ['record'] } } ] );
+               [ $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];
+      }
+    }
+
+    return @status;
+}
+
+sub _check_issuance_hold_is_possible {
+    my( $issuanceid, $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 => {
+                    sitem => {
+                        field  => 'unit',
+                        fkey   => 'id',
+                        filter => { issuance => $issuanceid }
+                    },
+                    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("issuance possible found ".scalar(@$copies)." potential copies");
+
+    if (!@$copies) {
+        my $empty_ok = $e->retrieve_config_global_flag('circ.holds.empty_issuance_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;
          }
    
@@ -2678,14 +2817,22 @@ sub hold_item_is_checked_out {
         limit => 1
     };
 
-    if($hold_type eq 'C') {
+    if($hold_type eq 'C' || $hold_type eq 'R' || $hold_type eq 'F') {
 
         $query->{where}->{'+acp'}->{id}->{in}->{where}->{'target_copy'} = $hold_target;
 
     } elsif($hold_type eq 'V') {
 
         $query->{where}->{'+acp'}->{call_number} = $hold_target;
-    
+
+     } elsif($hold_type eq 'I') {
+
+        $query->{from}->{acp}->{sitem} = {
+            field  => 'unit',
+            fkey   => 'id',
+            filter => {issuance => $hold_target},
+        };
+
     } elsif($hold_type eq 'T') {
 
         $query->{from}->{acp}->{acn} = {
index 1838efe..281f465 100644 (file)
@@ -96,6 +96,9 @@ econst OILS_SETTING_BLOCK_HOLD_FOR_EXPIRED_PATRON       => 'circ.holds.expired_p
 
 
 econst OILS_HOLD_TYPE_COPY        => 'C';
+econst OILS_HOLD_TYPE_FORCE       => 'F';
+econst OILS_HOLD_TYPE_RECALL      => 'R';
+econst OILS_HOLD_TYPE_ISSUANCE    => 'I';
 econst OILS_HOLD_TYPE_VOLUME      => 'V';
 econst OILS_HOLD_TYPE_TITLE       => 'T';
 econst OILS_HOLD_TYPE_METARECORD  => 'M';
index e22dd35..f4e1284 100644 (file)
@@ -68,7 +68,7 @@ CREATE TABLE config.upgrade_log (
     install_date    TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
 );
 
-INSERT INTO config.upgrade_log (version) VALUES ('0388'); -- phasefx
+INSERT INTO config.upgrade_log (version) VALUES ('0389'); -- miker
 
 CREATE TABLE config.bib_source (
        id              SERIAL  PRIMARY KEY,
index e3bbc10..80500c9 100644 (file)
@@ -1382,8 +1382,10 @@ INSERT INTO permission.perm_list VALUES
     ,(391, 'UPDATE_PICKUP_LIB_FROM_TRANSIT', oils_i18n_gettext( 391, 'Allow a user to change the pickup and transit destination for a captured hold item already in transit', 'ppl', 'description' ))
     ,(392, 'COPY_NEEDED_FOR_HOLD.override', oils_i18n_gettext( 392, 'Allow a user to force renewal of an item that could fulfill a hold request', 'ppl', 'description' ))
     ,(393, 'MERGE_AUTH_RECORDS', oils_i18n_gettext( 393, 'Allow a user to merge authority records together', 'ppl', 'description' ))
+    ,(394, 'ISSUANCE_HOLDS', oils_i18n_gettext( 394, 'Allow a user to place holds on serials issuances'))
 ;
 
+
 SELECT SETVAL('permission.perm_list_id_seq'::TEXT, 1000);
 
 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm) VALUES
@@ -6220,6 +6222,18 @@ INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
         'bool'
     );
 
+INSERT INTO config.global_flag (name, label, enabled)
+    VALUES (
+        'circ.holds.empty_issuance_ok',
+        oils_i18n_gettext(
+            'circ.holds.empty_issuance_ok',
+            'Holds: Allow holds on empty issuances',
+            'cgf',
+            'label'
+        ),
+        TRUE
+    );
+
 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
     VALUES (
         'ingest.disable_authority_linking',
diff --git a/Open-ILS/src/sql/Pg/upgrade/0389.data.issuance-holds.sql b/Open-ILS/src/sql/Pg/upgrade/0389.data.issuance-holds.sql
new file mode 100644 (file)
index 0000000..b10b642
--- /dev/null
@@ -0,0 +1,21 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0389'); -- miker
+
+-- Making this a global_flag (UI accessible) instead of an internal_flag
+INSERT INTO config.global_flag (name, label, enabled)
+    VALUES (
+        'circ.holds.empty_issuance_ok',
+        oils_i18n_gettext(
+            'circ.holds.empty_issuance_ok',
+            'Holds: Allow holds on empty issuances',
+            'cgf',
+            'label'
+        ),
+        TRUE
+    );
+
+INSERT INTO permission.perm_list (code, description) VALUES ('ISSUANCE_HOLDS', 'Allow a user to place holds on serials issuances');
+
+COMMIT;
+