[open-ils-commits] r19317 - in trunk/Open-ILS: examples src/sql/Pg src/sql/Pg/upgrade web/opac/locale/en-US web/templates/default/conify/global/config xul/staff_client/chrome/content/main (miker)
svn at svn.open-ils.org
svn at svn.open-ils.org
Fri Jan 28 09:58:40 EST 2011
Author: miker
Date: 2011-01-28 09:58:37 -0500 (Fri, 28 Jan 2011)
New Revision: 19317
Added:
trunk/Open-ILS/src/sql/Pg/099.matrix_weights.sql
trunk/Open-ILS/src/sql/Pg/upgrade/0479.schema.matrix_weights.sql
trunk/Open-ILS/web/templates/default/conify/global/config/circ_matrix_weights.tt2
trunk/Open-ILS/web/templates/default/conify/global/config/hold_matrix_weights.tt2
trunk/Open-ILS/web/templates/default/conify/global/config/weight_assoc.tt2
Modified:
trunk/Open-ILS/examples/fm_IDL.xml
trunk/Open-ILS/src/sql/Pg/006.schema.permissions.sql
trunk/Open-ILS/src/sql/Pg/020.schema.functions.sql
trunk/Open-ILS/src/sql/Pg/100.circ_matrix.sql
trunk/Open-ILS/src/sql/Pg/110.hold_matrix.sql
trunk/Open-ILS/src/sql/Pg/950.data.seed-values.sql
trunk/Open-ILS/src/sql/Pg/build-db.sh
trunk/Open-ILS/web/opac/locale/en-US/lang.dtd
trunk/Open-ILS/web/templates/default/conify/global/config/hold_matrix_matchpoint.tt2
trunk/Open-ILS/xul/staff_client/chrome/content/main/menu.js
trunk/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
Log:
Patch from Thomas Berezansky addressing logical importance of various circulation and hold matrix matchpoint components.
Previous to this commit, INDB circ and holds use a pre-defined weighting set for rule ordering. This can be changed via replacing the relevant "find" functions in the database, but this is not easily done for most people.
The weight set for circ matchpoints is obtained based on the context ou of the circ (aka, where the circ is happening).
The weight set for hold matchpoints is obtained based on the item's circ library (aka, where the item lives).
Optionally, add an enabled circ.holds.weight_owner_not_circ internal flag to have the weight set for hold matchpoints be obtained based on the item's owning library (owner of the call number).
TODO: discuss promotion of circ.holds.weight_owner_not_circ to a Global Flag; wikified or docbook'd documentation and use-case examples.
Modified: trunk/Open-ILS/examples/fm_IDL.xml
===================================================================
--- trunk/Open-ILS/examples/fm_IDL.xml 2011-01-27 20:58:41 UTC (rev 19316)
+++ trunk/Open-ILS/examples/fm_IDL.xml 2011-01-28 14:58:37 UTC (rev 19317)
@@ -1003,6 +1003,85 @@
</permacrud>
</class>
+ <class id="chmw" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::hold_matrix_weights" oils_persist:tablename="config.hold_matrix_weights" reporter:label="Hold Matrix Weights">
+ <fields oils_persist:primary="id" oils_persist:sequence="config.hold_matrix_weights_id_seq">
+ <field reporter:label="Hold Weights ID" name="id" reporter:datatype="id" reporter:selector="name"/>
+ <field reporter:label="Name" name="name" reporter:datatype="text"/>
+ <field reporter:label="User Home Library" name="user_home_ou" reporter:datatype="float"/>
+ <field reporter:label="Request Library" name="request_ou" reporter:datatype="float"/>
+ <field reporter:label="Pickup Library" name="pickup_ou" reporter:datatype="float"/>
+ <field reporter:label="Owning Library" name="item_owning_ou" reporter:datatype="float"/>
+ <field reporter:label="Item Circ Library" name="item_circ_ou" reporter:datatype="float"/>
+ <field reporter:label="User Permission Group" name="usr_grp" reporter:datatype="float"/>
+ <field reporter:label="Requestor Permission Group" name="requestor_grp" reporter:datatype="float"/>
+ <field reporter:label="Circulation Modifier" name="circ_modifier" oils_persist:primitive="string" reporter:datatype="float"/>
+ <field reporter:label="MARC Type" name="marc_type" oils_persist:primitive="string" reporter:datatype="float"/>
+ <field reporter:label="MARC Form" name="marc_form" oils_persist:primitive="string" reporter:datatype="float"/>
+ <field reporter:label="Videorecording Format" name="marc_vr_format" oils_persist:primitive="string" reporter:datatype="float"/>
+ <field reporter:label="Reference?" name="ref_flag" reporter:datatype="float"/>
+ </fields>
+ <links/>
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+ <actions>
+ <create permission="ADMIN_HOLD_MATRIX_MATCHPOINT" global_required="true"/>
+ <retrieve/>
+ <update permission="ADMIN_HOLD_MATRIX_MATCHPOINT" global_required="true"/>
+ <delete permission="ADMIN_HOLD_MATRIX_MATCHPOINT" global_required="true"/>
+ </actions>
+ </permacrud>
+ </class>
+
+ <class id="ccmw" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::circ_matrix_weights" oils_persist:tablename="config.circ_matrix_weights" reporter:label="Circ Matrix Weights">
+ <fields oils_persist:primary="id" oils_persist:sequence="config.circ_matrix_weights_id_seq">
+ <field reporter:label="Circ Weights ID" name="id" reporter:datatype="id" reporter:selector="name"/>
+ <field reporter:label="Name" name="name" reporter:datatype="text"/>
+ <field reporter:label="Org Unit" name="org_unit" reporter:datatype="float"/>
+ <field reporter:label="Copy Circ Lib" name="copy_circ_lib" reporter:datatype="float"/>
+ <field reporter:label="Copy Owning Lib" name="copy_owning_lib" reporter:datatype="float"/>
+ <field reporter:label="User Home Lib" name="user_home_ou" reporter:datatype="float"/>
+ <field reporter:label="Permission Group" name="grp" reporter:datatype="float"/>
+ <field reporter:label="Circulation Modifier" name="circ_modifier" reporter:datatype="float"/>
+ <field reporter:label="MARC Type" name="marc_type" reporter:datatype="float"/>
+ <field reporter:label="MARC Form" name="marc_form" reporter:datatype="float"/>
+ <field reporter:label="Videorecording Format" name="marc_vr_format" reporter:datatype="float"/>
+ <field reporter:label="Reference?" name="ref_flag" reporter:datatype="float"/>
+ <field reporter:label="User Age: Lower Bound" name="usr_age_lower_bound" reporter:datatype="float"/>
+ <field reporter:label="User Age: Upper Bound" name="usr_age_upper_bound" reporter:datatype="float"/>
+ </fields>
+ <links/>
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+ <actions>
+ <create permission="ADMIN_CIRC_MATRIX_MATCHPOINT" global_required="true"/>
+ <retrieve/>
+ <update permission="ADMIN_CIRC_MATRIX_MATCHPOINT" global_required="true"/>
+ <delete permission="ADMIN_CIRC_MATRIX_MATCHPOINT" global_required="true"/>
+ </actions>
+ </permacrud>
+ </class>
+
+ <class id="cwa" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::weight_assoc" oils_persist:tablename="config.weight_assoc" reporter:label="Matrix Weight Association">
+ <fields oils_persist:primary="id" oils_persist:sequence="config.weight_assoc_id_seq">
+ <field reporter:label="Assoc ID" name="id" reporter:datatype="id"/>
+ <field reporter:label="Active?" name="active" reporter:datatype="bool"/>
+ <field reporter:label="Org Unit" name="org_unit" reporter:datatype="org_unit"/>
+ <field reporter:label="Circ Weights" name="circ_weights" reporter:datatype="link"/>
+ <field reporter:label="Hold Weights" name="hold_weights" reporter:datatype="link"/>
+ </fields>
+ <links>
+ <link field="org_unit" reltype="has_a" key="id" map="" class="aou"/>
+ <link field="circ_weights" reltype="has_a" key="id" map="" class="ccmw"/>
+ <link field="hold_weights" reltype="has_a" key="id" map="" class="chmw"/>
+ </links>
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+ <actions>
+ <create permission="ADMIN_CIRC_MATRIX_MATCHPOINT ADMIN_HOLD_MATRIX_MATCHPOINT" context_field='org_unit'/>
+ <retrieve permission="ADMIN_CIRC_MATRIX_MATCHPOINT ADMIN_HOLD_MATRIX_MATCHPOINT VIEW_CIRC_MATRIX_MATCHPOINT VIEW_HOLD_MATRIX_MATCHPOINT" context_field='org_unit'/>
+ <update permission="ADMIN_CIRC_MATRIX_MATCHPOINT ADMIN_HOLD_MATRIX_MATCHPOINT" context_field='org_unit'/>
+ <delete permission="ADMIN_CIRC_MATRIX_MATCHPOINT ADMIN_HOLD_MATRIX_MATCHPOINT" context_field='org_unit'/>
+ </actions>
+ </permacrud>
+ </class>
+
<class id="chmm" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::hold_matrix_matchpoint" oils_persist:tablename="config.hold_matrix_matchpoint" reporter:label="Hold Matrix Matchpoint">
<fields oils_persist:primary="id" oils_persist:sequence="config.hold_matrix_matchpoint_id_seq">
<field reporter:label="Matchpoint ID" name="id" reporter:datatype="id"/>
@@ -1060,6 +1139,7 @@
<field reporter:label="Org Unit" name="org_unit" reporter:datatype="org_unit" oils_obj:required="true"/>
<field reporter:label="Copy Circ Lib" name="copy_circ_lib" reporter:datatype="org_unit"/>
<field reporter:label="Copy Owning Lib" name="copy_owning_lib" reporter:datatype="org_unit"/>
+ <field reporter:label="User Home Lib" name="user_home_ou" reporter:datatype="org_unit"/>
<field reporter:label="Permission Group" name="grp" reporter:datatype="link" oils_obj:required="true"/>
<field reporter:label="Circulation Modifier" name="circ_modifier" oils_persist:primitive="string" reporter:datatype="link"/>
<field reporter:label="MARC Type" name="marc_type" oils_persist:primitive="string" reporter:datatype="link"/>
@@ -1081,6 +1161,7 @@
<link field="org_unit" reltype="has_a" key="id" map="" class="aou"/>
<link field="copy_circ_lib" reltype="has_a" key="id" map="" class="aou"/>
<link field="copy_owning_lib" reltype="has_a" key="id" map="" class="aou"/>
+ <link field="user_home_ou" reltype="has_a" key="id" map="" class="aou"/>
<link field="grp" reltype="has_a" key="id" map="" class="pgt"/>
<link field="circ_modifier" reltype="has_a" key="code" map="" class="ccm"/>
<link field="marc_type" reltype="has_a" key="code" map="" class="citm"/>
Modified: trunk/Open-ILS/src/sql/Pg/006.schema.permissions.sql
===================================================================
--- trunk/Open-ILS/src/sql/Pg/006.schema.permissions.sql 2011-01-27 20:58:41 UTC (rev 19316)
+++ trunk/Open-ILS/src/sql/Pg/006.schema.permissions.sql 2011-01-28 14:58:37 UTC (rev 19317)
@@ -101,6 +101,27 @@
END, a.name;
$$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION permission.grp_ancestors_distance( INT ) RETURNS TABLE (id INT, distance INT) AS $$
+ WITH RECURSIVE grp_ancestors_distance(id, distance) AS (
+ SELECT $1, 0
+ UNION
+ SELECT pgt.parent, gad.distance+1
+ FROM permission.grp_tree pgt JOIN grp_ancestors_distance gad ON (pgt.id = gad.id)
+ WHERE pgt.parent IS NOT NULL
+ )
+ SELECT * FROM grp_ancestors_distance;
+$$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION permission.grp_descendants_distance( INT ) RETURNS TABLE (id INT, distance INT) AS $$
+ WITH RECURSIVE grp_descendants_distance(id, distance) AS (
+ SELECT $1, 0
+ UNION
+ SELECT pgt.id, gdd.distance+1
+ FROM permission.grp_tree pgt JOIN grp_descendants_distance gdd ON (pgt.parent = gdd.id)
+ )
+ SELECT * FROM grp_descendants_distance;
+$$ LANGUAGE SQL STABLE;
+
CREATE OR REPLACE FUNCTION permission.usr_perms ( INT ) RETURNS SETOF permission.usr_perm_map AS $$
SELECT DISTINCT ON (usr,perm) *
FROM (
Modified: trunk/Open-ILS/src/sql/Pg/020.schema.functions.sql
===================================================================
--- trunk/Open-ILS/src/sql/Pg/020.schema.functions.sql 2011-01-27 20:58:41 UTC (rev 19316)
+++ trunk/Open-ILS/src/sql/Pg/020.schema.functions.sql 2011-01-28 14:58:37 UTC (rev 19317)
@@ -224,6 +224,16 @@
) SELECT ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id);
$$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION actor.org_unit_descendants_distance( INT ) RETURNS TABLE (id INT, distance INT) AS $$
+ WITH RECURSIVE org_unit_descendants_distance(id, distance) AS (
+ SELECT $1, 0
+ UNION
+ SELECT ou.id, oudd.distance+1
+ FROM actor.org_unit ou JOIN org_unit_descendants_distance oudd ON (ou.parent_ou = oudd.id)
+ )
+ SELECT * FROM org_unit_descendants_distance;
+$$ LANGUAGE SQL STABLE;
+
CREATE OR REPLACE FUNCTION actor.org_unit_ancestors( INT ) RETURNS SETOF actor.org_unit AS $$
WITH RECURSIVE anscestor_depth AS (
SELECT ou.id,
@@ -247,6 +257,17 @@
ON x.ou_type = y.id AND y.depth = $2);
$$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION actor.org_unit_ancestors_distance( INT ) RETURNS TABLE (id INT, distance INT) AS $$
+ WITH RECURSIVE org_unit_ancestors_distance(id, distance) AS (
+ SELECT $1, 0
+ UNION
+ SELECT ou.parent_ou, ouad.distance+1
+ FROM actor.org_unit ou JOIN org_unit_ancestors_distance ouad ON (ou.id = ouad.id)
+ WHERE ou.parent_ou IS NOT NULL
+ )
+ SELECT * FROM org_unit_ancestors_distance;
+$$ LANGUAGE SQL STABLE;
+
CREATE OR REPLACE FUNCTION actor.org_unit_full_path ( INT ) RETURNS SETOF actor.org_unit AS $$
SELECT *
FROM actor.org_unit_ancestors($1)
Added: trunk/Open-ILS/src/sql/Pg/099.matrix_weights.sql
===================================================================
--- trunk/Open-ILS/src/sql/Pg/099.matrix_weights.sql (rev 0)
+++ trunk/Open-ILS/src/sql/Pg/099.matrix_weights.sql 2011-01-28 14:58:37 UTC (rev 19317)
@@ -0,0 +1,53 @@
+
+BEGIN;
+
+-- Circ Matrix Weights
+CREATE TABLE config.circ_matrix_weights (
+ id SERIAL PRIMARY KEY,
+ name TEXT NOT NULL UNIQUE,
+ org_unit NUMERIC(6,2) NOT NULL,
+ grp NUMERIC(6,2) NOT NULL,
+ circ_modifier NUMERIC(6,2) NOT NULL,
+ marc_type NUMERIC(6,2) NOT NULL,
+ marc_form NUMERIC(6,2) NOT NULL,
+ marc_vr_format NUMERIC(6,2) NOT NULL,
+ copy_circ_lib NUMERIC(6,2) NOT NULL,
+ copy_owning_lib NUMERIC(6,2) NOT NULL,
+ user_home_ou NUMERIC(6,2) NOT NULL,
+ ref_flag NUMERIC(6,2) NOT NULL,
+ juvenile_flag NUMERIC(6,2) NOT NULL,
+ is_renewal NUMERIC(6,2) NOT NULL,
+ usr_age_lower_bound NUMERIC(6,2) NOT NULL,
+ usr_age_upper_bound NUMERIC(6,2) NOT NULL
+);
+
+-- Hold Matrix Weights
+CREATE TABLE config.hold_matrix_weights (
+ id SERIAL PRIMARY KEY,
+ name TEXT NOT NULL UNIQUE,
+ user_home_ou NUMERIC(6,2) NOT NULL,
+ request_ou NUMERIC(6,2) NOT NULL,
+ pickup_ou NUMERIC(6,2) NOT NULL,
+ item_owning_ou NUMERIC(6,2) NOT NULL,
+ item_circ_ou NUMERIC(6,2) NOT NULL,
+ usr_grp NUMERIC(6,2) NOT NULL,
+ requestor_grp NUMERIC(6,2) NOT NULL,
+ circ_modifier NUMERIC(6,2) NOT NULL,
+ marc_type NUMERIC(6,2) NOT NULL,
+ marc_form NUMERIC(6,2) NOT NULL,
+ marc_vr_format NUMERIC(6,2) NOT NULL,
+ juvenile_flag NUMERIC(6,2) NOT NULL,
+ ref_flag NUMERIC(6,2) NOT NULL
+);
+
+-- Linking between weights and org units
+CREATE TABLE config.weight_assoc (
+ id SERIAL PRIMARY KEY,
+ active BOOL NOT NULL,
+ org_unit INT NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ circ_weights INT REFERENCES config.circ_matrix_weights (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
+ hold_weights INT REFERENCES config.hold_matrix_weights (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED
+);
+CREATE UNIQUE INDEX cwa_one_active_per_ou ON config.weight_assoc (org_unit) WHERE active;
+
+COMMIT;
Modified: trunk/Open-ILS/src/sql/Pg/100.circ_matrix.sql
===================================================================
--- trunk/Open-ILS/src/sql/Pg/100.circ_matrix.sql 2011-01-27 20:58:41 UTC (rev 19316)
+++ trunk/Open-ILS/src/sql/Pg/100.circ_matrix.sql 2011-01-28 14:58:37 UTC (rev 19317)
@@ -76,20 +76,6 @@
** developers focus on specific parts of the matrix.
**/
-
---
--- ****** Which ruleset and tests to use *******
---
--- * Most specific range for org_unit and grp wins.
---
--- * circ_modifier match takes precidence over marc_type match, if circ_modifier is set here
---
--- * marc_type is first checked against the circ_as_type from the copy, then the item type from the marc record
---
--- * If neither circ_modifier nor marc_type is set (both are NULLABLE) then the entry defines the default
--- ruleset and tests for the OU + group (like BOOK in PINES)
---
-
CREATE TABLE config.circ_matrix_matchpoint (
id SERIAL PRIMARY KEY,
active BOOL NOT NULL DEFAULT TRUE,
@@ -101,6 +87,7 @@
marc_vr_format TEXT REFERENCES config.videorecording_format_map (code) DEFERRABLE INITIALLY DEFERRED,
copy_circ_lib INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
copy_owning_lib INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
+ user_home_ou INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
ref_flag BOOL,
juvenile_flag BOOL,
is_renewal BOOL,
@@ -138,102 +125,116 @@
CREATE OR REPLACE FUNCTION action.find_circ_matrix_matchpoint( context_ou INT, match_item BIGINT, match_user INT, renewal BOOL ) RETURNS config.circ_matrix_matchpoint AS $func$
DECLARE
- current_group permission.grp_tree%ROWTYPE;
- user_object actor.usr%ROWTYPE;
- item_object asset.copy%ROWTYPE;
- cn_object asset.call_number%ROWTYPE;
- rec_descriptor metabib.rec_descriptor%ROWTYPE;
- current_mp config.circ_matrix_matchpoint%ROWTYPE;
- matchpoint config.circ_matrix_matchpoint%ROWTYPE;
+ user_object actor.usr%ROWTYPE;
+ item_object asset.copy%ROWTYPE;
+ cn_object asset.call_number%ROWTYPE;
+ rec_descriptor metabib.rec_descriptor%ROWTYPE;
+ matchpoint config.circ_matrix_matchpoint%ROWTYPE;
+ weights config.circ_matrix_weights%ROWTYPE;
+ user_age INTERVAL;
+ denominator INT;
BEGIN
- SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
- SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
- SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
- SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r JOIN asset.call_number c USING (record) WHERE c.id = item_object.call_number;
- SELECT INTO current_group * FROM permission.grp_tree WHERE id = user_object.profile;
+ SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
+ SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
+ SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
+ SELECT INTO rec_descriptor * FROM metabib.rec_descriptor WHERE record = cn_object.record;
- LOOP
- -- for each potential matchpoint for this ou and group ...
- FOR current_mp IN
- SELECT m.*
- FROM config.circ_matrix_matchpoint m
- JOIN actor.org_unit_ancestors( context_ou ) d ON (m.org_unit = d.id)
- LEFT JOIN actor.org_unit_proximity p ON (p.from_org = context_ou AND p.to_org = d.id)
- WHERE m.grp = current_group.id
- AND m.active
- AND (m.copy_owning_lib IS NULL OR cn_object.owning_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_owning_lib) ))
- AND (m.copy_circ_lib IS NULL OR item_object.circ_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_circ_lib) ))
- ORDER BY CASE WHEN p.prox IS NULL THEN 999 ELSE p.prox END,
- CASE WHEN m.copy_owning_lib IS NOT NULL
- THEN 256 / ( SELECT COALESCE(prox, 255) + 1 FROM actor.org_unit_proximity WHERE to_org = cn_object.owning_lib AND from_org = m.copy_owning_lib LIMIT 1 )
- ELSE 0
- END +
- CASE WHEN m.copy_circ_lib IS NOT NULL
- THEN 256 / ( SELECT COALESCE(prox, 255) + 1 FROM actor.org_unit_proximity WHERE to_org = item_object.circ_lib AND from_org = m.copy_circ_lib LIMIT 1 )
- ELSE 0
- END +
- CASE WHEN m.is_renewal = renewal THEN 128 ELSE 0 END +
- CASE WHEN m.juvenile_flag IS NOT NULL THEN 64 ELSE 0 END +
- CASE WHEN m.circ_modifier IS NOT NULL THEN 32 ELSE 0 END +
- CASE WHEN m.marc_type IS NOT NULL THEN 16 ELSE 0 END +
- CASE WHEN m.marc_form IS NOT NULL THEN 8 ELSE 0 END +
- CASE WHEN m.marc_vr_format IS NOT NULL THEN 4 ELSE 0 END +
- CASE WHEN m.ref_flag IS NOT NULL THEN 2 ELSE 0 END +
- CASE WHEN m.usr_age_lower_bound IS NOT NULL THEN 0.5 ELSE 0 END +
- CASE WHEN m.usr_age_upper_bound IS NOT NULL THEN 0.5 ELSE 0 END DESC LOOP
+ -- Pre-generate this so we only calc it once
+ IF user_object.dob IS NOT NULL THEN
+ SELECT INTO user_age age(user_object.dob);
+ END IF;
- IF current_mp.is_renewal IS NOT NULL THEN
- CONTINUE WHEN current_mp.is_renewal <> renewal;
- END IF;
+ -- Grab the closest set circ weight setting.
+ SELECT INTO weights cw.*
+ FROM config.weight_assoc wa
+ JOIN config.circ_matrix_weights cw ON (cw.id = wa.circ_weights)
+ JOIN actor.org_unit_ancestors_distance( context_ou ) d ON (wa.org_unit = d.id)
+ WHERE active
+ ORDER BY d.distance
+ LIMIT 1;
- IF current_mp.circ_modifier IS NOT NULL THEN
- CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
- END IF;
+ -- No weights? Bad admin! Defaults to handle that anyway.
+ IF weights.id IS NULL THEN
+ weights.grp := 11;
+ weights.org_unit := 10;
+ weights.circ_modifier := 5;
+ weights.marc_type := 4;
+ weights.marc_form := 3;
+ weights.marc_vr_format := 2;
+ weights.copy_circ_lib := 8;
+ weights.copy_owning_lib := 8;
+ weights.user_home_ou := 8;
+ weights.ref_flag := 1;
+ weights.juvenile_flag := 6;
+ weights.is_renewal := 7;
+ weights.usr_age_lower_bound := 0;
+ weights.usr_age_upper_bound := 0;
+ END IF;
- IF current_mp.marc_type IS NOT NULL THEN
- IF item_object.circ_as_type IS NOT NULL THEN
- CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
- ELSE
- CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
- END IF;
- END IF;
+ -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
+ -- If you break your org tree with funky parenting this may be wrong
+ -- Note: This CTE is duplicated in the find_hold_matrix_matchpoint function, and it may be a good idea to split it off to a function
+ -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
+ WITH all_distance(distance) AS (
+ SELECT depth AS distance FROM actor.org_unit_type
+ UNION
+ SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
+ )
+ SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
- IF current_mp.marc_form IS NOT NULL THEN
- CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
- END IF;
+ -- Select the winning matchpoint into the matchpoint variable for returning
+ SELECT INTO matchpoint m.*
+ FROM config.circ_matrix_matchpoint m
+ /*LEFT*/ JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.grp = upgad.id
+ /*LEFT*/ JOIN actor.org_unit_ancestors_distance( context_ou ) ctoua ON m.org_unit = ctoua.id
+ LEFT JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) cnoua ON m.copy_owning_lib = cnoua.id
+ LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.copy_circ_lib = iooua.id
+ LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou ) uhoua ON m.user_home_ou = uhoua.id
+ WHERE m.active
+ -- Permission Groups
+ -- AND (m.grp IS NULL OR upgad.id IS NOT NULL) -- Optional Permission Group?
+ -- Org Units
+ -- AND (m.org_unit IS NULL OR ctoua.id IS NOT NULL) -- Optional Org Unit?
+ AND (m.copy_owning_lib IS NULL OR cnoua.id IS NOT NULL)
+ AND (m.copy_circ_lib IS NULL OR iooua.id IS NOT NULL)
+ AND (m.user_home_ou IS NULL OR uhoua.id IS NOT NULL)
+ -- Circ Type
+ AND (m.is_renewal IS NULL OR m.is_renewal = renewal)
+ -- Static User Checks
+ AND (m.juvenile_flag IS NULL OR m.juvenile_flag = user_object.juvenile)
+ AND (m.usr_age_lower_bound IS NULL OR (user_age IS NOT NULL AND m.usr_age_lower_bound < user_age))
+ AND (m.usr_age_upper_bound IS NULL OR (user_age IS NOT NULL AND m.usr_age_upper_bound > user_age))
+ -- Static Item Checks
+ AND (m.circ_modifier IS NULL OR m.circ_modifier = item_object.circ_modifier)
+ AND (m.marc_type IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
+ AND (m.marc_form IS NULL OR m.marc_form = rec_descriptor.item_form)
+ AND (m.marc_vr_format IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
+ AND (m.ref_flag IS NULL OR m.ref_flag = item_object.ref)
+ ORDER BY
+ -- Permission Groups
+ CASE WHEN upgad.distance IS NOT NULL THEN 2^(2*weights.grp - (upgad.distance/denominator)) ELSE 0 END +
+ -- Org Units
+ CASE WHEN ctoua.distance IS NOT NULL THEN 2^(2*weights.org_unit - (ctoua.distance/denominator)) ELSE 0 END +
+ CASE WHEN cnoua.distance IS NOT NULL THEN 2^(2*weights.copy_owning_lib - (cnoua.distance/denominator)) ELSE 0 END +
+ CASE WHEN iooua.distance IS NOT NULL THEN 2^(2*weights.copy_circ_lib - (iooua.distance/denominator)) ELSE 0 END +
+ CASE WHEN uhoua.distance IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0 END +
+ -- Circ Type -- Note: 4^x is equiv to 2^(2*x)
+ CASE WHEN m.is_renewal IS NOT NULL THEN 4^weights.is_renewal ELSE 0 END +
+ -- Static User Checks
+ CASE WHEN m.juvenile_flag IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0 END +
+ CASE WHEN m.usr_age_lower_bound IS NOT NULL THEN 4^weights.usr_age_lower_bound ELSE 0 END +
+ CASE WHEN m.usr_age_upper_bound IS NOT NULL THEN 4^weights.usr_age_upper_bound ELSE 0 END +
+ -- Static Item Checks
+ CASE WHEN m.circ_modifier IS NOT NULL THEN 4^weights.circ_modifier ELSE 0 END +
+ CASE WHEN m.marc_type IS NOT NULL THEN 4^weights.marc_type ELSE 0 END +
+ CASE WHEN m.marc_form IS NOT NULL THEN 4^weights.marc_form ELSE 0 END +
+ CASE WHEN m.marc_vr_format IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0 END +
+ CASE WHEN m.ref_flag IS NOT NULL THEN 4^weights.ref_flag ELSE 0 END DESC,
+ -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
+ -- This prevents "we changed the table order by updating a rule, and we started getting different results"
+ m.id;
- IF current_mp.marc_vr_format IS NOT NULL THEN
- CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
- END IF;
-
- IF current_mp.ref_flag IS NOT NULL THEN
- CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
- END IF;
-
- IF current_mp.juvenile_flag IS NOT NULL THEN
- CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
- END IF;
-
- IF current_mp.usr_age_lower_bound IS NOT NULL THEN
- CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_lower_bound < age(user_object.dob);
- END IF;
-
- IF current_mp.usr_age_upper_bound IS NOT NULL THEN
- CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_upper_bound > age(user_object.dob);
- END IF;
-
-
- -- everything was undefined or matched
- matchpoint = current_mp;
-
- EXIT WHEN matchpoint.id IS NOT NULL;
- END LOOP;
-
- EXIT WHEN current_group.parent IS NULL OR matchpoint.id IS NOT NULL;
-
- SELECT INTO current_group * FROM permission.grp_tree WHERE id = current_group.parent;
- END LOOP;
-
+ -- Return the entire matchpoint
RETURN matchpoint;
END;
$func$ LANGUAGE plpgsql;
Modified: trunk/Open-ILS/src/sql/Pg/110.hold_matrix.sql
===================================================================
--- trunk/Open-ILS/src/sql/Pg/110.hold_matrix.sql 2011-01-27 20:58:41 UTC (rev 19316)
+++ trunk/Open-ILS/src/sql/Pg/110.hold_matrix.sql 2011-01-28 14:58:37 UTC (rev 19317)
@@ -55,156 +55,147 @@
CONSTRAINT hous_once_per_grp_loc_mod_marc UNIQUE (user_home_ou, request_ou, pickup_ou, item_owning_ou, item_circ_ou, requestor_grp, usr_grp, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag, juvenile_flag)
);
-CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT ) RETURNS INT AS $func$
+CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint(pickup_ou integer, request_ou integer, match_item bigint, match_user integer, match_requestor integer)
+ RETURNS integer AS
+$func$
DECLARE
- current_requestor_group permission.grp_tree%ROWTYPE;
requestor_object actor.usr%ROWTYPE;
- user_object actor.usr%ROWTYPE;
- item_object asset.copy%ROWTYPE;
- item_cn_object asset.call_number%ROWTYPE;
- rec_descriptor metabib.rec_descriptor%ROWTYPE;
- current_mp_weight FLOAT;
- matchpoint_weight FLOAT;
- tmp_weight FLOAT;
- current_mp config.hold_matrix_matchpoint%ROWTYPE;
- matchpoint config.hold_matrix_matchpoint%ROWTYPE;
+ user_object actor.usr%ROWTYPE;
+ item_object asset.copy%ROWTYPE;
+ item_cn_object asset.call_number%ROWTYPE;
+ rec_descriptor metabib.rec_descriptor%ROWTYPE;
+ matchpoint config.hold_matrix_matchpoint%ROWTYPE;
+ weights config.hold_matrix_weights%ROWTYPE;
+ denominator INT;
BEGIN
- SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
- SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
- SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
- SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
- SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r WHERE r.record = item_cn_object.record;
+ SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
+ SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
+ SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
+ SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
+ SELECT INTO rec_descriptor * FROM metabib.rec_descriptor WHERE record = item_cn_object.record;
- PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
+ -- The item's owner should probably be the one determining if the item is holdable
+ -- How to decide that is debatable. Decided to default to the circ library (where the item lives)
+ -- This flag will allow for setting it to the owning library (where the call number "lives")
+ PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.weight_owner_not_circ' AND enabled;
+ -- Grab the closest set circ weight setting.
IF NOT FOUND THEN
- SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = requestor_object.profile;
+ -- Default to circ library
+ SELECT INTO weights hw.*
+ FROM config.weight_assoc wa
+ JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
+ JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) d ON (wa.org_unit = d.id)
+ WHERE active
+ ORDER BY d.distance
+ LIMIT 1;
ELSE
- SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = user_object.profile;
+ -- Flag is set, use owning library
+ SELECT INTO weights hw.*
+ FROM config.weight_assoc wa
+ JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
+ JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) d ON (wa.org_unit = d.id)
+ WHERE active
+ ORDER BY d.distance
+ LIMIT 1;
END IF;
- LOOP
- -- for each potential matchpoint for this ou and group ...
- FOR current_mp IN
- SELECT m.*
- FROM config.hold_matrix_matchpoint m
- WHERE m.requestor_grp = current_requestor_group.id AND m.active
- ORDER BY CASE WHEN m.circ_modifier IS NOT NULL THEN 16 ELSE 0 END +
- CASE WHEN m.juvenile_flag IS NOT NULL THEN 16 ELSE 0 END +
- CASE WHEN m.marc_type IS NOT NULL THEN 8 ELSE 0 END +
- CASE WHEN m.marc_form IS NOT NULL THEN 4 ELSE 0 END +
- CASE WHEN m.marc_vr_format IS NOT NULL THEN 2 ELSE 0 END +
- CASE WHEN m.ref_flag IS NOT NULL THEN 1 ELSE 0 END DESC LOOP
+ -- No weights? Bad admin! Defaults to handle that anyway.
+ IF weights.id IS NULL THEN
+ weights.user_home_ou := 5;
+ weights.request_ou := 5;
+ weights.pickup_ou := 5;
+ weights.item_owning_ou := 5;
+ weights.item_circ_ou := 5;
+ weights.usr_grp := 7;
+ weights.requestor_grp := 8;
+ weights.circ_modifier := 4;
+ weights.marc_type := 3;
+ weights.marc_form := 2;
+ weights.marc_vr_format := 1;
+ weights.juvenile_flag := 4;
+ weights.ref_flag := 0;
+ END IF;
- IF NOT current_mp.strict_ou_match THEN
- current_mp_weight := 5.0;
- ELSE
- current_mp_weight := 0.0;
- END IF;
+ -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
+ -- If you break your org tree with funky parenting this may be wrong
+ -- Note: This CTE is duplicated in the find_circ_matrix_matchpoint function, and it may be a good idea to split it off to a function
+ -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
+ WITH all_distance(distance) AS (
+ SELECT depth AS distance FROM actor.org_unit_type
+ UNION
+ SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
+ )
+ SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
- IF current_mp.circ_modifier IS NOT NULL THEN
- CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
- END IF;
+ -- To ATTEMPT to make this work like it used to, make it reverse the user/requestor profile ids.
+ -- This may be better implemented as part of the upgrade script?
+ -- Set usr_grp = requestor_grp, requestor_grp = 1 or something when this flag is already set
+ -- Then remove this flag, of course.
+ PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
- IF current_mp.marc_type IS NOT NULL THEN
- IF item_object.circ_as_type IS NOT NULL THEN
- CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
- ELSE
- CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
- END IF;
- END IF;
+ IF FOUND THEN
+ -- Note: This, to me, is REALLY hacky. I put it in anyway.
+ -- If you can't tell, this is a single call swap on two variables.
+ SELECT INTO user_object.profile, requestor_object.profile
+ requestor_object.profile, user_object.profile;
+ END IF;
- IF current_mp.marc_form IS NOT NULL THEN
- CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
- END IF;
+ -- Select the winning matchpoint into the matchpoint variable for returning
+ SELECT INTO matchpoint m.*
+ FROM config.hold_matrix_matchpoint m
+ /*LEFT*/ JOIN permission.grp_ancestors_distance( requestor_object.profile ) rpgad ON m.requestor_grp = rpgad.id
+ LEFT JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.usr_grp = upgad.id
+ LEFT JOIN actor.org_unit_ancestors_distance( pickup_ou ) puoua ON m.pickup_ou = puoua.id
+ LEFT JOIN actor.org_unit_ancestors_distance( request_ou ) rqoua ON m.request_ou = rqoua.id
+ LEFT JOIN actor.org_unit_ancestors_distance( item_cn_object.owning_lib ) cnoua ON m.item_owning_ou = cnoua.id
+ LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.item_circ_ou = iooua.id
+ LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou ) uhoua ON m.user_home_ou = uhoua.id
+ WHERE m.active
+ -- Permission Groups
+ -- AND (m.requestor_grp IS NULL OR upgad.id IS NOT NULL) -- Optional Requestor Group?
+ AND (m.usr_grp IS NULL OR upgad.id IS NOT NULL)
+ -- Org Units
+ AND (m.pickup_ou IS NULL OR (puoua.id IS NOT NULL AND (puoua.distance = 0 OR NOT m.strict_ou_match)))
+ AND (m.request_ou IS NULL OR (rqoua.id IS NOT NULL AND (rqoua.distance = 0 OR NOT m.strict_ou_match)))
+ AND (m.item_owning_ou IS NULL OR (cnoua.id IS NOT NULL AND (cnoua.distance = 0 OR NOT m.strict_ou_match)))
+ AND (m.item_circ_ou IS NULL OR (iooua.id IS NOT NULL AND (iooua.distance = 0 OR NOT m.strict_ou_match)))
+ AND (m.user_home_ou IS NULL OR (uhoua.id IS NOT NULL AND (uhoua.distance = 0 OR NOT m.strict_ou_match)))
+ -- Static User Checks
+ AND (m.juvenile_flag IS NULL OR m.juvenile_flag = user_object.juvenile)
+ -- Static Item Checks
+ AND (m.circ_modifier IS NULL OR m.circ_modifier = item_object.circ_modifier)
+ AND (m.marc_type IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
+ AND (m.marc_form IS NULL OR m.marc_form = rec_descriptor.item_form)
+ AND (m.marc_vr_format IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
+ AND (m.ref_flag IS NULL OR m.ref_flag = item_object.ref)
+ ORDER BY
+ -- Permission Groups
+ CASE WHEN rpgad.distance IS NOT NULL THEN 2^(2*weights.requestor_grp - (rpgad.distance/denominator)) ELSE 0 END +
+ CASE WHEN upgad.distance IS NOT NULL THEN 2^(2*weights.usr_grp - (upgad.distance/denominator)) ELSE 0 END +
+ -- Org Units
+ CASE WHEN puoua.distance IS NOT NULL THEN 2^(2*weights.pickup_ou - (puoua.distance/denominator)) ELSE 0 END +
+ CASE WHEN rqoua.distance IS NOT NULL THEN 2^(2*weights.request_ou - (rqoua.distance/denominator)) ELSE 0 END +
+ CASE WHEN cnoua.distance IS NOT NULL THEN 2^(2*weights.item_owning_ou - (cnoua.distance/denominator)) ELSE 0 END +
+ CASE WHEN iooua.distance IS NOT NULL THEN 2^(2*weights.item_circ_ou - (iooua.distance/denominator)) ELSE 0 END +
+ CASE WHEN uhoua.distance IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0 END +
+ -- Static User Checks -- Note: 4^x is equiv to 2^(2*x)
+ CASE WHEN m.juvenile_flag IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0 END +
+ -- Static Item Checks
+ CASE WHEN m.circ_modifier IS NOT NULL THEN 4^weights.circ_modifier ELSE 0 END +
+ CASE WHEN m.marc_type IS NOT NULL THEN 4^weights.marc_type ELSE 0 END +
+ CASE WHEN m.marc_form IS NOT NULL THEN 4^weights.marc_form ELSE 0 END +
+ CASE WHEN m.marc_vr_format IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0 END +
+ CASE WHEN m.ref_flag IS NOT NULL THEN 4^weights.ref_flag ELSE 0 END DESC,
+ -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
+ -- This prevents "we changed the table order by updating a rule, and we started getting different results"
+ m.id;
- IF current_mp.marc_vr_format IS NOT NULL THEN
- CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
- END IF;
-
- IF current_mp.juvenile_flag IS NOT NULL THEN
- CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
- END IF;
-
- IF current_mp.ref_flag IS NOT NULL THEN
- CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
- END IF;
-
-
- -- caclulate the rule match weight
- IF current_mp.item_owning_ou IS NOT NULL THEN
- CONTINUE WHEN current_mp.item_owning_ou NOT IN (SELECT (actor.org_unit_ancestors(item_cn_object.owning_lib)).id);
- IF NOT current_mp.strict_ou_match THEN
- SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_owning_ou, item_cn_object.owning_lib)::FLOAT + 1.0)::FLOAT;
- ELSE
- CONTINUE WHEN current_mp.item_owning_ou <> item_cn_object.owning_lib;
- tmp_weight := CASE WHEN current_mp.item_owning_ou = item_cn_object.owning_lib THEN 1.0 ELSE 0.0 END;
- END IF;
- current_mp_weight := current_mp_weight - tmp_weight;
- END IF;
-
- IF current_mp.item_circ_ou IS NOT NULL THEN
- CONTINUE WHEN current_mp.item_circ_ou NOT IN (SELECT (actor.org_unit_ancestors(item_object.circ_lib)).id);
- IF NOT current_mp.strict_ou_match THEN
- SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_circ_ou, item_object.circ_lib)::FLOAT + 1.0)::FLOAT;
- ELSE
- CONTINUE WHEN current_mp.item_circ_ou <> item_object.circ_lib;
- tmp_weight := CASE WHEN current_mp.item_circ_ou = item_object.circ_lib THEN 1.0 ELSE 0.0 END;
- END IF;
- current_mp_weight := current_mp_weight - tmp_weight;
- END IF;
-
- IF current_mp.pickup_ou IS NOT NULL THEN
- CONTINUE WHEN current_mp.pickup_ou NOT IN (SELECT (actor.org_unit_ancestors(pickup_ou)).id);
- IF NOT current_mp.strict_ou_match THEN
- SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.pickup_ou, pickup_ou)::FLOAT + 1.0)::FLOAT;
- ELSE
- CONTINUE WHEN current_mp.pickup_ou <> pickup_ou;
- tmp_weight := CASE WHEN current_mp.pickup_ou = pickiup_ou THEN 1.0 ELSE 0.0 END;
- END IF;
- current_mp_weight := current_mp_weight - tmp_weight;
- END IF;
-
- IF current_mp.request_ou IS NOT NULL THEN
- CONTINUE WHEN current_mp.request_ou NOT IN (SELECT (actor.org_unit_ancestors(request_ou)).id);
- IF NOT current_mp.strict_ou_match THEN
- SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.request_ou, request_ou)::FLOAT + 1.0)::FLOAT;
- ELSE
- CONTINUE WHEN current_mp.request_ou <> request_ou;
- tmp_weight := CASE WHEN current_mp.request_ou = request_ou THEN 1.0 ELSE 0.0 END;
- END IF;
- current_mp_weight := current_mp_weight - tmp_weight;
- END IF;
-
- IF current_mp.user_home_ou IS NOT NULL THEN
- CONTINUE WHEN current_mp.user_home_ou NOT IN (SELECT (actor.org_unit_ancestors(user_object.home_ou)).id);
- IF NOT current_mp.strict_ou_match THEN
- SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.user_home_ou, user_object.home_ou)::FLOAT + 1.0)::FLOAT;
- ELSE
- CONTINUE WHEN current_mp.user_home_ou <> user_object.home_ou;
- tmp_weight := CASE WHEN current_mp.user_home_ou = user_object.home_ou THEN 1.0 ELSE 0.0 END;
- END IF;
- current_mp_weight := current_mp_weight - tmp_weight;
- END IF;
-
- -- set the matchpoint if we found the best one
- IF matchpoint_weight IS NULL OR matchpoint_weight > current_mp_weight THEN
- matchpoint = current_mp;
- matchpoint_weight = current_mp_weight;
- END IF;
-
- END LOOP;
-
- EXIT WHEN current_requestor_group.parent IS NULL OR matchpoint.id IS NOT NULL;
-
- SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = current_requestor_group.parent;
- END LOOP;
-
+ -- Return just the ID for now
RETURN matchpoint.id;
END;
-$func$ LANGUAGE plpgsql;
+$func$ LANGUAGE 'plpgsql';
-
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;
Modified: trunk/Open-ILS/src/sql/Pg/950.data.seed-values.sql
===================================================================
--- trunk/Open-ILS/src/sql/Pg/950.data.seed-values.sql 2011-01-27 20:58:41 UTC (rev 19316)
+++ trunk/Open-ILS/src/sql/Pg/950.data.seed-values.sql 2011-01-28 14:58:37 UTC (rev 19317)
@@ -1621,11 +1621,25 @@
-- circ matrix
INSERT INTO config.circ_matrix_matchpoint (org_unit,grp,duration_rule,recurring_fine_rule,max_fine_rule) VALUES (1,1,11,1,1);
+INSERT INTO config.circ_matrix_weights(name, org_unit, grp, circ_modifier, marc_type, marc_form, marc_vr_format, copy_circ_lib, copy_owning_lib, user_home_ou, ref_flag, juvenile_flag, is_renewal, usr_age_upper_bound, usr_age_lower_bound) VALUES
+ ('Default', 10.0, 11.0, 5.0, 4.0, 3.0, 2.0, 8.0, 8.0, 8.0, 1.0, 6.0, 7.0, 0.0, 0.0),
+ ('Org_Unit_First', 11.0, 10.0, 5.0, 4.0, 3.0, 2.0, 8.0, 8.0, 8.0, 1.0, 6.0, 7.0, 0.0, 0.0),
+ ('Item_Owner_First', 8.0, 8.0, 5.0, 4.0, 3.0, 2.0, 10.0, 11.0, 8.0, 1.0, 6.0, 7.0, 0.0, 0.0),
+ ('All_Equal', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
-- hold matrix - 110.hold_matrix.sql:
INSERT INTO config.hold_matrix_matchpoint (requestor_grp) VALUES (1);
+INSERT INTO config.hold_matrix_weights(name, user_home_ou, request_ou, pickup_ou, item_owning_ou, item_circ_ou, usr_grp, requestor_grp, circ_modifier, marc_type, marc_form, marc_vr_format, juvenile_flag, ref_flag) VALUES
+ ('Default', 5.0, 5.0, 5.0, 5.0, 5.0, 7.0, 8.0, 4.0, 3.0, 2.0, 1.0, 4.0, 0.0),
+ ('Item_Owner_First', 5.0, 5.0, 5.0, 8.0, 7.0, 5.0, 5.0, 4.0, 3.0, 2.0, 1.0, 4.0, 0.0),
+ ('User_Before_Requestor', 5.0, 5.0, 5.0, 5.0, 5.0, 8.0, 7.0, 4.0, 3.0, 2.0, 1.0, 4.0, 0.0),
+ ('All_Equal', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+-- dynamic weight associations
+INSERT INTO config.weight_assoc(active, org_unit, circ_weights, hold_weights) VALUES
+ (true, 1, 1, 1);
+
-- User setting types
INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
VALUES ('opac.default_font', TRUE, 'OPAC Font Size', 'OPAC Font Size', 'string');
Modified: trunk/Open-ILS/src/sql/Pg/build-db.sh
===================================================================
--- trunk/Open-ILS/src/sql/Pg/build-db.sh 2011-01-27 20:58:41 UTC (rev 19316)
+++ trunk/Open-ILS/src/sql/Pg/build-db.sh 2011-01-28 14:58:37 UTC (rev 19317)
@@ -101,7 +101,8 @@
080.schema.money.sql
090.schema.action.sql
095.schema.booking.sql
-
+
+ 099.matrix_weights.sql
100.circ_matrix.sql
110.hold_matrix.sql
Added: trunk/Open-ILS/src/sql/Pg/upgrade/0479.schema.matrix_weights.sql
===================================================================
--- trunk/Open-ILS/src/sql/Pg/upgrade/0479.schema.matrix_weights.sql (rev 0)
+++ trunk/Open-ILS/src/sql/Pg/upgrade/0479.schema.matrix_weights.sql 2011-01-28 14:58:37 UTC (rev 19317)
@@ -0,0 +1,368 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0XXX');
+
+CREATE OR REPLACE FUNCTION permission.grp_ancestors_distance( INT ) RETURNS TABLE (id INT, distance INT) AS $$
+ WITH RECURSIVE grp_ancestors_distance(id, distance) AS (
+ SELECT $1, 0
+ UNION
+ SELECT pgt.parent, gad.distance+1
+ FROM permission.grp_tree pgt JOIN grp_ancestors_distance gad ON pgt.id = gad.id
+ WHERE pgt.parent IS NOT NULL
+ )
+ SELECT * FROM grp_ancestors_distance;
+$$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION permission.grp_descendants_distance( INT ) RETURNS TABLE (id INT, distance INT) AS $$
+ WITH RECURSIVE grp_descendants_distance(id, distance) AS (
+ SELECT $1, 0
+ UNION
+ SELECT pgt.id, gdd.distance+1
+ FROM permission.grp_tree pgt JOIN grp_descendants_distance gdd ON pgt.parent = gdd.id
+ )
+ SELECT * FROM grp_descendants_distance;
+$$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION actor.org_unit_ancestors_distance( INT ) RETURNS TABLE (id INT, distance INT) AS $$
+ WITH RECURSIVE org_unit_ancestors_distance(id, distance) AS (
+ SELECT $1, 0
+ UNION
+ SELECT ou.parent_ou, ouad.distance+1
+ FROM actor.org_unit ou JOIN org_unit_ancestors_distance ouad ON ou.id = ouad.id
+ WHERE ou.parent_ou IS NOT NULL
+ )
+ SELECT * FROM org_unit_ancestors_distance;
+$$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION actor.org_unit_descendants_distance( INT ) RETURNS TABLE (id INT, distance INT) AS $$
+ WITH RECURSIVE org_unit_descendants_distance(id, distance) AS (
+ SELECT $1, 0
+ UNION
+ SELECT ou.id, oudd.distance+1
+ FROM actor.org_unit ou JOIN org_unit_descendants_distance oudd ON ou.parent_ou = oudd.id
+ )
+ SELECT * FROM org_unit_descendants_distance;
+$$ LANGUAGE SQL STABLE;
+
+ALTER TABLE config.circ_matrix_matchpoint
+ ADD COLUMN user_home_ou INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
+
+CREATE TABLE config.circ_matrix_weights (
+ id SERIAL PRIMARY KEY,
+ name TEXT NOT NULL UNIQUE,
+ org_unit NUMERIC(6,2) NOT NULL,
+ grp NUMERIC(6,2) NOT NULL,
+ circ_modifier NUMERIC(6,2) NOT NULL,
+ marc_type NUMERIC(6,2) NOT NULL,
+ marc_form NUMERIC(6,2) NOT NULL,
+ marc_vr_format NUMERIC(6,2) NOT NULL,
+ copy_circ_lib NUMERIC(6,2) NOT NULL,
+ copy_owning_lib NUMERIC(6,2) NOT NULL,
+ user_home_ou NUMERIC(6,2) NOT NULL,
+ ref_flag NUMERIC(6,2) NOT NULL,
+ juvenile_flag NUMERIC(6,2) NOT NULL,
+ is_renewal NUMERIC(6,2) NOT NULL,
+ usr_age_lower_bound NUMERIC(6,2) NOT NULL,
+ usr_age_upper_bound NUMERIC(6,2) NOT NULL
+);
+
+CREATE TABLE config.hold_matrix_weights (
+ id SERIAL PRIMARY KEY,
+ name TEXT NOT NULL UNIQUE,
+ user_home_ou NUMERIC(6,2) NOT NULL,
+ request_ou NUMERIC(6,2) NOT NULL,
+ pickup_ou NUMERIC(6,2) NOT NULL,
+ item_owning_ou NUMERIC(6,2) NOT NULL,
+ item_circ_ou NUMERIC(6,2) NOT NULL,
+ usr_grp NUMERIC(6,2) NOT NULL,
+ requestor_grp NUMERIC(6,2) NOT NULL,
+ circ_modifier NUMERIC(6,2) NOT NULL,
+ marc_type NUMERIC(6,2) NOT NULL,
+ marc_form NUMERIC(6,2) NOT NULL,
+ marc_vr_format NUMERIC(6,2) NOT NULL,
+ juvenile_flag NUMERIC(6,2) NOT NULL,
+ ref_flag NUMERIC(6,2) NOT NULL
+);
+
+CREATE TABLE config.weight_assoc (
+ id SERIAL PRIMARY KEY,
+ active BOOL NOT NULL,
+ org_unit INT NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ circ_weights INT REFERENCES config.circ_matrix_weights (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
+ hold_weights INT REFERENCES config.hold_matrix_weights (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED
+);
+CREATE UNIQUE INDEX cwa_one_active_per_ou ON config.weight_assoc (org_unit) WHERE active;
+
+CREATE OR REPLACE FUNCTION action.find_circ_matrix_matchpoint( context_ou INT, match_item BIGINT, match_user INT, renewal BOOL ) RETURNS config.circ_matrix_matchpoint AS $func$
+DECLARE
+ user_object actor.usr%ROWTYPE;
+ item_object asset.copy%ROWTYPE;
+ cn_object asset.call_number%ROWTYPE;
+ rec_descriptor metabib.rec_descriptor%ROWTYPE;
+ matchpoint config.circ_matrix_matchpoint%ROWTYPE;
+ weights config.circ_matrix_weights%ROWTYPE;
+ user_age INTERVAL;
+ denominator INT;
+BEGIN
+ SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
+ SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
+ SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
+ SELECT INTO rec_descriptor * FROM metabib.rec_descriptor WHERE record = cn_object.record;
+
+ -- Pre-generate this so we only calc it once
+ IF user_object.dob IS NOT NULL THEN
+ SELECT INTO user_age age(user_object.dob);
+ END IF;
+
+ -- Grab the closest set circ weight setting.
+ SELECT INTO weights cw.*
+ FROM config.weight_assoc wa
+ JOIN config.circ_matrix_weights cw ON (cw.id = wa.circ_weights)
+ JOIN actor.org_unit_ancestors_distance( context_ou ) d ON (wa.org_unit = d.id)
+ WHERE active
+ ORDER BY d.distance
+ LIMIT 1;
+
+ -- No weights? Bad admin! Defaults to handle that anyway.
+ IF weights.id IS NULL THEN
+ weights.grp := 11;
+ weights.org_unit := 10;
+ weights.circ_modifier := 5;
+ weights.marc_type := 4;
+ weights.marc_form := 3;
+ weights.marc_vr_format := 2;
+ weights.copy_circ_lib := 8;
+ weights.copy_owning_lib := 8;
+ weights.user_home_ou := 8;
+ weights.ref_flag := 1;
+ weights.juvenile_flag := 6;
+ weights.is_renewal := 7;
+ weights.usr_age_lower_bound := 0;
+ weights.usr_age_upper_bound := 0;
+ END IF;
+
+ -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
+ -- If you break your org tree with funky parenting this may be wrong
+ -- Note: This CTE is duplicated in the find_hold_matrix_matchpoint function, and it may be a good idea to split it off to a function
+ -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
+ WITH all_distance(distance) AS (
+ SELECT depth AS distance FROM actor.org_unit_type
+ UNION
+ SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
+ )
+ SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
+
+ -- Select the winning matchpoint into the matchpoint variable for returning
+ SELECT INTO matchpoint m.*
+ FROM config.circ_matrix_matchpoint m
+ /*LEFT*/ JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.grp = upgad.id
+ /*LEFT*/ JOIN actor.org_unit_ancestors_distance( context_ou ) ctoua ON m.org_unit = ctoua.id
+ LEFT JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) cnoua ON m.copy_owning_lib = cnoua.id
+ LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.copy_circ_lib = iooua.id
+ LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou ) uhoua ON m.user_home_ou = uhoua.id
+ WHERE m.active
+ -- Permission Groups
+ -- AND (m.grp IS NULL OR upgad.id IS NOT NULL) -- Optional Permission Group?
+ -- Org Units
+ -- AND (m.org_unit IS NULL OR ctoua.id IS NOT NULL) -- Optional Org Unit?
+ AND (m.copy_owning_lib IS NULL OR cnoua.id IS NOT NULL)
+ AND (m.copy_circ_lib IS NULL OR iooua.id IS NOT NULL)
+ AND (m.user_home_ou IS NULL OR uhoua.id IS NOT NULL)
+ -- Circ Type
+ AND (m.is_renewal IS NULL OR m.is_renewal = renewal)
+ -- Static User Checks
+ AND (m.juvenile_flag IS NULL OR m.juvenile_flag = user_object.juvenile)
+ AND (m.usr_age_lower_bound IS NULL OR (user_age IS NOT NULL AND m.usr_age_lower_bound < user_age))
+ AND (m.usr_age_upper_bound IS NULL OR (user_age IS NOT NULL AND m.usr_age_upper_bound > user_age))
+ -- Static Item Checks
+ AND (m.circ_modifier IS NULL OR m.circ_modifier = item_object.circ_modifier)
+ AND (m.marc_type IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
+ AND (m.marc_form IS NULL OR m.marc_form = rec_descriptor.item_form)
+ AND (m.marc_vr_format IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
+ AND (m.ref_flag IS NULL OR m.ref_flag = item_object.ref)
+ ORDER BY
+ -- Permission Groups
+ CASE WHEN upgad.distance IS NOT NULL THEN 2^(2*weights.grp - (upgad.distance/denominator)) ELSE 0 END +
+ -- Org Units
+ CASE WHEN ctoua.distance IS NOT NULL THEN 2^(2*weights.org_unit - (ctoua.distance/denominator)) ELSE 0 END +
+ CASE WHEN cnoua.distance IS NOT NULL THEN 2^(2*weights.copy_owning_lib - (cnoua.distance/denominator)) ELSE 0 END +
+ CASE WHEN iooua.distance IS NOT NULL THEN 2^(2*weights.copy_circ_lib - (iooua.distance/denominator)) ELSE 0 END +
+ CASE WHEN uhoua.distance IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0 END +
+ -- Circ Type -- Note: 4^x is equiv to 2^(2*x)
+ CASE WHEN m.is_renewal IS NOT NULL THEN 4^weights.is_renewal ELSE 0 END +
+ -- Static User Checks
+ CASE WHEN m.juvenile_flag IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0 END +
+ CASE WHEN m.usr_age_lower_bound IS NOT NULL THEN 4^weights.usr_age_lower_bound ELSE 0 END +
+ CASE WHEN m.usr_age_upper_bound IS NOT NULL THEN 4^weights.usr_age_upper_bound ELSE 0 END +
+ -- Static Item Checks
+ CASE WHEN m.circ_modifier IS NOT NULL THEN 4^weights.circ_modifier ELSE 0 END +
+ CASE WHEN m.marc_type IS NOT NULL THEN 4^weights.marc_type ELSE 0 END +
+ CASE WHEN m.marc_form IS NOT NULL THEN 4^weights.marc_form ELSE 0 END +
+ CASE WHEN m.marc_vr_format IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0 END +
+ CASE WHEN m.ref_flag IS NOT NULL THEN 4^weights.ref_flag ELSE 0 END DESC,
+ -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
+ -- This prevents "we changed the table order by updating a rule, and we started getting different results"
+ m.id;
+
+ -- Return the entire matchpoint
+ RETURN matchpoint;
+END;
+$func$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint(pickup_ou integer, request_ou integer, match_item bigint, match_user integer, match_requestor integer)
+ RETURNS integer AS
+$func$
+DECLARE
+ requestor_object actor.usr%ROWTYPE;
+ user_object actor.usr%ROWTYPE;
+ item_object asset.copy%ROWTYPE;
+ item_cn_object asset.call_number%ROWTYPE;
+ rec_descriptor metabib.rec_descriptor%ROWTYPE;
+ matchpoint config.hold_matrix_matchpoint%ROWTYPE;
+ weights config.hold_matrix_weights%ROWTYPE;
+ denominator INT;
+BEGIN
+ SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
+ SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
+ SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
+ SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
+ SELECT INTO rec_descriptor * FROM metabib.rec_descriptor WHERE record = item_cn_object.record;
+
+ -- The item's owner should probably be the one determining if the item is holdable
+ -- How to decide that is debatable. Decided to default to the circ library (where the item lives)
+ -- This flag will allow for setting it to the owning library (where the call number "lives")
+ PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.weight_owner_not_circ' AND enabled;
+
+ -- Grab the closest set circ weight setting.
+ IF NOT FOUND THEN
+ -- Default to circ library
+ SELECT INTO weights hw.*
+ FROM config.weight_assoc wa
+ JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
+ JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) d ON (wa.org_unit = d.id)
+ WHERE active
+ ORDER BY d.distance
+ LIMIT 1;
+ ELSE
+ -- Flag is set, use owning library
+ SELECT INTO weights hw.*
+ FROM config.weight_assoc wa
+ JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
+ JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) d ON (wa.org_unit = d.id)
+ WHERE active
+ ORDER BY d.distance
+ LIMIT 1;
+ END IF;
+
+ -- No weights? Bad admin! Defaults to handle that anyway.
+ IF weights.id IS NULL THEN
+ weights.user_home_ou := 5;
+ weights.request_ou := 5;
+ weights.pickup_ou := 5;
+ weights.item_owning_ou := 5;
+ weights.item_circ_ou := 5;
+ weights.usr_grp := 7;
+ weights.requestor_grp := 8;
+ weights.circ_modifier := 4;
+ weights.marc_type := 3;
+ weights.marc_form := 2;
+ weights.marc_vr_format := 1;
+ weights.juvenile_flag := 4;
+ weights.ref_flag := 0;
+ END IF;
+
+ -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
+ -- If you break your org tree with funky parenting this may be wrong
+ -- Note: This CTE is duplicated in the find_circ_matrix_matchpoint function, and it may be a good idea to split it off to a function
+ -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
+ WITH all_distance(distance) AS (
+ SELECT depth AS distance FROM actor.org_unit_type
+ UNION
+ SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
+ )
+ SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
+
+ -- To ATTEMPT to make this work like it used to, make it reverse the user/requestor profile ids.
+ -- This may be better implemented as part of the upgrade script?
+ -- Set usr_grp = requestor_grp, requestor_grp = 1 or something when this flag is already set
+ -- Then remove this flag, of course.
+ PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
+
+ IF FOUND THEN
+ -- Note: This, to me, is REALLY hacky. I put it in anyway.
+ -- If you can't tell, this is a single call swap on two variables.
+ SELECT INTO user_object.profile, requestor_object.profile
+ requestor_object.profile, user_object.profile;
+ END IF;
+
+ -- Select the winning matchpoint into the matchpoint variable for returning
+ SELECT INTO matchpoint m.*
+ FROM config.hold_matrix_matchpoint m
+ /*LEFT*/ JOIN permission.grp_ancestors_distance( requestor_object.profile ) rpgad ON m.requestor_grp = rpgad.id
+ LEFT JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.usr_grp = upgad.id
+ LEFT JOIN actor.org_unit_ancestors_distance( pickup_ou ) puoua ON m.pickup_ou = puoua.id
+ LEFT JOIN actor.org_unit_ancestors_distance( request_ou ) rqoua ON m.request_ou = rqoua.id
+ LEFT JOIN actor.org_unit_ancestors_distance( item_cn_object.owning_lib ) cnoua ON m.item_owning_ou = cnoua.id
+ LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.item_circ_ou = iooua.id
+ LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou ) uhoua ON m.user_home_ou = uhoua.id
+ WHERE m.active
+ -- Permission Groups
+ -- AND (m.requestor_grp IS NULL OR upgad.id IS NOT NULL) -- Optional Requestor Group?
+ AND (m.usr_grp IS NULL OR upgad.id IS NOT NULL)
+ -- Org Units
+ AND (m.pickup_ou IS NULL OR (puoua.id IS NOT NULL AND (puoua.distance = 0 OR NOT m.strict_ou_match)))
+ AND (m.request_ou IS NULL OR (rqoua.id IS NOT NULL AND (rqoua.distance = 0 OR NOT m.strict_ou_match)))
+ AND (m.item_owning_ou IS NULL OR (cnoua.id IS NOT NULL AND (cnoua.distance = 0 OR NOT m.strict_ou_match)))
+ AND (m.item_circ_ou IS NULL OR (iooua.id IS NOT NULL AND (iooua.distance = 0 OR NOT m.strict_ou_match)))
+ AND (m.user_home_ou IS NULL OR (uhoua.id IS NOT NULL AND (uhoua.distance = 0 OR NOT m.strict_ou_match)))
+ -- Static User Checks
+ AND (m.juvenile_flag IS NULL OR m.juvenile_flag = user_object.juvenile)
+ -- Static Item Checks
+ AND (m.circ_modifier IS NULL OR m.circ_modifier = item_object.circ_modifier)
+ AND (m.marc_type IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
+ AND (m.marc_form IS NULL OR m.marc_form = rec_descriptor.item_form)
+ AND (m.marc_vr_format IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
+ AND (m.ref_flag IS NULL OR m.ref_flag = item_object.ref)
+ ORDER BY
+ -- Permission Groups
+ CASE WHEN rpgad.distance IS NOT NULL THEN 2^(2*weights.requestor_grp - (rpgad.distance/denominator)) ELSE 0 END +
+ CASE WHEN upgad.distance IS NOT NULL THEN 2^(2*weights.usr_grp - (upgad.distance/denominator)) ELSE 0 END +
+ -- Org Units
+ CASE WHEN puoua.distance IS NOT NULL THEN 2^(2*weights.pickup_ou - (puoua.distance/denominator)) ELSE 0 END +
+ CASE WHEN rqoua.distance IS NOT NULL THEN 2^(2*weights.request_ou - (rqoua.distance/denominator)) ELSE 0 END +
+ CASE WHEN cnoua.distance IS NOT NULL THEN 2^(2*weights.item_owning_ou - (cnoua.distance/denominator)) ELSE 0 END +
+ CASE WHEN iooua.distance IS NOT NULL THEN 2^(2*weights.item_circ_ou - (iooua.distance/denominator)) ELSE 0 END +
+ CASE WHEN uhoua.distance IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0 END +
+ -- Static User Checks -- Note: 4^x is equiv to 2^(2*x)
+ CASE WHEN m.juvenile_flag IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0 END +
+ -- Static Item Checks
+ CASE WHEN m.circ_modifier IS NOT NULL THEN 4^weights.circ_modifier ELSE 0 END +
+ CASE WHEN m.marc_type IS NOT NULL THEN 4^weights.marc_type ELSE 0 END +
+ CASE WHEN m.marc_form IS NOT NULL THEN 4^weights.marc_form ELSE 0 END +
+ CASE WHEN m.marc_vr_format IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0 END +
+ CASE WHEN m.ref_flag IS NOT NULL THEN 4^weights.ref_flag ELSE 0 END DESC,
+ -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
+ -- This prevents "we changed the table order by updating a rule, and we started getting different results"
+ m.id;
+
+ -- Return just the ID for now
+ RETURN matchpoint.id;
+END;
+$func$ LANGUAGE 'plpgsql';
+
+INSERT INTO config.circ_matrix_weights(name, org_unit, grp, circ_modifier, marc_type, marc_form, marc_vr_format, copy_circ_lib, copy_owning_lib, user_home_ou, ref_flag, juvenile_flag, is_renewal, usr_age_upper_bound, usr_age_lower_bound) VALUES
+ ('Default', 10.0, 11.0, 5.0, 4.0, 3.0, 2.0, 8.0, 8.0, 8.0, 1.0, 6.0, 7.0, 0.0, 0.0),
+ ('Org_Unit_First', 11.0, 10.0, 5.0, 4.0, 3.0, 2.0, 8.0, 8.0, 8.0, 1.0, 6.0, 7.0, 0.0, 0.0),
+ ('Item_Owner_First', 8.0, 8.0, 5.0, 4.0, 3.0, 2.0, 10.0, 11.0, 8.0, 1.0, 6.0, 7.0, 0.0, 0.0),
+ ('All_Equal', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+
+INSERT INTO config.hold_matrix_weights(name, user_home_ou, request_ou, pickup_ou, item_owning_ou, item_circ_ou, usr_grp, requestor_grp, circ_modifier, marc_type, marc_form, marc_vr_format, juvenile_flag, ref_flag) VALUES
+ ('Default', 5.0, 5.0, 5.0, 5.0, 5.0, 7.0, 8.0, 4.0, 3.0, 2.0, 1.0, 4.0, 0.0),
+ ('Item_Owner_First', 5.0, 5.0, 5.0, 8.0, 7.0, 5.0, 5.0, 4.0, 3.0, 2.0, 1.0, 4.0, 0.0),
+ ('User_Before_Requestor', 5.0, 5.0, 5.0, 5.0, 5.0, 8.0, 7.0, 4.0, 3.0, 2.0, 1.0, 4.0, 0.0),
+ ('All_Equal', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+
+INSERT INTO config.weight_assoc(active, org_unit, circ_weights, hold_weights) VALUES
+ (true, 1, 1, 1);
+
+COMMIT;
Modified: trunk/Open-ILS/web/opac/locale/en-US/lang.dtd
===================================================================
--- trunk/Open-ILS/web/opac/locale/en-US/lang.dtd 2011-01-27 20:58:41 UTC (rev 19316)
+++ trunk/Open-ILS/web/opac/locale/en-US/lang.dtd 2011-01-28 14:58:37 UTC (rev 19317)
@@ -717,6 +717,9 @@
<!ENTITY staff.main.menu.admin.server_admin.conify.config_rule_recurring_fine "Circulation Recurring Fine Rules">
<!ENTITY staff.main.menu.admin.server_admin.conify.config_rule_max_fine "Circulation Max Fine Rules">
<!ENTITY staff.main.menu.admin.server_admin.conify.config_rule_age_hold_protect "Age Hold Protect Rules">
+<!ENTITY staff.main.menu.admin.server_admin.conify.config_circ_weights "Circulation Matchpoint Weights">
+<!ENTITY staff.main.menu.admin.server_admin.conify.config_hold_weights "Hold Matchpoint Weights">
+<!ENTITY staff.main.menu.admin.server_admin.conify.config_weight_assoc "Weights Association">
<!ENTITY staff.main.menu.admin.server_admin.conify.global_flag.label "Global Flags">
<!ENTITY staff.main.menu.admin.server_admin.acq.label "Acquisitions">
Added: trunk/Open-ILS/web/templates/default/conify/global/config/circ_matrix_weights.tt2
===================================================================
--- trunk/Open-ILS/web/templates/default/conify/global/config/circ_matrix_weights.tt2 (rev 0)
+++ trunk/Open-ILS/web/templates/default/conify/global/config/circ_matrix_weights.tt2 2011-01-28 14:58:37 UTC (rev 19317)
@@ -0,0 +1,28 @@
+[% WRAPPER default/base.tt2 %]
+[% ctx.page_title = 'Circ Matrix Weights' %]
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="top" class='oils-header-panel'>
+ <div>Circ Matrix Weights</div>
+ <div>
+ <button dojoType='dijit.form.Button' onClick='ruleCircWeightsGrid.showCreateDialog()'>New Weight Set</button>
+ <button dojoType='dijit.form.Button' onClick='ruleCircWeightsGrid.deleteSelected()'>Delete Selected</button>
+ </div>
+ </div>
+ <div>
+ <table jsId="ruleCircWeightsGrid"
+ dojoType="openils.widget.AutoGrid"
+ fieldOrder="['name']"
+ suppressFields="['id']"
+ query="{id: '*'}"
+ fmClass='ccmw'
+ editOnEnter='true'/>
+</div>
+
+<script type="text/javascript">
+ dojo.require('openils.Util');
+ dojo.require('openils.widget.AutoGrid');
+ openils.Util.addOnLoad( function() { ruleCircWeightsGrid.loadAll(); } );
+</script>
+[% END %]
+
+
Modified: trunk/Open-ILS/web/templates/default/conify/global/config/hold_matrix_matchpoint.tt2
===================================================================
--- trunk/Open-ILS/web/templates/default/conify/global/config/hold_matrix_matchpoint.tt2 2011-01-27 20:58:41 UTC (rev 19316)
+++ trunk/Open-ILS/web/templates/default/conify/global/config/hold_matrix_matchpoint.tt2 2011-01-28 14:58:37 UTC (rev 19317)
@@ -9,7 +9,6 @@
autoHeight='true'
dojoType="openils.widget.AutoGrid"
fieldOrder="['id', 'strict_ou_match', 'user_home_ou', 'request_ou', 'pickup_ou', 'item_owning_ou', 'item_circ_ou', 'requestor_grp', 'circ_modifier']"
- suppressFields="['usr_grp']"
defaultCellWidth='"auto"'
query="{id: '*'}"
fmClass='chmm'
Added: trunk/Open-ILS/web/templates/default/conify/global/config/hold_matrix_weights.tt2
===================================================================
--- trunk/Open-ILS/web/templates/default/conify/global/config/hold_matrix_weights.tt2 (rev 0)
+++ trunk/Open-ILS/web/templates/default/conify/global/config/hold_matrix_weights.tt2 2011-01-28 14:58:37 UTC (rev 19317)
@@ -0,0 +1,28 @@
+[% WRAPPER default/base.tt2 %]
+[% ctx.page_title = 'Hold Matrix Weights' %]
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="top" class='oils-header-panel'>
+ <div>Hold Matrix Weights</div>
+ <div>
+ <button dojoType='dijit.form.Button' onClick='ruleHoldWeightsGrid.showCreateDialog()'>New Weight Set</button>
+ <button dojoType='dijit.form.Button' onClick='ruleHoldWeightsGrid.deleteSelected()'>Delete Selected</button>
+ </div>
+ </div>
+ <div>
+ <table jsId="ruleHoldWeightsGrid"
+ dojoType="openils.widget.AutoGrid"
+ fieldOrder="['name']"
+ suppressFields="['id']"
+ query="{id: '*'}"
+ fmClass='chmw'
+ editOnEnter='true'/>
+</div>
+
+<script type="text/javascript">
+ dojo.require('openils.Util');
+ dojo.require('openils.widget.AutoGrid');
+ openils.Util.addOnLoad( function() { ruleHoldWeightsGrid.loadAll(); } );
+</script>
+[% END %]
+
+
Added: trunk/Open-ILS/web/templates/default/conify/global/config/weight_assoc.tt2
===================================================================
--- trunk/Open-ILS/web/templates/default/conify/global/config/weight_assoc.tt2 (rev 0)
+++ trunk/Open-ILS/web/templates/default/conify/global/config/weight_assoc.tt2 2011-01-28 14:58:37 UTC (rev 19317)
@@ -0,0 +1,28 @@
+[% WRAPPER default/base.tt2 %]
+[% ctx.page_title = 'Matrix Weight Associations' %]
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="top" class='oils-header-panel'>
+ <div>Matrix Weight Associations</div>
+ <div>
+ <button dojoType='dijit.form.Button' onClick='ruleWeightAssocGrid.showCreateDialog()'>New Weight Association</button>
+ <button dojoType='dijit.form.Button' onClick='ruleWeightAssocGrid.deleteSelected()'>Delete Selected</button>
+ </div>
+ </div>
+ <div>
+ <table jsId="ruleWeightAssocGrid"
+ dojoType="openils.widget.AutoGrid"
+ fieldOrder="['active','org_unit','circ_weights','hold_weights']"
+ suppressFields="['id']"
+ query="{id: '*'}"
+ fmClass='cwa'
+ editOnEnter='true'/>
+</div>
+
+<script type="text/javascript">
+ dojo.require('openils.Util');
+ dojo.require('openils.widget.AutoGrid');
+ openils.Util.addOnLoad( function() { ruleWeightAssocGrid.loadAll(); } );
+</script>
+[% END %]
+
+
Modified: trunk/Open-ILS/xul/staff_client/chrome/content/main/menu.js
===================================================================
--- trunk/Open-ILS/xul/staff_client/chrome/content/main/menu.js 2011-01-27 20:58:41 UTC (rev 19316)
+++ trunk/Open-ILS/xul/staff_client/chrome/content/main/menu.js 2011-01-28 14:58:37 UTC (rev 19317)
@@ -629,6 +629,18 @@
['oncommand'],
function() { open_eg_web_page('conify/global/config/rule_age_hold_protect'); }
],
+ 'cmd_server_admin_config_circ_weights' : [
+ ['oncommand'],
+ function() { open_eg_web_page('conify/global/config/circ_matrix_weights'); }
+ ],
+ 'cmd_server_admin_config_hold_weights' : [
+ ['oncommand'],
+ function() { open_eg_web_page('conify/global/config/hold_matrix_weights'); }
+ ],
+ 'cmd_server_admin_config_weight_assoc' : [
+ ['oncommand'],
+ function() { open_eg_web_page('conify/global/config/weight_assoc'); }
+ ],
'cmd_local_admin_external_text_editor' : [
['oncommand'],
function() {
Modified: trunk/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
===================================================================
--- trunk/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul 2011-01-27 20:58:41 UTC (rev 19316)
+++ trunk/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul 2011-01-28 14:58:37 UTC (rev 19317)
@@ -173,6 +173,9 @@
<command id="cmd_server_admin_booking_resource_attr" />
<command id="cmd_server_admin_booking_resource_attr_value" />
<command id="cmd_server_admin_booking_resource_attr_map" />
+ <command id="cmd_server_admin_config_circ_weights" />
+ <command id="cmd_server_admin_config_hold_weights" />
+ <command id="cmd_server_admin_config_weight_assoc" />
</commandset>
@@ -400,6 +403,9 @@
<menuitem label="&staff.main.menu.admin.server_admin.conify.config_rule_recurring_fine;" command="cmd_server_admin_config_rule_recurring_fine"/>
<menuitem label="&staff.main.menu.admin.server_admin.conify.config_rule_max_fine;" command="cmd_server_admin_config_rule_max_fine"/>
<menuitem label="&staff.main.menu.admin.server_admin.conify.config_rule_age_hold_protect;" command="cmd_server_admin_config_rule_age_hold_protect"/>
+ <menuitem label="&staff.main.menu.admin.server_admin.conify.config_circ_weights;" command="cmd_server_admin_config_circ_weights"/>
+ <menuitem label="&staff.main.menu.admin.server_admin.conify.config_hold_weights;" command="cmd_server_admin_config_hold_weights"/>
+ <menuitem label="&staff.main.menu.admin.server_admin.conify.config_weight_assoc;" command="cmd_server_admin_config_weight_assoc"/>
<menu id="main.menu.admin.server.acq" label="&staff.main.menu.admin.server_admin.acq.label;" accesskey="&staff.main.menu.admin.server_admin.acq.accesskey;">
<menupopup id="main.menu.admin.server.acq.popup">
<menuitem label="&staff.main.menu.admin.server_admin.acq.fund.label;" accesskey="&staff.main.menu.admin.server_admin.acq.fund.accesskey;" command="cmd_server_admin_acq_fund" />
More information about the open-ils-commits
mailing list