</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"/>
<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"/>
<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"/>
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 (
) 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,
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)
--- /dev/null
+
+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;
** 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,
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,
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;
-
- 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
-
- IF current_mp.is_renewal IS NOT NULL THEN
- CONTINUE WHEN current_mp.is_renewal <> renewal;
- END IF;
-
- 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;
-
- 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 current_mp.marc_form IS NOT NULL THEN
- CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
- END IF;
-
- 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 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;
- SELECT INTO current_group * FROM permission.grp_tree WHERE id = current_group.parent;
- END LOOP;
+ -- 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;
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;
-
- PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
-
+ 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
- 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
-
- IF NOT current_mp.strict_ou_match THEN
- current_mp_weight := 5.0;
- ELSE
- current_mp_weight := 0.0;
- END IF;
-
- 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;
-
- 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 current_mp.marc_form IS NOT NULL THEN
- CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
- END IF;
-
- 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;
+ -- 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;
- EXIT WHEN current_requestor_group.parent IS NULL OR matchpoint.id IS NOT NULL;
+ -- 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;
- SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = current_requestor_group.parent;
- END LOOP;
+ 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;
-
+$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
-- 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)
080.schema.money.sql
090.schema.action.sql
095.schema.booking.sql
-
+
+ 099.matrix_weights.sql
100.circ_matrix.sql
110.hold_matrix.sql
--- /dev/null
+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;
<!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">
--- /dev/null
+[% 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 %]
+
+
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'
--- /dev/null
+[% 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 %]
+
+
--- /dev/null
+[% 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 %]
+
+
['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() {
<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>
<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" />