[open-ils-commits] [GIT] Evergreen ILS branch master updated. eabd8160c6b88dd6e04e22b0d2b26e62f0d118cb
Evergreen Git
git at git.evergreen-ils.org
Tue Feb 16 12:47:49 EST 2016
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Evergreen ILS".
The branch, master has been updated
via eabd8160c6b88dd6e04e22b0d2b26e62f0d118cb (commit)
via 831a808746308a174f1209d3bec9c2284602798b (commit)
via 63205ed42a72a3cb6b3c7d7d3d76154dab40be1f (commit)
from c3fe2f44128f83720777052745015b2b7265887a (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit eabd8160c6b88dd6e04e22b0d2b26e62f0d118cb
Author: Jason Stephenson <jason at sigio.com>
Date: Sun Oct 11 14:30:11 2015 -0400
LP 1499123: Add release notes.
Signed-off-by: Jason Stephenson <jason at sigio.com>
Signed-off-by: Kathy Lussier <klussier at masslnc.org>
diff --git a/docs/RELEASE_NOTES_NEXT/Circulation/standing_penalty_ignore_proximity.txt b/docs/RELEASE_NOTES_NEXT/Circulation/standing_penalty_ignore_proximity.txt
new file mode 100644
index 0000000..7bdc5b8
--- /dev/null
+++ b/docs/RELEASE_NOTES_NEXT/Circulation/standing_penalty_ignore_proximity.txt
@@ -0,0 +1,30 @@
+Standing Penalty Ignore Proximity
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Standing penalties now have an ignore_proximity field that takes an
+integer value. When set, the value of this field represents the
+proximity from the user's home organizational unit where this penalty
+will be ignored for purposes of circulation and holds. Typical values
+for this field would be 0, 1, or 2 when using a standard hierarchy of
+Consortium -> System -> Branch -> Sublibrary/Bookmoblie. A value of 1
+would cause the penalty to be ignored at the user's home organization
+unit, it's parent and/or immediate child. A value of 2 should cause
+it to be ignored at the above as well as all sibling organizational
+units to the user's home. In all cases, a value of zero causes the
+penalty to be ignored at the user's home and to apply at all other
+organizational units. If the value of this field is left unset (or
+set to a negative value), the penalty will still take effect
+everywhere using the normal organizational unit and depth values. If
+you use a custom hierarchy, you will need to figure out any values
+greater than 0 on your own.
+
+The ignore_proximity does not affect where penalties are applied. It
+is used when determining whether or not a penalty blocks an activity
+at the current organizational unit or the organizational unit that
+owns the copy involved in the current transaction. For instance, if
+you set the ignore_proximity to 0 on patron exceeds overdue fines,
+then the patron will still be able to place holds on and checkout
+copies owned by their home organizational unit at their home
+organizational unit. They will not, however, be able to receive
+copies form other organizational units, nor use other organizational
+units as a patron.
commit 831a808746308a174f1209d3bec9c2284602798b
Author: Jason Stephenson <jason at sigio.com>
Date: Sat Sep 26 11:42:35 2015 -0400
LP 1499123: Modify Perl code for csp.ignore_proximity field.
* Add get_org_unit_proximity function to AppUtils.
First, we add a helper function to OpenILS::Application::AppUtils that
returns the proximity value between a "from" org_unit and a "to"
org_unit. It takes a CStoreEditor and the ids of the two org_units as
arguments.
* Use csp.ignore_proximity in O::A::Circ::Circulate::Circulator.
Modify the check_hold_fulfill_blocks method of the Circulator object
to take the csp.ignore_proximity into account.
The new code first calculates the proximity of the circ_lib and the
copy's circ_lib with the patron's home_ou. It then modifies the main
query in the function to check if the csp object's ignore_proximity
is null or greater than either of the two calculated proximity values.
* Teach SIP::Patron about csp.ignore_proximity.
We modify SIP::Patron::flesh_user_penalties to not report penalties
within csp.ignore_proximity of the user's home_ou.
In order to have a notion of "here" for the proximity check, we modify
SIP::Patron->new to assign its authtoken argument, if any, to the
CStoreEditor. We then use this authtoken to retrieve the authsession
user so that we may use the authsession user's ws_ou or home_ou as a
context ou for penalty lookup and filtering based on the
csp.ignore_proximity in flesh_user_penalties. If we're not given the
authtoken, we fall back to using the patron's home_ou and the
penalty's context ou for the proximity lookup.
This assumes, of course, that the authsession user's ws_ou or home_ou
have a logical relationship with the desired transaction ou. For most
self-checks this will likely be true. For other uses of the SIP
protocol, this is less likely to be true.
* Add Perl live tests.
Add tests for basic checkout and hold functionality as well as for
the OpenILS::SIP::Patron->flesh_user_penalties() changes.
Signed-off-by: Jason Stephenson <jason at sigio.com>
Signed-off-by: Kathy Lussier <klussier at masslnc.org>
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm
index 3d0ec38..ab89ac4 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm
@@ -1496,6 +1496,23 @@ sub org_unit_ancestor_at_depth {
return ($resp) ? $resp->{id} : undef;
}
+# Returns the proximity value between two org units.
+sub get_org_unit_proximity {
+ my ($class, $e, $from_org, $to_org) = @_;
+ $e = OpenILS::Utils::CStoreEditor->new unless ($e);
+ my $r = $e->json_query(
+ {
+ select => {aoup => ['prox']},
+ from => 'aoup',
+ where => {from_org => $from_org, to_org => $to_org}
+ }
+ );
+ if (ref($r) eq 'ARRAY' && @$r) {
+ return $r->[0]->{prox};
+ }
+ return undef;
+}
+
# returns the user's configured locale as a string. Defaults to en-US if none is configured.
sub get_user_locale {
my($self, $user_id, $e) = @_;
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
index c729abc..befe1bc 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
@@ -1482,6 +1482,22 @@ sub bail_on_events {
sub check_hold_fulfill_blocks {
my $self = shift;
+ # With the addition of ignore_proximity in csp, we need to fetch
+ # the proximity of both the circ_lib and the copy's circ_lib to
+ # the patron's home_ou.
+ my ($ou_prox, $copy_prox);
+ my $home_ou = (ref($self->patron->home_ou)) ? $self->patron->home_ou->id : $self->patron->home_ou;
+ $ou_prox = $U->get_org_unit_proximity($self->editor, $home_ou, $self->circ_lib);
+ $ou_prox = -1 unless (defined($ou_prox));
+ my $copy_ou = (ref($self->copy->circ_lib)) ? $self->copy->circ_lib->id : $self->copy->circ_lib;
+ if ($copy_ou == $self->circ_lib) {
+ # Save us the time of an extra query.
+ $copy_prox = $ou_prox;
+ } else {
+ $copy_prox = $U->get_org_unit_proximity($self->editor, $home_ou, $copy_ou);
+ $copy_prox = -1 unless (defined($copy_prox));
+ }
+
# See if the user has any penalties applied that prevent hold fulfillment
my $pens = $self->editor->json_query({
select => {csp => ['name', 'label']},
@@ -1495,7 +1511,14 @@ sub check_hold_fulfill_blocks {
{stop_date => {'>' => 'now'}}
]
},
- '+csp' => {block_list => {'like' => '%FULFILL%'}}
+ '+csp' => {
+ block_list => {'like' => '%FULFILL%'},
+ '-or' => [
+ {ignore_proximity => undef},
+ {ignore_proximity => {'<' => $ou_prox}},
+ {ignore_proximity => {'<' => $copy_prox}}
+ ]
+ }
}
});
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/SIP/Patron.pm b/Open-ILS/src/perlmods/lib/OpenILS/SIP/Patron.pm
index ac4f05c..1600db1 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/SIP/Patron.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/SIP/Patron.pm
@@ -51,6 +51,12 @@ sub new {
syslog("LOG_DEBUG", "OILS: new OpenILS Patron(%s => %s): searching...", $key, $patron_id);
my $e = OpenILS::SIP->editor();
+ # Pass the authtoken, if any, to the editor so that we can use it
+ # to fake a context org_unit for the csp.ignore_proximity in
+ # flesh_user_penalties, below.
+ unless ($e->authtoken()) {
+ $e->authtoken($args{authtoken}) if ($args{authtoken});
+ }
my $usr_flesh = {
flesh => 2,
@@ -143,9 +149,22 @@ sub get_act_who {
sub flesh_user_penalties {
my ($self, $user, $e) = @_;
- $user->standing_penalties(
+ # Use the ws_ou or home_ou of the authsession user, if any, as a
+ # context org_unit for the penalties and the csp.ignore_proximity.
+ my $here;
+ if ($e->authtoken()) {
+ my $auth_usr = $e->checkauth();
+ if ($auth_usr) {
+ $here = $auth_usr->ws_ou() || $auth_usr->home_ou();
+ }
+ }
+
+ # Get the "raw" list of user's penalties and flesh the
+ # standing_penalty field, so we can filter them based on
+ # csp.ignore_proximity.
+ my $raw_penalties =
$e->search_actor_user_standing_penalty([
- {
+ {
usr => $user->id,
'-or' => [
@@ -158,21 +177,20 @@ sub flesh_user_penalties {
in => {
select => {
aou => [{
- column => 'id',
- transform => 'actor.org_unit_ancestors',
+ column => 'id',
+ transform => 'actor.org_unit_ancestors',
result_field => 'id'
}]
},
from => 'aou',
- # at this point, there is no concept of "here", so fetch penalties
- # for the patron's home lib plus ancestors
- where => {id => $user->home_ou},
+ # Use "here" or user's home_ou.
+ where => {id => ($here) ? $here : $user->home_ou},
distinct => 1
}
},
- # in addition to fines and excessive overdue penalties,
+ # in addition to fines and excessive overdue penalties,
# we only care about penalties that result in blocks
standing_penalty => {
in => {
@@ -187,8 +205,28 @@ sub flesh_user_penalties {
}
}
},
- ])
- );
+ {
+ flesh => 1,
+ flesh_fields => {ausp => ['standing_penalty']}
+ }
+ ]);
+ # We filter the raw penalties that apply into this array.
+ my $applied_penalties = [];
+ if (ref($raw_penalties) eq 'ARRAY' && @$raw_penalties) {
+ my $here_prox = ($here) ? $U->get_org_unit_proximity($e, $here, $user->home_ou())
+ : undef;
+ # Filter out those that do not apply and deflesh the standing_penalty.
+ $applied_penalties = [map
+ { $_->standing_penalty($_->standing_penalty->id()) }
+ grep {
+ !defined($_->standing_penalty->ignore_proximity())
+ || ((defined($here_prox))
+ ? $_->standing_penalty->ignore_proximity() < $here_prox
+ : $_->standing_penalty->ignore_proximity() <
+ $U->get_org_unit_proximity($e, $_->org_unit(), $user->home_ou()))
+ } @$raw_penalties];
+ }
+ $user->standing_penalties($applied_penalties);
}
sub id {
diff --git a/Open-ILS/src/perlmods/live_t/12-lp1499123_csp_ignore_proximity.t b/Open-ILS/src/perlmods/live_t/12-lp1499123_csp_ignore_proximity.t
new file mode 100644
index 0000000..2b993bc
--- /dev/null
+++ b/Open-ILS/src/perlmods/live_t/12-lp1499123_csp_ignore_proximity.t
@@ -0,0 +1,252 @@
+#!perl
+use strict; use warnings;
+
+use Test::More tests => 28;
+use Data::Dumper;
+
+diag("Test config.standing_penalty.ignore_proximity feature.");
+
+use OpenILS::Utils::TestUtils;
+use OpenILS::SIP::Patron;
+my $script = OpenILS::Utils::TestUtils->new();
+our $apputils = 'OpenILS::Application::AppUtils';
+
+use constant WORKSTATION_NAME => 'BR1-test-lp1499123_csp_ignore_proximity.t';
+use constant WORKSTATION_LIB => 4;
+
+# Because this may run multiple times, without a DB reload, we search
+# for the workstation before registering it.
+sub find_workstation {
+ my $r = $apputils->simplereq(
+ 'open-ils.actor',
+ 'open-ils.actor.workstation.list',
+ $script->authtoken,
+ WORKSTATION_LIB
+ );
+ if ($r->{&WORKSTATION_LIB}) {
+ return scalar(grep {$_->name() eq WORKSTATION_NAME} @{$r->{&WORKSTATION_LIB}});
+ }
+ return 0;
+}
+
+sub retrieve_staff_chr {
+ my $e = shift;
+ my $staff_chr = $e->retrieve_config_standing_penalty(25);
+ return $staff_chr;
+}
+
+sub update_staff_chr {
+ my $e = shift;
+ my $penalty = shift;
+ $e->xact_begin;
+ my $r = $e->update_config_standing_penalty($penalty) || $e->event();
+ if (ref($r)) {
+ $e->rollback();
+ } else {
+ $e->commit;
+ }
+ return $r;
+}
+
+sub retrieve_user_by_barcode {
+ my $barcode = shift;
+ return $apputils->simplereq(
+ 'open-ils.actor',
+ 'open-ils.actor.user.fleshed.retrieve_by_barcode',
+ $script->authtoken,
+ $barcode
+ );
+}
+
+sub retrieve_copy_by_barcode {
+ my $editor = shift;
+ my $barcode = shift;
+ my $r = $editor->search_asset_copy({barcode => $barcode});
+ if (ref($r) eq 'ARRAY' && @$r) {
+ return $r->[0];
+ }
+ return undef;
+}
+
+sub apply_staff_chr_to_patron {
+ my ($staff, $patron) = @_;
+ my $penalty = Fieldmapper::actor::user_standing_penalty->new();
+ $penalty->standing_penalty(25);
+ $penalty->usr($patron->id());
+ $penalty->set_date('now');
+ $penalty->staff($staff->id());
+ $penalty->org_unit(1); # Consortium-wide.
+ $penalty->note('LP 1499123 csp.ignore_proximity test');
+ my $r = $apputils->simplereq(
+ 'open-ils.actor',
+ 'open-ils.actor.user.penalty.apply',
+ $script->authtoken,
+ $penalty
+ );
+ if (ref($r)) {
+ undef($penalty);
+ } else {
+ $penalty->id($r);
+ }
+ return $penalty;
+}
+
+sub remove_staff_chr_from_patron {
+ my $penalty = shift;
+ return $apputils->simplereq(
+ 'open-ils.actor',
+ 'open-ils.actor.user.penalty.remove',
+ $script->authtoken,
+ $penalty
+ );
+}
+
+sub checkout_permit_test {
+ my $patron = shift;
+ my $copy_barcode = shift;
+ my $r = $apputils->simplereq(
+ 'open-ils.circ',
+ 'open-ils.circ.checkout.permit',
+ $script->authtoken,
+ {
+ patron => $patron->id(),
+ barcode => $copy_barcode
+ }
+ );
+ if (ref($r) eq 'HASH' && $r->{textcode} eq 'SUCCESS') {
+ return 1;
+ }
+ return 0;
+}
+
+sub copy_hold_permit_test {
+ my $editor = shift;
+ my $patron = shift;
+ my $copy_barcode = shift;
+ my $copy = retrieve_copy_by_barcode($editor, $copy_barcode);
+ if ($copy) {
+ my $r = $apputils->simplereq(
+ 'open-ils.circ',
+ 'open-ils.circ.title_hold.is_possible',
+ $script->authtoken,
+ {
+ patronid => $patron->id(),
+ pickup_lib => 4,
+ copy_id => $copy->id(),
+ hold_type => 'C'
+ }
+ );
+ if (ref($r) && defined $r->{success}) {
+ return $r->{success};
+ }
+ }
+ return undef;
+}
+
+sub patron_sip_test {
+ my $patron_id = shift;
+ my $patron = OpenILS::SIP::Patron->new(usr => $patron_id, authtoken => $script->authtoken);
+ return scalar(@{$patron->{user}->standing_penalties()});
+}
+
+# In concerto, we need to register a workstation.
+$script->authenticate({
+ username => 'admin',
+ password => 'demo123',
+ type => 'staff',
+});
+ok($script->authtoken, 'Initial Login');
+
+SKIP: {
+ my $ws = find_workstation();
+ skip 'Workstation exists', 1 if ($ws);
+ $ws = $script->register_workstation(WORKSTATION_NAME, WORKSTATION_LIB) unless ($ws);
+ ok(! ref $ws, 'Registered a new workstation');
+}
+
+$script->logout();
+$script->authenticate({
+ username => 'admin',
+ password => 'demo123',
+ type => 'staff',
+ workstation => WORKSTATION_NAME
+});
+ok($script->authtoken, 'Login with workstaion');
+
+# Get a CStoreEditor for later use.
+my $editor = $script->editor(authtoken=>$script->authtoken);
+my $staff = $editor->checkauth();
+ok(ref($staff), 'Got a staff user');
+
+# We retrieve STAFF_CHR block and check that it has an undefined
+# ignore_proximity.
+my $staff_chr = retrieve_staff_chr($editor);
+isa_ok($staff_chr, 'Fieldmapper::config::standing_penalty', 'STAFF_CHR');
+is($staff_chr->name, 'STAFF_CHR', 'Penalty name is STAFF_CHR');
+is($staff_chr->ignore_proximity, undef, 'STAFF_CHR ignore_proximity is undefined');
+
+# We set the ignore_proximity to 0.
+$staff_chr->ignore_proximity(0);
+ok(! ref update_staff_chr($editor, $staff_chr), 'Update of STAFF_CHR');
+
+# We need a patron with no penalties to test holds and circulation.
+my $patron = retrieve_user_by_barcode("99999350419");
+isa_ok($patron, 'Fieldmapper::actor::user', 'Patron');
+
+# Patron should have no penalties.
+ok(! scalar(@{$patron->standing_penalties()}), 'Patron has no penalties');
+
+# Add the STAFF_CHR to the patron
+my $penalty = apply_staff_chr_to_patron($staff, $patron);
+ok(ref $penalty, 'Added STAFF_CHR to patron');
+is(patron_sip_test($patron->id()), 0, 'SIP says patron has no penalties');
+
+# See if we can place a hold on a copy owned by BR1.
+is(copy_hold_permit_test($editor, $patron, "CONC4300036"), 1, 'Can place hold on copy from BR1');
+# We should not be able to place a hold on a copy owned by a different branch.
+is(copy_hold_permit_test($editor, $patron, "CONC51000636"), 0, 'Cannot place hold on copy from BR2');
+
+# See if we can check out a copy owned by branch 4 out to the patron.
+# This should succeed.
+ok(checkout_permit_test($patron, "CONC4300036"), 'Can checkout copy from BR1');
+
+# We should not be able to checkout a copy owned by a different branch.
+ok(!checkout_permit_test($patron, "CONC51000636"), 'Cannot checkout copy from BR2');
+
+# We reset the ignore_proximity of STAFF_CHR.
+$staff_chr->clear_ignore_proximity();
+ok(! ref update_staff_chr($editor, $staff_chr), 'Reset of STAFF_CHR');
+is(patron_sip_test($patron->id()), 1, 'SIP says patron has one penalty');
+
+# See if we can place a hold on a copy owned by BR1.
+is(copy_hold_permit_test($editor, $patron, "CONC4300036"), 0, 'Cannot place hold on copy from BR1');
+# We should not be able to place a hold on a copy owned by a different branch.
+is(copy_hold_permit_test($editor, $patron, "CONC51000636"), 0, 'Cannot place hold on copy from BR2');
+
+# See if we can check out a copy owned by branch 4 out to the patron.
+# This should succeed.
+ok(!checkout_permit_test($patron, "CONC4300036"), 'Cannot checkout copy from BR1');
+
+# We should not be able to checkout a copy owned by a different branch.
+ok(!checkout_permit_test($patron, "CONC51000636"), 'Cannot checkout copy from BR2');
+
+# We remove the STAFF_CHR from our test patron.
+my $r = remove_staff_chr_from_patron($penalty);
+ok( ! ref $r, 'STAFF_CHR removed from patron');
+
+# Do the checks again, all should pass.
+is(patron_sip_test($patron->id()), 0, 'SIP says patron has no penalties');
+
+# See if we can place a hold on a copy owned by BR1.
+is(copy_hold_permit_test($editor, $patron, "CONC4300036"), 1, 'Can place hold on copy from BR1');
+# We should now be able to place a hold on a copy owned by a different branch.
+is(copy_hold_permit_test($editor, $patron, "CONC51000636"), 1, 'Can place hold on copy from BR2');
+
+# See if we can check out a copy owned by branch 4 out to the patron.
+# This should succeed.
+ok(checkout_permit_test($patron, "CONC4300036"), 'Can checkout copy from BR1');
+
+# We should not be able to checkout a copy owned by a different branch.
+ok(checkout_permit_test($patron, "CONC51000636"), 'Can checkout copy from BR2');
+
+$script->logout();
commit 63205ed42a72a3cb6b3c7d7d3d76154dab40be1f
Author: Jason Stephenson <jason at sigio.com>
Date: Thu Sep 24 20:35:50 2015 -0400
LP 1499123: Add ignore_proximity to config.standing_penalty.
This commit adds the integer column ignore_proximity to the
config.standing_penalty table. It also adds the column to the
csp class entry in the IDL.
It also modifies the action.hold_permit_test() function from
110.hold_matrix.sql to use the ignore_proximity field from
config.standing_penalty when checking the user's penalties to see if
they block the hold.
We also modify the action.item_user_circ_test() function from
100.circ_matrix.sql to use the ignore_proximity field from the
config.standing_penalty table when checking to see if the user's
penalties block the circulation.
Signed-off-by: Jason Stephenson <jason at sigio.com>
Signed-off-by: Kathy Lussier <klussier at masslnc.org>
diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index 75c7f12..421b967 100644
--- a/Open-ILS/examples/fm_IDL.xml
+++ b/Open-ILS/examples/fm_IDL.xml
@@ -3758,6 +3758,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
<field name="block_list" reporter:datatype="text"/>
<field name="staff_alert" reporter:datatype="bool"/>
<field name="org_depth" reporter:datatype="int"/>
+ <field name="ignore_proximity" reporter:datatype="int"/>
</fields>
<links/>
<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql
index 5381cfd..f46affe 100644
--- a/Open-ILS/src/sql/Pg/002.schema.config.sql
+++ b/Open-ILS/src/sql/Pg/002.schema.config.sql
@@ -128,7 +128,8 @@ CREATE TABLE config.standing_penalty (
label TEXT NOT NULL,
block_list TEXT,
staff_alert BOOL NOT NULL DEFAULT FALSE,
- org_depth INTEGER
+ org_depth INTEGER,
+ ignore_proximity INTEGER
);
CREATE TABLE config.xml_transform (
diff --git a/Open-ILS/src/sql/Pg/100.circ_matrix.sql b/Open-ILS/src/sql/Pg/100.circ_matrix.sql
index 837c34e..4226a27 100644
--- a/Open-ILS/src/sql/Pg/100.circ_matrix.sql
+++ b/Open-ILS/src/sql/Pg/100.circ_matrix.sql
@@ -427,6 +427,8 @@ DECLARE
items_out INT;
context_org_list INT[];
done BOOL := FALSE;
+ item_prox INT;
+ home_prox INT;
BEGIN
-- Assume success unless we hit a failure condition
result.success := TRUE;
@@ -522,6 +524,12 @@ BEGIN
-- Use Circ OU for penalties and such
SELECT INTO context_org_list ARRAY_AGG(id) FROM actor.org_unit_full_path( circ_ou );
+ -- Proximity of user's home_ou to circ_ou to see if penalties should be ignored.
+ SELECT INTO home_prox prox FROM actor.org_unit_proximity WHERE from_org = user_object.home_ou AND to_org = circ_ou;
+
+ -- Proximity of user's home_ou to item circ_lib to see if penalties should be ignored.
+ SELECT INTO item_prox prox FROM actor.org_unit_proximity WHERE from_org = user_object.home_ou AND to_org = item_object.circ_lib;
+
IF renewal THEN
penalty_type = '%RENEW%';
ELSE
@@ -535,6 +543,9 @@ BEGIN
WHERE usr = match_user
AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
AND (usp.stop_date IS NULL or usp.stop_date > NOW())
+ AND (csp.ignore_proximity IS NULL
+ OR csp.ignore_proximity < home_prox
+ OR csp.ignore_proximity < item_prox)
AND csp.block_list LIKE penalty_type LOOP
result.fail_part := standing_penalty.name;
diff --git a/Open-ILS/src/sql/Pg/110.hold_matrix.sql b/Open-ILS/src/sql/Pg/110.hold_matrix.sql
index 8f6518b..fe17a9a 100644
--- a/Open-ILS/src/sql/Pg/110.hold_matrix.sql
+++ b/Open-ILS/src/sql/Pg/110.hold_matrix.sql
@@ -241,6 +241,8 @@ DECLARE
hold_penalty TEXT;
v_pickup_ou ALIAS FOR pickup_ou;
v_request_ou ALIAS FOR request_ou;
+ item_prox INT;
+ pickup_prox INT;
BEGIN
SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
SELECT INTO context_org_list ARRAY_AGG(id) FROM actor.org_unit_full_path( v_pickup_ou );
@@ -358,6 +360,15 @@ BEGIN
END IF;
END IF;
+ -- Proximity of user's home_ou to the pickup_lib to see if penalty should be ignored.
+ SELECT INTO pickup_prox prox FROM actor.org_unit_proximity WHERE from_org = user_object.home_ou AND to_org = v_pickup_ou;
+ -- Proximity of user's home_ou to the items' lib to see if penalty should be ignored.
+ IF hold_test.distance_is_from_owner THEN
+ SELECT INTO item_prox prox FROM actor.org_unit_proximity WHERE from_org = user_object.home_ou AND to_org = item_cn_object.owning_lib;
+ ELSE
+ SELECT INTO item_prox prox FROM actor.org_unit_proximity WHERE from_org = user_object.home_ou AND to_org = item_object.circ_lib;
+ END IF;
+
FOR standing_penalty IN
SELECT DISTINCT csp.*
FROM actor.usr_standing_penalty usp
@@ -365,6 +376,8 @@ BEGIN
WHERE usr = match_user
AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
AND (usp.stop_date IS NULL or usp.stop_date > NOW())
+ AND (csp.ignore_proximity IS NULL OR csp.ignore_proximity < item_prox
+ OR csp.ignore_proximity < pickup_prox)
AND csp.block_list LIKE '%' || hold_penalty || '%' LOOP
result.fail_part := standing_penalty.name;
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.config.standing_penalty.ignore_proximity.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.config.standing_penalty.ignore_proximity.sql
new file mode 100644
index 0000000..615350e
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.config.standing_penalty.ignore_proximity.sql
@@ -0,0 +1,478 @@
+BEGIN;
+
+--SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+ALTER TABLE config.standing_penalty
+ ADD COLUMN ignore_proximity INTEGER;
+
+CREATE OR REPLACE FUNCTION action.hold_request_permit_test( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT, retargetting BOOL ) RETURNS SETOF action.matrix_test_result AS $func$
+DECLARE
+ matchpoint_id INT;
+ user_object actor.usr%ROWTYPE;
+ age_protect_object config.rule_age_hold_protect%ROWTYPE;
+ standing_penalty config.standing_penalty%ROWTYPE;
+ transit_range_ou_type actor.org_unit_type%ROWTYPE;
+ transit_source actor.org_unit%ROWTYPE;
+ item_object asset.copy%ROWTYPE;
+ item_cn_object asset.call_number%ROWTYPE;
+ item_status_object config.copy_status%ROWTYPE;
+ item_location_object asset.copy_location%ROWTYPE;
+ ou_skip actor.org_unit_setting%ROWTYPE;
+ result action.matrix_test_result;
+ hold_test config.hold_matrix_matchpoint%ROWTYPE;
+ use_active_date TEXT;
+ age_protect_date TIMESTAMP WITH TIME ZONE;
+ hold_count INT;
+ hold_transit_prox INT;
+ frozen_hold_count INT;
+ context_org_list INT[];
+ done BOOL := FALSE;
+ hold_penalty TEXT;
+ v_pickup_ou ALIAS FOR pickup_ou;
+ v_request_ou ALIAS FOR request_ou;
+ item_prox INT;
+ pickup_prox INT;
+BEGIN
+ SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
+ SELECT INTO context_org_list ARRAY_AGG(id) FROM actor.org_unit_full_path( v_pickup_ou );
+
+ result.success := TRUE;
+
+ -- The HOLD penalty block only applies to new holds.
+ -- The CAPTURE penalty block applies to existing holds.
+ hold_penalty := 'HOLD';
+ IF retargetting THEN
+ hold_penalty := 'CAPTURE';
+ END IF;
+
+ -- Fail if we couldn't find a user
+ IF user_object.id IS NULL THEN
+ result.fail_part := 'no_user';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ RETURN;
+ END IF;
+
+ SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
+
+ -- Fail if we couldn't find a copy
+ IF item_object.id IS NULL THEN
+ result.fail_part := 'no_item';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ RETURN;
+ END IF;
+
+ SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(v_pickup_ou, v_request_ou, match_item, match_user, match_requestor);
+ result.matchpoint := matchpoint_id;
+
+ SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
+
+ -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
+ IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
+ result.fail_part := 'circ.holds.target_skip_me';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ RETURN;
+ END IF;
+
+ -- Fail if user is barred
+ IF user_object.barred IS TRUE THEN
+ result.fail_part := 'actor.usr.barred';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ RETURN;
+ END IF;
+
+ SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
+ SELECT INTO item_status_object * FROM config.copy_status WHERE id = item_object.status;
+ SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
+
+ -- Fail if we couldn't find any matchpoint (requires a default)
+ IF matchpoint_id IS NULL THEN
+ result.fail_part := 'no_matchpoint';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ RETURN;
+ END IF;
+
+ SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
+
+ IF hold_test.holdable IS FALSE THEN
+ result.fail_part := 'config.hold_matrix_test.holdable';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ END IF;
+
+ IF item_object.holdable IS FALSE THEN
+ result.fail_part := 'item.holdable';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ END IF;
+
+ IF item_status_object.holdable IS FALSE THEN
+ result.fail_part := 'status.holdable';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ END IF;
+
+ IF item_location_object.holdable IS FALSE THEN
+ result.fail_part := 'location.holdable';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ END IF;
+
+ IF hold_test.transit_range IS NOT NULL THEN
+ SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
+ IF hold_test.distance_is_from_owner THEN
+ SELECT INTO transit_source ou.* FROM actor.org_unit ou JOIN asset.call_number cn ON (cn.owning_lib = ou.id) WHERE cn.id = item_object.call_number;
+ ELSE
+ SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
+ END IF;
+
+ PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = v_pickup_ou;
+
+ IF NOT FOUND THEN
+ result.fail_part := 'transit_range';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ END IF;
+ END IF;
+
+ -- Proximity of user's home_ou to the pickup_lib to see if penalty should be ignored.
+ SELECT INTO pickup_prox prox FROM actor.org_unit_proximity WHERE from_org = user_object.home_ou AND to_org = v_pickup_ou;
+ -- Proximity of user's home_ou to the items' lib to see if penalty should be ignored.
+ IF hold_test.distance_is_from_owner THEN
+ SELECT INTO item_prox prox FROM actor.org_unit_proximity WHERE from_org = user_object.home_ou AND to_org = item_cn_object.owning_lib;
+ ELSE
+ SELECT INTO item_prox prox FROM actor.org_unit_proximity WHERE from_org = user_object.home_ou AND to_org = item_object.circ_lib;
+ END IF;
+
+ FOR standing_penalty IN
+ SELECT DISTINCT csp.*
+ FROM actor.usr_standing_penalty usp
+ JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
+ WHERE usr = match_user
+ AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
+ AND (usp.stop_date IS NULL or usp.stop_date > NOW())
+ AND (csp.ignore_proximity IS NULL OR csp.ignore_proximity < item_prox
+ OR csp.ignore_proximity < pickup_prox)
+ AND csp.block_list LIKE '%' || hold_penalty || '%' LOOP
+
+ result.fail_part := standing_penalty.name;
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ END LOOP;
+
+ IF hold_test.stop_blocked_user IS TRUE THEN
+ FOR standing_penalty IN
+ SELECT DISTINCT csp.*
+ FROM actor.usr_standing_penalty usp
+ JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
+ WHERE usr = match_user
+ AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
+ AND (usp.stop_date IS NULL or usp.stop_date > NOW())
+ AND csp.block_list LIKE '%CIRC%' LOOP
+
+ result.fail_part := standing_penalty.name;
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ END LOOP;
+ END IF;
+
+ IF hold_test.max_holds IS NOT NULL AND NOT retargetting THEN
+ SELECT INTO hold_count COUNT(*)
+ FROM action.hold_request
+ WHERE usr = match_user
+ AND fulfillment_time IS NULL
+ AND cancel_time IS NULL
+ AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
+
+ IF hold_count >= hold_test.max_holds THEN
+ result.fail_part := 'config.hold_matrix_test.max_holds';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ END IF;
+ END IF;
+
+ IF item_object.age_protect IS NOT NULL THEN
+ SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
+ IF hold_test.distance_is_from_owner THEN
+ SELECT INTO use_active_date value FROM actor.org_unit_ancestor_setting('circ.holds.age_protect.active_date', item_cn_object.owning_lib);
+ ELSE
+ SELECT INTO use_active_date value FROM actor.org_unit_ancestor_setting('circ.holds.age_protect.active_date', item_object.circ_lib);
+ END IF;
+ IF use_active_date = 'true' THEN
+ age_protect_date := COALESCE(item_object.active_date, NOW());
+ ELSE
+ age_protect_date := item_object.create_date;
+ END IF;
+ IF age_protect_date + age_protect_object.age > NOW() THEN
+ IF hold_test.distance_is_from_owner THEN
+ SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
+ SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = v_pickup_ou;
+ ELSE
+ SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = v_pickup_ou;
+ END IF;
+
+ IF hold_transit_prox > age_protect_object.prox THEN
+ result.fail_part := 'config.rule_age_hold_protect.prox';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ END IF;
+ END IF;
+ END IF;
+
+ IF NOT done THEN
+ RETURN NEXT result;
+ END IF;
+
+ RETURN;
+END;
+$func$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION action.item_user_circ_test( circ_ou INT, match_item BIGINT, match_user INT, renewal BOOL ) RETURNS SETOF action.circ_matrix_test_result AS $func$
+DECLARE
+ user_object actor.usr%ROWTYPE;
+ standing_penalty config.standing_penalty%ROWTYPE;
+ item_object asset.copy%ROWTYPE;
+ item_status_object config.copy_status%ROWTYPE;
+ item_location_object asset.copy_location%ROWTYPE;
+ result action.circ_matrix_test_result;
+ circ_test action.found_circ_matrix_matchpoint;
+ circ_matchpoint config.circ_matrix_matchpoint%ROWTYPE;
+ circ_limit_set config.circ_limit_set%ROWTYPE;
+ hold_ratio action.hold_stats%ROWTYPE;
+ penalty_type TEXT;
+ items_out INT;
+ context_org_list INT[];
+ done BOOL := FALSE;
+ item_prox INT;
+ home_prox INT;
+BEGIN
+ -- Assume success unless we hit a failure condition
+ result.success := TRUE;
+
+ -- Need user info to look up matchpoints
+ SELECT INTO user_object * FROM actor.usr WHERE id = match_user AND NOT deleted;
+
+ -- (Insta)Fail if we couldn't find the user
+ IF user_object.id IS NULL THEN
+ result.fail_part := 'no_user';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ RETURN;
+ END IF;
+
+ -- Need item info to look up matchpoints
+ SELECT INTO item_object * FROM asset.copy WHERE id = match_item AND NOT deleted;
+
+ -- (Insta)Fail if we couldn't find the item
+ IF item_object.id IS NULL THEN
+ result.fail_part := 'no_item';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ RETURN;
+ END IF;
+
+ SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, item_object, user_object, renewal);
+
+ circ_matchpoint := circ_test.matchpoint;
+ result.matchpoint := circ_matchpoint.id;
+ result.circulate := circ_matchpoint.circulate;
+ result.duration_rule := circ_matchpoint.duration_rule;
+ result.recurring_fine_rule := circ_matchpoint.recurring_fine_rule;
+ result.max_fine_rule := circ_matchpoint.max_fine_rule;
+ result.hard_due_date := circ_matchpoint.hard_due_date;
+ result.renewals := circ_matchpoint.renewals;
+ result.grace_period := circ_matchpoint.grace_period;
+ result.buildrows := circ_test.buildrows;
+
+ -- (Insta)Fail if we couldn't find a matchpoint
+ IF circ_test.success = false THEN
+ result.fail_part := 'no_matchpoint';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ RETURN;
+ END IF;
+
+ -- All failures before this point are non-recoverable
+ -- Below this point are possibly overridable failures
+
+ -- Fail if the user is barred
+ IF user_object.barred IS TRUE THEN
+ result.fail_part := 'actor.usr.barred';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ END IF;
+
+ -- Fail if the item can't circulate
+ IF item_object.circulate IS FALSE THEN
+ result.fail_part := 'asset.copy.circulate';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ END IF;
+
+ -- Fail if the item isn't in a circulateable status on a non-renewal
+ IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN
+ result.fail_part := 'asset.copy.status';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ -- Alternately, fail if the item isn't checked out on a renewal
+ ELSIF renewal AND item_object.status <> 1 THEN
+ result.fail_part := 'asset.copy.status';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ END IF;
+
+ -- Fail if the item can't circulate because of the shelving location
+ SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
+ IF item_location_object.circulate IS FALSE THEN
+ result.fail_part := 'asset.copy_location.circulate';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ END IF;
+
+ -- Use Circ OU for penalties and such
+ SELECT INTO context_org_list ARRAY_AGG(id) FROM actor.org_unit_full_path( circ_ou );
+
+ -- Proximity of user's home_ou to circ_ou to see if penalties should be ignored.
+ SELECT INTO home_prox prox FROM actor.org_unit_proximity WHERE from_org = user_object.home_ou AND to_org = circ_ou;
+
+ -- Proximity of user's home_ou to item circ_lib to see if penalties should be ignored.
+ SELECT INTO item_prox prox FROM actor.org_unit_proximity WHERE from_org = user_object.home_ou AND to_org = item_object.circ_lib;
+
+ IF renewal THEN
+ penalty_type = '%RENEW%';
+ ELSE
+ penalty_type = '%CIRC%';
+ END IF;
+
+ FOR standing_penalty IN
+ SELECT DISTINCT csp.*
+ FROM actor.usr_standing_penalty usp
+ JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
+ WHERE usr = match_user
+ AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
+ AND (usp.stop_date IS NULL or usp.stop_date > NOW())
+ AND (csp.ignore_proximity IS NULL
+ OR csp.ignore_proximity < home_prox
+ OR csp.ignore_proximity < item_prox)
+ AND csp.block_list LIKE penalty_type LOOP
+
+ result.fail_part := standing_penalty.name;
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ END LOOP;
+
+ -- Fail if the test is set to hard non-circulating
+ IF circ_matchpoint.circulate IS FALSE THEN
+ result.fail_part := 'config.circ_matrix_test.circulate';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ END IF;
+
+ -- Fail if the total copy-hold ratio is too low
+ IF circ_matchpoint.total_copy_hold_ratio IS NOT NULL THEN
+ SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
+ IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_matchpoint.total_copy_hold_ratio THEN
+ result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ END IF;
+ END IF;
+
+ -- Fail if the available copy-hold ratio is too low
+ IF circ_matchpoint.available_copy_hold_ratio IS NOT NULL THEN
+ IF hold_ratio.hold_count IS NULL THEN
+ SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
+ END IF;
+ IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_matchpoint.available_copy_hold_ratio THEN
+ result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ END IF;
+ END IF;
+
+ -- Fail if the user has too many items out by defined limit sets
+ FOR circ_limit_set IN SELECT ccls.* FROM config.circ_limit_set ccls
+ JOIN config.circ_matrix_limit_set_map ccmlsm ON ccmlsm.limit_set = ccls.id
+ WHERE ccmlsm.active AND ( ccmlsm.matchpoint = circ_matchpoint.id OR
+ ( ccmlsm.matchpoint IN (SELECT * FROM unnest(result.buildrows)) AND ccmlsm.fallthrough )
+ ) LOOP
+ IF circ_limit_set.items_out > 0 AND NOT renewal THEN
+ SELECT INTO context_org_list ARRAY_AGG(aou.id)
+ FROM actor.org_unit_full_path( circ_ou ) aou
+ JOIN actor.org_unit_type aout ON aou.ou_type = aout.id
+ WHERE aout.depth >= circ_limit_set.depth;
+ IF circ_limit_set.global THEN
+ WITH RECURSIVE descendant_depth AS (
+ SELECT ou.id,
+ ou.parent_ou
+ FROM actor.org_unit ou
+ WHERE ou.id IN (SELECT * FROM unnest(context_org_list))
+ UNION
+ SELECT ou.id,
+ ou.parent_ou
+ FROM actor.org_unit ou
+ JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
+ ) SELECT INTO context_org_list ARRAY_AGG(ou.id) FROM actor.org_unit ou JOIN descendant_depth USING (id);
+ END IF;
+ SELECT INTO items_out COUNT(DISTINCT circ.id)
+ FROM action.circulation circ
+ JOIN asset.copy copy ON (copy.id = circ.target_copy)
+ LEFT JOIN action.circulation_limit_group_map aclgm ON (circ.id = aclgm.circ)
+ WHERE circ.usr = match_user
+ AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
+ AND circ.checkin_time IS NULL
+ AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
+ AND (copy.circ_modifier IN (SELECT circ_mod FROM config.circ_limit_set_circ_mod_map WHERE limit_set = circ_limit_set.id)
+ OR copy.location IN (SELECT copy_loc FROM config.circ_limit_set_copy_loc_map WHERE limit_set = circ_limit_set.id)
+ OR aclgm.limit_group IN (SELECT limit_group FROM config.circ_limit_set_group_map WHERE limit_set = circ_limit_set.id)
+ );
+ IF items_out >= circ_limit_set.items_out THEN
+ result.fail_part := 'config.circ_matrix_circ_mod_test';
+ result.success := FALSE;
+ done := TRUE;
+ RETURN NEXT result;
+ END IF;
+ END IF;
+ SELECT INTO result.limit_groups result.limit_groups || ARRAY_AGG(limit_group) FROM config.circ_limit_set_group_map WHERE limit_set = circ_limit_set.id AND NOT check_only;
+ END LOOP;
+
+ -- If we passed everything, return the successful matchpoint
+ IF NOT done THEN
+ RETURN NEXT result;
+ END IF;
+
+ RETURN;
+END;
+$func$ LANGUAGE plpgsql;
+
+COMMIT;
-----------------------------------------------------------------------
Summary of changes:
Open-ILS/examples/fm_IDL.xml | 1 +
.../perlmods/lib/OpenILS/Application/AppUtils.pm | 17 +
.../lib/OpenILS/Application/Circ/Circulate.pm | 25 +-
Open-ILS/src/perlmods/lib/OpenILS/SIP/Patron.pm | 58 ++-
.../live_t/12-lp1499123_csp_ignore_proximity.t | 252 ++++++++++
Open-ILS/src/sql/Pg/002.schema.config.sql | 3 +-
Open-ILS/src/sql/Pg/100.circ_matrix.sql | 11 +
Open-ILS/src/sql/Pg/110.hold_matrix.sql | 13 +
...ma.config.standing_penalty.ignore_proximity.sql | 478 ++++++++++++++++++++
.../standing_penalty_ignore_proximity.txt | 30 ++
10 files changed, 876 insertions(+), 12 deletions(-)
create mode 100644 Open-ILS/src/perlmods/live_t/12-lp1499123_csp_ignore_proximity.t
create mode 100644 Open-ILS/src/sql/Pg/upgrade/XXXX.schema.config.standing_penalty.ignore_proximity.sql
create mode 100644 docs/RELEASE_NOTES_NEXT/Circulation/standing_penalty_ignore_proximity.txt
hooks/post-receive
--
Evergreen ILS
More information about the open-ils-commits
mailing list