From: miker Date: Wed, 8 Sep 2010 20:46:27 +0000 (+0000) Subject: Middle Layer work to enable Issuance (type I) holds; also cleaned up support for... X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=a6cbe99c22370cc0d0f0d571d8c4e7d21c1c6905;p=evergreen%2Fbjwebb.git Middle Layer work to enable Issuance (type I) holds; also cleaned up support for F(orce) and R(ecall) copy-type holds, UI pending git-svn-id: svn://svn.open-ils.org/ILS/trunk@17527 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Circ/Holds.pm b/Open-ILS/src/perlmods/OpenILS/Application/Circ/Holds.pm index 4333f4c1a..1d2817ed0 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Circ/Holds.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Circ/Holds.pm @@ -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} = { diff --git a/Open-ILS/src/perlmods/OpenILS/Const.pm b/Open-ILS/src/perlmods/OpenILS/Const.pm index 1838efe6c..281f465bd 100644 --- a/Open-ILS/src/perlmods/OpenILS/Const.pm +++ b/Open-ILS/src/perlmods/OpenILS/Const.pm @@ -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'; diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql index e22dd35d0..f4e12848e 100644 --- a/Open-ILS/src/sql/Pg/002.schema.config.sql +++ b/Open-ILS/src/sql/Pg/002.schema.config.sql @@ -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, diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql index e3bbc1013..80500c964 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -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 index 000000000..b10b6421f --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/0389.data.issuance-holds.sql @@ -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; +