Floating Groups
authorThomas Berezansky <tsbere@mvlc.org>
Thu, 19 Apr 2012 02:03:16 +0000 (22:03 -0400)
committerDan Wells <dbw2@calvin.edu>
Thu, 26 Sep 2013 16:43:47 +0000 (12:43 -0400)
Convert the floating bool to a link to floating groups.

Each group contains zero or more members that define how copies can float.

See the included documentation file for an overview.

Signed-off-by: Thomas Berezansky <tsbere@mvlc.org>
Signed-off-by: Dan Wells <dbw2@calvin.edu>
25 files changed:
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
Open-ILS/src/sql/Pg/040.schema.asset.sql
Open-ILS/src/sql/Pg/120.floating_groups.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/800.fkeys.sql
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/sql_file_manifest
Open-ILS/src/sql/Pg/upgrade/XXXX.floating_groups.sql [new file with mode: 0644]
Open-ILS/src/templates/conify/global/config/floating_groups.tt2 [new file with mode: 0644]
Open-ILS/web/js/ui/default/conify/global/config/floating_groups.js [new file with mode: 0644]
Open-ILS/web/opac/locale/en-US/lang.dtd
Open-ILS/xul/staff_client/chrome/content/OpenILS/data.js
Open-ILS/xul/staff_client/chrome/content/main/constants.js
Open-ILS/xul/staff_client/chrome/content/main/menu.js
Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties
Open-ILS/xul/staff_client/server/cat/copy_editor.js
Open-ILS/xul/staff_client/server/circ/alternate_copy_summary.js
Open-ILS/xul/staff_client/server/circ/checkin.js
Open-ILS/xul/staff_client/server/circ/checkin_overlay.xul
Open-ILS/xul/staff_client/server/circ/util.js
Open-ILS/xul/staff_client/server/locale/en-US/cat.properties
Open-ILS/xul/staff_client/server/skin/circ.css
docs/floating_groups.txt [new file with mode: 0644]

index 2cd362d..7ae11f2 100644 (file)
@@ -4664,7 +4664,7 @@ SELECT  usr,
                        <field reporter:label="Copy Status" name="status" reporter:datatype="link"/>
                        <field reporter:label="Copy Status Changed Time" name="status_changed_time" reporter:datatype="timestamp"/>
                        <field reporter:label="Is Mint Condition" name="mint_condition" reporter:datatype="bool"/>
-                       <field reporter:label="Is Floating" name="floating" reporter:datatype="bool"/>
+                       <field reporter:label="Floating Group" name="floating" reporter:datatype="link"/>
                        <field reporter:label="Cost" name="cost" reporter:datatype="money"/>
                        <field reporter:label="Sort Key" name="sort_key" reporter:datatype="text"/>
                        <field reporter:label="Summary Contents" name="summary_contents" reporter:datatype="text"/>
@@ -4691,6 +4691,7 @@ SELECT  usr,
                        <link field="circulations" reltype="has_many" key="target_copy" map="" class="circ"/>
                        <link field="total_circ_count" reltype="might_have" key="id" map="" class="erfcc"/>
                        <link field="circ_modifier" reltype="has_a" key="code" map="" class="ccm"/>
+                       <link field="floating" reltype="has_a" key="id" map="" class="cfg"/>
                </links>
                <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
                        <actions>
@@ -6244,7 +6245,7 @@ SELECT  usr,
                        <field reporter:label="Copy Status" name="status" reporter:datatype="link"/>
                        <field reporter:label="Copy Status Changed Time" name="status_changed_time" reporter:datatype="timestamp"/>
                        <field reporter:label="Is Mint Condition" name="mint_condition" reporter:datatype="bool"/>
-                       <field reporter:label="Is Floating" name="floating" reporter:datatype="bool"/>
+                       <field reporter:label="Floating Group" name="floating" reporter:datatype="link"/>
                        <field reporter:label="Cost" name="cost" reporter:datatype="money"/>
                        <field reporter:label="Copy Notes" name="notes" oils_persist:virtual="true" reporter:datatype="link"/>
                        <field reporter:label="Stat-Cat entry maps" name="stat_cat_entry_copy_maps" oils_persist:virtual="true" reporter:datatype="link"/>
@@ -6278,6 +6279,7 @@ SELECT  usr,
                        <link field="peer_record_maps" reltype="has_many" key="target_copy" map="" class="bpbcm"/>
                        <link field="peer_records" reltype="has_many" key="target_copy" map="peer_record" class="bpbcm"/>
                        <link field="last_captured_hold" reltype="has_a" key="current_copy" map="" class="alhr"/>
+                       <link field="floating" reltype="has_a" key="id" map="" class="cfg"/>
                </links>
         <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
             <actions>
@@ -6320,7 +6322,7 @@ SELECT  usr,
                        <field reporter:label="Circ As Type" name="circ_as_type" reporter:datatype="text"/>
                        <field reporter:label="Alert Message" name="alert_message" reporter:datatype="text"/>
                        <field reporter:label="OPAC Visible?" name="opac_visible" reporter:datatype="bool"/>
-                       <field reporter:label="Floating?" name="floating" reporter:datatype="bool"/>
+                       <field reporter:label="Floating Group" name="floating" reporter:datatype="link"/>
                        <field reporter:label="Mint Condition?" name="mint_condition" reporter:datatype="bool"/>
                </fields>
                <links>
@@ -6331,6 +6333,7 @@ SELECT  usr,
                        <link field="status" reltype="has_a" key="id" map="" class="ccs"/>
                        <link field="circ_modifier" reltype="has_a" key="code" map="" class="ccm"/>
                        <link field="location" reltype="has_a" key="id" map="" class="acpl"/>
+                       <link field="floating" reltype="has_a" key="id" map="" class="cfg"/>
                </links>
                <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
                        <actions>
@@ -10844,7 +10847,47 @@ SELECT  usr,
                        <link field="stat_cat" reltype="has_a" key="id" map="" class="asc"/>
                </links>
        </class>
-
+       <class id="cfg" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::floating_group" oils_persist:tablename="config.floating_group" reporter:label="Floating Group">
+               <fields oils_persist:primary="id" oils_persist:sequence="config.floating_group_id_seq">
+                       <field reporter:label="ID" name="id" reporter:datatype="id"/>
+                       <field reporter:label="Name" name="name" reporter:datatype="text"/>
+                       <field reporter:label="Manual" name="manual" reporter:datatype="bool"/>
+                       <field reporter:label="Group Members" name="members" oils_persist:virtual="true" reporter:datatype="link"/>
+               </fields>
+               <links>
+                       <link field="members" reltype="has_many" key="floating_group" class="cfgm"/>
+               </links>
+               <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+                       <actions>
+                               <create permission="ADMIN_FLOAT_GROUPS" global_required="true"/>
+                               <retrieve/>
+                               <update permission="ADMIN_FLOAT_GROUPS" global_required="true"/>
+                               <delete permission="ADMIN_FLOAT_GROUPS" global_required="true"/>
+                       </actions>
+               </permacrud>
+       </class>
+       <class id="cfgm" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::floating_group_member" oils_persist:tablename="config.floating_group_member" reporter:label="Floating Group Members">
+               <fields oils_persist:primary="id" oils_persist:sequence="config.floating_group_member_id_seq">
+                       <field reporter:label="ID" name="id" reporter:datatype="id"/>
+                       <field reporter:label="Floating Group" name="floating_group" reporter:datatype="link"/>
+                       <field reporter:label="Org Unit" name="org_unit" reporter:datatype="link"/>
+                       <field reporter:label="Stop Depth" name="stop_depth" reporter:datatype="int"/>
+                       <field reporter:label="Max Depth" name="max_depth" reporter:datatype="int"/>
+                       <field reporter:label="Exclude" name="exclude" reporter:datatype="bool"/>
+               </fields>
+               <links>
+                       <link field="floating_group" reltype="has_a" key="id" class="cfg"/>
+                       <link field="org_unit" reltype="has_a" key="id" class="aou"/>
+               </links>
+               <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+                       <actions>
+                               <create permission="ADMIN_FLOAT_GROUPS" global_required="true"/>
+                               <retrieve/>
+                               <update permission="ADMIN_FLOAT_GROUPS" global_required="true"/>
+                               <delete permission="ADMIN_FLOAT_GROUPS" global_required="true"/>
+                       </actions>
+               </permacrud>
+       </class>
 
        <!-- ********************************************************************************************************************* -->
 
index 94dbaf8..e07f173 100644 (file)
@@ -965,7 +965,7 @@ sub copy_details {
             {
                 flesh => 2,
                 flesh_fields => {
-                    acp => ['call_number','parts','peer_record_maps'],
+                    acp => ['call_number','parts','peer_record_maps','floating'],
                     acn => ['record','prefix','suffix','label_class']
                 }
             }
index fd90e27..b390688 100644 (file)
@@ -33,7 +33,7 @@ sub determine_booking_status {
 
 my $MK_ENV_FLESH = { 
     flesh => 2, 
-    flesh_fields => {acp => ['call_number','parts'], acn => ['record']}
+    flesh_fields => {acp => ['call_number','parts','floating'], acn => ['record']}
 };
 
 sub initialize {
@@ -544,6 +544,7 @@ my @AUTOLOAD_FIELDS = qw/
     limit_groups
     override_args
     checkout_is_for_hold
+    manual_float
 /;
 
 
@@ -2703,8 +2704,20 @@ sub do_checkin {
     
             } else {
                 # copy needs to transit "home", or stick here if it's a floating copy
-    
-                if ($U->is_true( $self->copy->floating ) && !$self->remote_hold) { # copy is floating, stick here
+                my $can_float = 0;
+                if ($self->copy->floating && ($self->manual_float || !$U->is_true($self->copy->floating->manual)) && !$self->remote_hold) { # copy is potentially floating?
+                    my $res = $self->editor->json_query(
+                        {   from => [
+                                'evergreen.can_float',
+                                $self->copy->floating->id,
+                                $self->copy->circ_lib,
+                                $self->circ_lib
+                            ]
+                        }
+                    );
+                    $can_float = $U->is_true($res->[0]->{'evergreen.can_float'}) if $res; 
+                }
+                if ($can_float) { # Yep, floating, stick here
                     $self->checkin_changed(1);
                     $self->copy->circ_lib( $self->circ_lib );
                     $self->update_copy;
index dea51fd..34d829f 100644 (file)
@@ -96,7 +96,7 @@ CREATE TABLE asset.copy (
        alert_message   TEXT,
        opac_visible    BOOL                            NOT NULL DEFAULT TRUE,
        deleted         BOOL                            NOT NULL DEFAULT FALSE,
-       floating                BOOL                            NOT NULL DEFAULT FALSE,
+       floating        INT,
        dummy_isbn      TEXT,
        status_changed_time TIMESTAMP WITH TIME ZONE,
        active_date TIMESTAMP WITH TIME ZONE,
@@ -506,7 +506,7 @@ CREATE TABLE asset.copy_template (
        circ_as_type   TEXT,
        alert_message  TEXT,
        opac_visible   BOOL,
-       floating       BOOL,
+       floating       INT,
        mint_condition BOOL
 );
 
diff --git a/Open-ILS/src/sql/Pg/120.floating_groups.sql b/Open-ILS/src/sql/Pg/120.floating_groups.sql
new file mode 100644 (file)
index 0000000..cd04d91
--- /dev/null
@@ -0,0 +1,51 @@
+-- Floating Groups
+BEGIN;
+
+CREATE TABLE config.floating_group (
+    id      SERIAL PRIMARY KEY, 
+    name    TEXT UNIQUE NOT NULL,
+    manual  BOOL NOT NULL DEFAULT FALSE
+    );
+
+CREATE TABLE config.floating_group_member (
+    id              SERIAL PRIMARY KEY,
+    floating_group  INT NOT NULL REFERENCES config.floating_group (id),
+    org_unit        INT NOT NULL REFERENCES actor.org_unit (id),
+    stop_depth      INT NOT NULL DEFAULT 0,
+    max_depth       INT,
+    exclude         BOOL NOT NULL DEFAULT FALSE
+    );
+
+CREATE OR REPLACE FUNCTION evergreen.can_float( copy_floating_group integer, from_ou integer, to_ou integer ) RETURNS BOOL AS $f$
+DECLARE
+    float_member config.floating_group_member%ROWTYPE;
+    shared_ou_depth INT;
+    to_ou_depth INT;
+BEGIN
+    -- Grab the shared OU depth. If this is less than the stop depth later we ignore the entry.
+    SELECT INTO shared_ou_depth max(depth) FROM actor.org_unit_common_ancestors( from_ou, to_ou ) aou JOIN actor.org_unit_type aout ON aou.ou_type = aout.id;
+    -- Grab the to ou depth. If this is greater than max depth we ignore the entry.
+    SELECT INTO to_ou_depth depth FROM actor.org_unit aou JOIN actor.org_unit_type aout ON aou.ou_type = aout.id WHERE aou.id = to_ou;
+    -- Grab float members that apply. We don't care what we get beyond wanting excluded ones first.
+    SELECT INTO float_member *
+        FROM
+            config.floating_group_member cfgm
+            JOIN actor.org_unit aou ON cfgm.org_unit = aou.id
+            JOIN actor.org_unit_type aout ON aou.ou_type = aout.id
+        WHERE
+            cfgm.floating_group = copy_floating_group
+            AND to_ou IN (SELECT id FROM actor.org_unit_descendants(aou.id))
+            AND cfgm.stop_depth <= shared_ou_depth
+            AND (cfgm.max_depth IS NULL OR to_ou_depth <= max_depth)
+        ORDER BY
+            exclude DESC;
+    -- If we found something then we want to return the opposite of the exclude flag
+    IF FOUND THEN
+        RETURN NOT float_member.exclude;
+    END IF;
+    -- Otherwise no floating.
+    RETURN false;
+END;
+$f$ LANGUAGE PLPGSQL;
+
+COMMIT;
index efb37c5..151cacb 100644 (file)
@@ -150,4 +150,8 @@ ALTER TABLE config.filter_dialog_filter_set
 ALTER TABLE config.filter_dialog_filter_set
     ADD CONSTRAINT config_filter_dialog_filter_set_filters_check
     CHECK (evergreen.is_json(filters));
+
+ALTER TABLE asset.copy ADD CONSTRAINT asset_copy_floating_fkey FOREIGN KEY (floating) REFERENCES config.floating_group (id) DEFERRABLE INITIALLY DEFERRED;
+ALTER TABLE asset.copy_template ADD CONSTRAINT asset_copy_template_floating_fkey FOREIGN KEY (floating) REFERENCES config.floating_group (id) DEFERRABLE INITIALLY DEFERRED;
+
 COMMIT;
index 78fc099..90bd1e0 100644 (file)
@@ -1609,10 +1609,11 @@ INSERT INTO permission.perm_list ( id, code, description ) VALUES
  ( 550, 'SET_CIRC_LONG_OVERDUE', oils_i18n_gettext(550,
         'Allows the user to mark a circulation as long-overdue', 'ppl', 'code')),
  ( 551, 'ADMIN_SERVER_ADDON_FOR_WORKSTATION', oils_i18n_gettext( 551,
-        'Allows a user to specify which Server Add-ons get invoked at the current workstation', 'ppl', 'description'))
+        'Allows a user to specify which Server Add-ons get invoked at the current workstation', 'ppl', 'description')),
+ ( 552, 'ADMIN_FLOAT_GROUPS', oils_i18n_gettext( 552,
+    'Allows administration of floating groups', 'ppl', 'description' ))
 ;
 
-
 SELECT SETVAL('permission.perm_list_id_seq'::TEXT, 1000);
 
 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm) VALUES
@@ -13513,3 +13514,5 @@ VALUES (
     )
 );
 
+INSERT INTO config.floating_group(name) VALUES ('Everywhere');
+INSERT INTO config.floating_group_member(floating_group, org_unit) VALUES (1, 1);
index 459b604..8f56133 100644 (file)
@@ -33,6 +33,7 @@ FTS_CONFIG_FILE
 099.matrix_weights.sql 
 100.circ_matrix.sql
 110.hold_matrix.sql
+120.floating_groups.sql
 
 210.schema.serials.sql
 200.schema.acq.sql
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.floating_groups.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.floating_groups.sql
new file mode 100644 (file)
index 0000000..4842580
--- /dev/null
@@ -0,0 +1,82 @@
+CREATE TABLE config.floating_group (
+    id      SERIAL PRIMARY KEY, 
+    name    TEXT UNIQUE NOT NULL,
+    manual  BOOL NOT NULL DEFAULT FALSE
+    );
+
+CREATE TABLE config.floating_group_member (
+    id              SERIAL PRIMARY KEY,
+    floating_group  INT NOT NULL REFERENCES config.floating_group (id),
+    org_unit        INT NOT NULL REFERENCES actor.org_unit (id),
+    stop_depth      INT NOT NULL DEFAULT 0,
+    max_depth       INT,
+    exclude         BOOL NOT NULL DEFAULT FALSE
+    );
+
+CREATE OR REPLACE FUNCTION evergreen.can_float( copy_floating_group integer, from_ou integer, to_ou integer ) RETURNS BOOL AS $f$
+DECLARE
+    float_member config.floating_group_member%ROWTYPE;
+    shared_ou_depth INT;
+    to_ou_depth INT;
+BEGIN
+    -- Grab the shared OU depth. If this is less than the stop depth later we ignore the entry.
+    SELECT INTO shared_ou_depth max(depth) FROM actor.org_unit_common_ancestors( from_ou, to_ou ) aou JOIN actor.org_unit_type aout ON aou.ou_type = aout.id;
+    -- Grab the to ou depth. If this is greater than max depth we ignore the entry.
+    SELECT INTO to_ou_depth depth FROM actor.org_unit aou JOIN actor.org_unit_type aout ON aou.ou_type = aout.id WHERE aou.id = to_ou;
+    -- Grab float members that apply. We don't care what we get beyond wanting excluded ones first.
+    SELECT INTO float_member *
+        FROM
+            config.floating_group_member cfgm
+            JOIN actor.org_unit aou ON cfgm.org_unit = aou.id
+            JOIN actor.org_unit_type aout ON aou.ou_type = aout.id
+        WHERE
+            cfgm.floating_group = copy_floating_group
+            AND to_ou IN (SELECT id FROM actor.org_unit_descendants(aou.id))
+            AND cfgm.stop_depth <= shared_ou_depth
+            AND (cfgm.max_depth IS NULL OR to_ou_depth <= max_depth)
+        ORDER BY
+            exclude DESC;
+    -- If we found something then we want to return the opposite of the exclude flag
+    IF FOUND THEN
+        RETURN NOT float_member.exclude;
+    END IF;
+    -- Otherwise no floating.
+    RETURN false;
+END;
+$f$ LANGUAGE PLPGSQL;
+
+INSERT INTO config.floating_group(name) VALUES ('Everywhere');
+INSERT INTO config.floating_group_member(floating_group, org_unit) VALUES (1, 1);
+
+-- We need to drop these before we can update asset.copy
+DROP VIEW auditor.asset_copy_lifecycle;
+DROP VIEW auditor.serial_unit_lifecycle;
+
+-- Update the appropriate auditor tables
+ALTER TABLE auditor.asset_copy_history
+    ALTER COLUMN floating DROP DEFAULT,
+    ALTER COLUMN floating DROP NOT NULL,
+    ALTER COLUMN floating TYPE int USING CASE WHEN floating THEN 1 ELSE NULL END;
+ALTER TABLE auditor.serial_unit_history
+    ALTER COLUMN floating DROP DEFAULT,
+    ALTER COLUMN floating DROP NOT NULL,
+    ALTER COLUMN floating TYPE int USING CASE WHEN floating THEN 1 ELSE NULL END;
+
+-- Update asset.copy itself (does not appear to trigger update triggers!)
+ALTER TABLE asset.copy
+    ALTER COLUMN floating DROP DEFAULT,
+    ALTER COLUMN floating DROP NOT NULL,
+    ALTER COLUMN floating TYPE int USING CASE WHEN floating THEN 1 ELSE NULL END;
+
+ALTER TABLE asset.copy ADD CONSTRAINT asset_copy_floating_fkey FOREIGN KEY (floating) REFERENCES config.floating_group (id) DEFERRABLE INITIALLY DEFERRED;
+
+-- Update asset.copy_template too
+ALTER TABLE asset.copy_template
+    ALTER COLUMN floating TYPE int USING CASE WHEN floating THEN 1 ELSE NULL END;
+ALTER TABLE asset.copy_template ADD CONSTRAINT asset_copy_template_floating_fkey FOREIGN KEY (floating) REFERENCES config.floating_group (id) DEFERRABLE INITIALLY DEFERRED;
+
+INSERT INTO permission.perm_list( code, description) VALUES
+('ADMIN_FLOAT_GROUPS', 'Allows administration of floating groups');
+
+-- And lets just update all auditors to re-create those lifecycle views
+SELECT auditor.update_auditors();
diff --git a/Open-ILS/src/templates/conify/global/config/floating_groups.tt2 b/Open-ILS/src/templates/conify/global/config/floating_groups.tt2
new file mode 100644 (file)
index 0000000..035f647
--- /dev/null
@@ -0,0 +1,59 @@
+[% ctx.page_title = 'Floating Groups Configuration' %]
+[% WRAPPER base.tt2 %]
+<script type="text/javascript" src='[% ctx.media_prefix %]/js/ui/default/conify/global/config/floating_groups.js'> </script>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="top" class='oils-header-panel'>
+    <div>Floating Groups Configuration</div>
+    <div><button dojoType='dijit.form.Button' onClick='cfgGrid.showCreatePane()'>New</button></div>
+</div>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+    <table  jsId="cfgGrid"
+            style="height: 600px;"
+            dojoType="openils.widget.AutoGrid"
+            fieldOrder="['id', 'name', 'manual']"
+            defaultCellWidth='"auto"'
+            query="{id: '*'}"
+            fmClass='cfg'
+            editStyle='pane'
+            editOnEnter='true'
+            showColumnPicker='false'
+            columnPersistKey='"conify.config.floating_groups"'>
+    </table>
+</div>
+
+<div class='hidden'>
+    <div id='group-member-editor' style='border:1px solid #aaa'>
+        <h3>Linked Group Members</h3>
+        <table class='oils-generic-table'>
+            <thead>
+                <tr>
+                    <th>Org Unit</th>
+                    <th>Stop Depth</th>
+                    <th>Max Depth</th>
+                    <th>Exclude</th>
+                    <th>Remove</th>
+                </tr>
+            </thead>
+            <tbody name='group-member-entry-tbody'>
+                <tr name='group-member-entry-row'>
+                    <td name='org-unit'></td>
+                    <td><input type="text" name="group-member-stop-depth" value="0" /></td>
+                    <td><input type="text" name="group-member-max-depth" /></td>
+                    <td><input type="checkbox" name="group-member-exclude"/></td>
+                    <td><a name='remove-group-member' href='javascript:void(0);'>Remove</a></td>
+                </tr>
+            </tbody>
+            <tfoot name='group-member-entry-new'>
+                <tr>
+                    <td><div name='org-unit-selector'></div></td>
+                    <td colspan="4"><a href='javascript:void(0);' name='add-group-member'>Add</a></td>
+                </tr>
+            </tfoot>
+        </table>
+    </div>
+</div>
+
+
+<div class='hidden'><div dojoType='openils.widget.ProgressDialog' jsId='progressDialog'></div></div>
+
+[% END %]
+
diff --git a/Open-ILS/web/js/ui/default/conify/global/config/floating_groups.js b/Open-ILS/web/js/ui/default/conify/global/config/floating_groups.js
new file mode 100644 (file)
index 0000000..666670e
--- /dev/null
@@ -0,0 +1,197 @@
+dojo.require('dijit.layout.ContentPane');
+dojo.require('dijit.form.Button');
+dojo.require('openils.widget.AutoGrid');
+dojo.require('openils.widget.AutoFieldWidget');
+dojo.require('openils.PermaCrud');
+dojo.require('openils.widget.ProgressDialog');
+
+var groupMemberEditor = null;
+var groupMemberEntryCache = [];
+var orgUnitCache = {};
+
+function load(){
+    cfgGrid.loadAll({order_by:{cfg:'name'}});
+    cfgGrid.onEditPane = buildEditPaneAdditions;
+    cfgGrid.onPostUpdate = updateLinked;
+    cfgGrid.onPostCreate = updateLinked;
+    groupMemberEditor = dojo.byId('group-member-editor').parentNode.removeChild(dojo.byId('group-member-editor'));
+
+    // Cache org unit info for later display
+    var pcrud = new openils.PermaCrud();
+    var temp = pcrud.retrieveAll('aou');
+    dojo.forEach(temp, function(g) { orgUnitCache[g.id()] = g; } );
+}
+
+function byName(name, ctxt) {
+    return dojo.query('[name=' + name + ']', ctxt)[0];
+}
+
+function buildEditPaneAdditions(editPane) {
+    groupMemberEntryCache = [];
+    var tr = document.createElement('tr');
+    var td = document.createElement('td');
+    td.setAttribute('colspan','2');
+    // Explanation....
+    // editPane.domNode.lastChild = Table
+    // .lastChild = Table Body
+    // .lastChild = Table Row containing Action Buttons
+    editPane.domNode.lastChild.lastChild.insertBefore(tr, editPane.domNode.lastChild.lastChild.lastChild);
+    tr.appendChild(td);
+    curGroupMemberEditor = groupMemberEditor.cloneNode(true);
+    td.appendChild(curGroupMemberEditor);
+    var groupMemberTmpl = byName('group-member-entry-tbody', curGroupMemberEditor).removeChild(byName('group-member-entry-row', curGroupMemberEditor));
+
+    var selector = new openils.widget.AutoFieldWidget({
+        fmClass : 'cfgm',
+        fmField : 'org_unit',
+        parentNode : byName('org-unit-selector', curGroupMemberEditor)
+    });
+    selector.build();
+
+    function addMember(ounit) {
+        var row = groupMemberTmpl.cloneNode(true);
+        row.setAttribute('org_unit', ounit);
+        byName('org-unit', row).innerHTML = orgUnitCache[ounit].shortname();
+        byName('remove-group-member', row).onclick = function() {
+            byName('group-member-entry-tbody', cfgGrid.editPane.domNode).removeChild(row);
+        }
+        byName('group-member-entry-tbody', editPane.domNode).appendChild(row);
+    }
+
+    byName('add-group-member', editPane.domNode).onclick = function() {
+        addMember(selector.widget.attr('value'));
+    }
+
+    // On edit we need to load existing entries.
+    // On create, not so much.
+    if(!editPane.fmObject) return; 
+
+    if(editPane.mode == 'update') {
+        var pcrud = new openils.PermaCrud();
+        groupMemberEntryCache = pcrud.search('cfgm', {floating_group: editPane.fmObject.id()});
+        dojo.forEach(groupMemberEntryCache, function(g) { addGroupMember(groupMemberTmpl, g); } );
+    } 
+}
+
+function addGroupMember(tmpl, group_member_entry) {
+    var row = tmpl.cloneNode(true);
+    row.setAttribute('group_member', group_member_entry.id());
+    byName('org-unit', row).innerHTML = orgUnitCache[group_member_entry.org_unit()].shortname();
+    byName('group-member-stop-depth', row).value = group_member_entry.stop_depth();
+    if(group_member_entry.max_depth() != null) {
+        byName('group-member-max-depth', row).value = group_member_entry.max_depth();
+    }
+    if(group_member_entry.exclude() == 't') {
+        byName('group-member-exclude', row).setAttribute('checked', 'true');
+    }
+    byName('remove-group-member', row).onclick = function() {
+        byName('group-member-entry-tbody', cfgGrid.editPane.domNode).removeChild(row);
+    }
+    byName('group-member-entry-tbody', cfgGrid.editPane.domNode).appendChild(row);
+}
+
+function updateLinked(fmObject, rowindex) {
+    var id = null;
+    if(rowindex != undefined && this.editPane && this.editPane.fmObject) {
+        // Edit, grab existing ID
+        id = this.editPane.fmObject.id();
+    } else if(fmObject.id) {
+        // Create, grab new ID
+        id = fmObject.id();
+    }
+    // If we don't have an ID, drop out.
+    if(id == null) return;
+    var pcrud = new openils.PermaCrud();
+    progressDialog.show(true);
+
+    var add = [];
+    var remove = [];
+    var update = [];
+
+    var group_members = [];
+    dojo.query('[name=group-member-entry-row]', this.editPane.domNode).forEach(
+        function(row) {
+            var member_id = row.getAttribute('group_member');
+            var cached;
+            if (member_id)
+                cached = groupMemberEntryCache.filter(function(i) { return (i.id() == member_id); })[0];
+            var stop_depth = byName('group-member-stop-depth', row).value;
+            var max_depth = byName('group-member-max-depth', row).value;
+            if (max_depth === '') max_depth = null;
+            var exclude = byName('group-member-exclude', row).checked;
+            if (cached) {
+                group_members.push(member_id);
+                if((stop_depth != cached.stop_depth()) || (max_depth !== cached.max_depth()) || (exclude != (cached.exclude() == 't'))) {
+                    cached.stop_depth(stop_depth);
+                    cached.max_depth(max_depth);
+                    cached.exclude(exclude ? 't' : 'f');
+                    cached.ischanged(true);
+                    update.push(cached);
+                }
+            } else {
+                var entry = new fieldmapper.cfgm();
+                var org_unit = row.getAttribute('org_unit');
+                entry.isnew(true);
+                entry.floating_group(id);
+                entry.org_unit(org_unit);
+                entry.stop_depth(stop_depth);
+                entry.max_depth(max_depth);
+                entry.exclude(exclude ? 't' : 'f');
+                add.push(entry);
+            }
+        }
+    );
+    dojo.forEach(groupMemberEntryCache, function(eMember) {
+            if(!group_members.filter(function(i) { return (i == eMember.id()); })[0]) {
+                eMember.isdeleted(true);
+                remove.push(eMember);
+            }
+        }
+    );
+
+    function updateEntries() {
+        pcrud.update(update, {
+            oncomplete : function () {
+                progressDialog.hide();
+            }
+        });
+    }
+
+    function removeEntries() {
+        pcrud.eliminate(remove, {
+            oncomplete : function () {
+                if(update.length) {
+                    updateEntries();
+                } else {
+                    progressDialog.hide();
+                }
+            }
+        });
+    }
+
+    function addEntries() {
+        pcrud.create(add, {
+            oncomplete : function () {
+                if(remove.length) {
+                    removeEntries();
+                } else if (update.length) {
+                    updateEntries();
+                } else {
+                    progressDialog.hide();
+                }
+            }
+        });
+    }
+
+    if(add.length)
+        addEntries();
+    else if (remove.length)
+        removeEntries();
+    else if (update.length)
+        updateEntries();
+    else
+        progressDialog.hide();
+}
+
+openils.Util.addOnLoad(load);
+
index dafd068..c3c6cf0 100644 (file)
 <!ENTITY staff.main.menu.admin.server_admin.conify.config.remote_account "Remote Accounts">
 <!ENTITY staff.main.menu.admin.server_admin.conify.global_flag.label "Global Flags">
 <!ENTITY staff.main.menu.admin.server_admin.conify.circulation_limit_group.label "Circulation Limit Groups">
+<!ENTITY staff.main.menu.admin.server_admin.conify.floating_groups.label "Floating Groups">
 
 <!ENTITY staff.main.menu.admin.server_admin.acq.label "Acquisitions">
 <!ENTITY staff.main.menu.admin.server_admin.acq.accesskey "A">
 <!ENTITY staff.circ.checkin_overlay.checkin_auto_retarget_all_ind.label "Always Retarget Local Holds">
 <!ENTITY staff.circ.checkin_overlay.checkin_local_hold_as_transit.label "Capture Local Holds as Transits">
 <!ENTITY staff.circ.checkin_overlay.checkin_local_hold_as_transit.accesskey "L">
+<!ENTITY staff.circ.checkin_overlay.checkin_manual_float.label "Manual Floating Active">
+<!ENTITY staff.circ.checkin_overlay.checkin_manual_float.accesskey "F">
 <!ENTITY staff.circ.renew_overlay.background_text "Renew Item">
 <!ENTITY staff.circ.renew_overlay.sel_clip.label "Copy to Clipboard">
 <!ENTITY staff.circ.renew_overlay.sel_clip.accesskey "C">
index 74c8793..04af3b8 100644 (file)
@@ -667,6 +667,27 @@ OpenILS.data.prototype = {
         this.chain.push(
             function() {
                 var f = gen_fm_retrieval_func(
+                    'cfg',
+                    [
+                        api.FM_CFG_RETRIEVE_VIA_PCRUD.app,
+                        api.FM_CFG_RETRIEVE_VIA_PCRUD.method,
+                        [ obj.session.key, {"id":{"!=":null}}, {"order_by":{"cfg":"id"}} ],
+                        false
+                    ]
+                );
+                try {
+                    f();
+                } catch(E) {
+                    var error = 'Error: ' + js2JSON(E);
+                    obj.error.sdump('D_ERROR',error);
+                    throw(E);
+                }
+            }
+        );
+
+        this.chain.push(
+            function() {
+                var f = gen_fm_retrieval_func(
                     'csp',
                     [
                         api.FM_CSP_PCRUD_SEARCH.app,
index 95fa50f..3856f72 100644 (file)
@@ -254,6 +254,7 @@ var api = {
     'FM_CNAL_RETRIEVE' : { 'app' : 'open-ils.actor', 'method' : 'open-ils.actor.net_access_level.retrieve.all', 'secure' : false },
     'FM_CNCT_RETRIEVE' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.non_cat_types.retrieve.all', 'secure' : false },
     'FM_CRAHP_RETRIEVE' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.config.rules.age_hold_protect.retrieve.all', 'secure' : false },
+    'FM_CFG_RETRIEVE_VIA_PCRUD' : { 'app' : 'open-ils.pcrud', 'method' : 'open-ils.pcrud.search.cfg.atomic', 'secure' : false },
     'FM_CSC_RETRIEVE_VIA_PCRUD' : { 'app' : 'open-ils.pcrud', 'method' : 'open-ils.pcrud.search.csc.atomic' },
     'FM_CSP_PCRUD_SEARCH' : { 'app' : 'open-ils.pcrud', 'method' : 'open-ils.pcrud.search.csp.atomic', 'secure' : false },
     'FM_CST_RETRIEVE' : { 'app' : 'open-ils.actor', 'method' : 'open-ils.actor.standings.retrieve', 'secure' : false },
index c98b224..b69ebec 100644 (file)
@@ -808,6 +808,10 @@ main.menu.prototype = {
                 ['oncommand'],
                 function(event) { open_eg_web_page('conify/global/actor/org_unit_custom_tree', null, event); }
             ],
+            'cmd_server_admin_floating_groups' : [
+                ['oncommand'],
+                function(event) { open_eg_web_page('conify/global/config/floating_groups', 'menu.cmd_server_admin_floating_groups.tab', event); }
+            ],
             'cmd_local_admin_external_text_editor' : [
                 ['oncommand'],
                 function() {
index 2fba991..2288254 100644 (file)
     <command id="cmd_server_admin_vandelay_import_bib_trash_group"
              perm="CREATE_IMPORT_TRASH_FIELD UPDATE_IMPORT_TRASH_FIELD DELETE_IMPORT_TRASH_FIELD"
              />
+    <command id="cmd_server_admin_floating_groups"
+             perm="ADMIN_FLOAT_GROUPS"
+             />
 
     <command id="cmd_hotkeys_toggle" />
     <command id="cmd_hotkeys_set" />
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.z3950_index_field_map.label;" command="cmd_server_admin_z39_index_field_map"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.circulation_modifier.label;" command="cmd_server_admin_circ_mod"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.circulation_limit_group.label;" command="cmd_server_admin_circ_limit_group"/>
+                <menuitem label="&staff.main.menu.admin.server_admin.conify.floating_groups.label;" command="cmd_server_admin_floating_groups"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.global_flag.label;" command="cmd_server_admin_global_flag"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.import_match_set;" command="cmd_server_admin_import_match_set"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.org_unit_setting_type;" command="cmd_server_admin_org_unit_setting_type"/>
index adf86a6..b16e2a1 100644 (file)
@@ -333,3 +333,4 @@ util.hide_elements.update_setting.update_success=Successfully updated the settin
 util.hide_elements.update_setting.delete_success=Successfully deleted the setting.
 util.hide_elements.update_setting.failure=Setting not changed.
 util.hide_elements.missing_permission=Missing permission %1$s
+menu.cmd_server_admin_floating_groups.tab=Floating Groups
index 1e826c2..67f7971 100644 (file)
@@ -1070,8 +1070,8 @@ g.panes_and_field_names = {
     [
         $('catStrings').getString('staff.cat.copy_editor.field.floating.label'),
         { 
-            render: 'fm.floating() == null ? $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null") : ( get_bool( fm.floating() ) ? $("catStrings").getString("staff.cat.copy_editor.field.floating.yes_or_true") : $("catStrings").getString("staff.cat.copy_editor.field.floating.no_or_false") )',
-            input: 'c = function(v){ g.apply("floating",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( [ [ $("catStrings").getString("staff.cat.copy_editor.field.floating.yes_or_true"), get_db_true() ], [ $("catStrings").getString("staff.cat.copy_editor.field.floating.no_or_false"), get_db_false() ] ] ); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
+            render: 'fm.floating() == null ? $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null") : ( typeof fm.floating() == "object" ? fm.floating().name() : g.data.hash.cfg[ fm.floating() ].name() )',
+            input: 'c = function(v){ g.apply("floating",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( [ [ $("catStrings").getString("staff.cat.copy_editor.remove_floating"), "<HACK:KLUDGE:NULL>" ] ].concat( util.functional.map_list( g.data.list.cfg, function(obj) { return [ obj.name(), obj.id() ]; }).sort() ) ); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
         }
     ],
     [
index 29a7dd8..0aa656f 100644 (file)
@@ -202,7 +202,7 @@ function load_item() {
                 set("circ_modifier","");
             }
             set("circulate", get_localized_bool( details.copy.circulate() )); 
-            set("floating", get_localized_bool( details.copy.floating() )); 
+            set("floating", (details.copy.floating() && typeof details.copy.floating() == 'object' ? details.copy.floating().name() : get_localized_bool( details.copy.floating() ))); 
             set("copy_number", details.copy.copy_number()); 
             set("copy_create_date", util.date.formatted_date( details.copy.create_date(), '%{localized}' )); 
             set("copy_active_date", util.date.formatted_date( details.copy.active_date(), '%{localized}' ));
index 631122a..2cde9de 100644 (file)
@@ -469,6 +469,18 @@ circ.checkin.prototype = {
                         document.getElementById('checkin_barcode_entry_textbox').focus();
                         return true;
 
+                    } ],
+                    'cmd_checkin_manual_float' : [ ['command'], function(ev) {
+                        dump('in cmd_checkin_manual_float\n');
+                        var bg = document.getElementById('background');
+                        var cb = document.getElementById('checkin_manual_float');
+                        var ind = document.getElementById('checkin_manual_float_indicator');
+                        var cn = 'checkin_screen_checkin_manual_float';
+                        if (cb.getAttribute('checked') == 'true') { addCSSClass(bg,cn); } else { removeCSSClass(bg,cn); }
+                        ind.hidden = cb.getAttribute('checked') != 'true'; 
+                        document.getElementById('checkin_barcode_entry_textbox').focus();
+                        return true;
+
                     } ]
                 }
             }
@@ -646,6 +658,9 @@ circ.checkin.prototype = {
             var hold_as_transit = document.getElementById('checkin_local_hold_as_transit');
             if (hold_as_transit) hold_as_transit = hold_as_transit.getAttribute('checked') == 'true';
             if (hold_as_transit) params.hold_as_transit = 1;
+            var manual_float = document.getElementById('checkin_manual_float');
+            if (manual_float) manual_float = manual_float.getAttribute('checked') == 'true';
+            if (manual_float) params.manual_float = 1;
             circ.util.checkin_via_barcode(
                 ses(), 
                 params,
index 9cc6b67..22731c7 100644 (file)
@@ -39,6 +39,7 @@
     <command id="cmd_checkin_clear_shelf_expired" />
     <command id="cmd_checkin_auto_retarget" />
     <command id="cmd_checkin_local_hold_as_transit" />
+    <command id="cmd_checkin_manual_float" />
 </commandset>
 
 
@@ -83,6 +84,7 @@
                 <description id="checkin_auto_retarget_indicator" hidden="true">&staff.circ.checkin_overlay.checkin_auto_retarget.label;</description>
                 <description id="checkin_auto_retarget_all_indicator" hidden="true">&staff.circ.checkin_overlay.checkin_auto_retarget_all_ind.label;</description>
                 <description id="checkin_local_hold_as_transit_indicator" hidden="true">&staff.circ.checkin_overlay.checkin_local_hold_as_transit.label;</description>
+                <description id="checkin_manual_float_indicator" hidden="true">&staff.circ.checkin_overlay.checkin_manual_float.label;</description>
             </vbox>
         </vbox>
         <spacer flex="1"/>
                 label="&staff.circ.checkin_overlay.checkin_auto_retarget_all.label;" accesskey="&staff.circ.checkin_overlay.checkin_auto_retarget_all.accesskey;"/>
             <menuitem type="checkbox" id="checkin_local_hold_as_transit" oils_persist="checked" command="cmd_checkin_local_hold_as_transit"
                 label="&staff.circ.checkin_overlay.checkin_local_hold_as_transit.label;" accesskey="&staff.circ.checkin_overlay.checkin_local_hold_as_transit;"/>
+            <menuitem type="checkbox" id="checkin_manual_float" oils_persist="checked" command="cmd_checkin_manual_float"
+                label="&staff.circ.checkin_overlay.checkin_manual_float.label;" accesskey="&staff.circ.checkin_overlay.checkin_manual_float.accesskey;"/>
         </menupopup>
     </button>
 </hbox>
index 19e728a..42b1df7 100644 (file)
@@ -970,10 +970,14 @@ circ.util.columns = function(modify,params) {
             'primary' : false,
             'hidden' : true,
             'editable' : false, 'render' : function(my) {
-                if (get_bool( my.acp.floating() )) {
-                    return document.getElementById('circStrings').getString('staff.circ.utils.yes');
+                if (my.acp.floating() && typeof my.acp.floating() == 'object') {
+                    return my.acp.floating().name();
                 } else {
-                    return document.getElementById('circStrings').getString('staff.circ.utils.no');
+                    if (get_bool( my.acp.floating() )) {
+                        return document.getElementById('circStrings').getString('staff.circ.utils.yes');
+                    } else {
+                        return document.getElementById('circStrings').getString('staff.circ.utils.no');
+                    }
                 }
             },
             'persist' : 'hidden width ordinal'
index f42ec10..ef4772d 100644 (file)
@@ -165,6 +165,7 @@ staff.cat.copy_editor.populate_stat_cat.error=Error populating statistical categ
 staff.cat.copy_editor.remove_stat_cat_entry=<Remove Stat Cat>
 staff.cat.copy_editor.remove_age_based_hold_protection=<Remove Protection>
 staff.cat.copy_editor.remove_circulate_as_type=<Remove Circulate as Type>
+staff.cat.copy_editor.remove_floating=<Remove Floating>
 staff.cat.copy_editor.field.unset_or_null=<Unset>
 staff.cat.copy_editor.field.owning_library.label=Owning Lib : Call Number
 staff.cat.copy_editor.field.creator.label=Creator
@@ -183,8 +184,6 @@ staff.cat.copy_editor.field.holdable.label=Holdable?
 staff.cat.copy_editor.field.holdable.yes_or_true=Yes
 staff.cat.copy_editor.field.holdable.no_or_false=No
 staff.cat.copy_editor.field.floating.label=Floating?
-staff.cat.copy_editor.field.floating.yes_or_true=Yes
-staff.cat.copy_editor.field.floating.no_or_false=No
 staff.cat.copy_editor.field.age_based_hold_protection.label=Age-based Hold Protection
 staff.cat.copy_editor.field.loan_duration.label=Loan Duration
 staff.cat.copy_editor.field.loan_duration.short=Short
index 6069258..aebf50b 100644 (file)
@@ -90,6 +90,7 @@ treechildren::-moz-tree-row(backdate_failed,selected) {
 #checkin_auto_retarget_indicator { background-color: -moz-dialog; color: -moz-dialog-text; font-size: large; font-weight: bold; }
 #checkin_auto_retarget_all_indicator { background-color: -moz-dialog; color: -moz-dialog-text; font-size: large; font-weight: bold; }
 #checkin_local_hold_as_transit_indicator { background-color: -moz-dialog; color: -moz-dialog-text; font-size: large; font-weight: bold; }
+#checkin_manual_float_indicator { background-color: -moz-dialog; color: -moz-dialog-text; font-size: large; font-weight: bold; }
 
 .big_emphasis1 { font-weight: bold; font-size: x-large; }
 .big_emphasis2 { font-weight: bold; font-size: large; }
diff --git a/docs/floating_groups.txt b/docs/floating_groups.txt
new file mode 100644 (file)
index 0000000..a484c5e
--- /dev/null
@@ -0,0 +1,122 @@
+Floating Groups
+===============
+Thomas Berezansky <tsbere@mvlc.org>
+:Date: 2012-04-22
+
+Before floating groups copies could float or not. If they floated then they floated everywhere, with no restrictions.
+
+After floating groups where a copy will float is defined by what group it has been assigned to.
+
+== Floating Groups
+
+Each floating group comes with a name and a manual flag, plus zero or more group members. The name is used solely for selection and display purposes.
+
+The manual flag dictates whether or not the "Manual Floating Active" checkin modifier needs to be active for a copy to float. This allows for greater control over when items float. It also prevents automated checkins via SIP2 from triggering floats.
+
+== Floating Group Members
+
+Each member of a floating group references an org unit and has a stop depth, an optional max depth, and an exclude flag.
+
+=== Org Unit
+
+The org unit and all descendants are included, unless max depth is set, in which case the tree is cut off at the max depth.
+
+=== Stop Depth
+
+The stop depth is the highest point from the current copy circ library to the checkin library for the item that will be traversed. If the item has to go higher than the stop depth on the tree the member rule in question is ignored.
+
+=== Max Depth
+
+As mentioned with the org unit, the max depth is the furthest down on the tree from the org unit that gets included. This is based on the entire tree, not just off of the org unit. So in the default tree a max depth of 1 will stop at the system level no matter if org unit is set to CONS or SYS1.
+
+=== Exclude
+
+Exclude, if set, causes floating to not happen for the member. Excludes always take priority, so you can remove an org unit from floating without having to worry about other rules overriding it.
+
+== Examples
+
+=== Float Everywhere
+
+This is a default floating rule to emulate the previous floating behavior for new installs and upgrades.
+
+One member:
+
+* Org Unit: CONS
+* Stop Depth: 0
+* Max Depth: Unset
+* Exclude: Off
+
+=== Float Within System
+
+This would permit a copy to float anywhere within a system, but would return to the system if it was returned elsewhere.
+
+One member:
+
+* Org Unit: CONS
+* Stop Depth: 1
+* Max Depth: Unset
+* Exclude: Off
+
+=== Float To All Branches
+
+This would permit a copy to float to any branch, but not to sublibraries or bookmobiles.
+
+One member:
+
+* Org Unit: CONS
+* Stop Depth: 0
+* Max Depth: 2
+* Exclude: Off
+
+=== Float To All Branches Within System
+
+This would permit a copy to float to any branch in a system, but not to sublibraries or bookmobiles, and returning to the system if returned elsewhere.
+
+One member:
+
+* Org Unit: CONS
+* Stop Depth: 1
+* Max Depth: 2
+* Exclude: Off
+
+=== Float Between BR1 and BR3
+
+This would permit a copy to float between BR1 and BR3 specifically, excluding sublibraries and bookmobiles.
+
+It would consist of two members, identical other than the org unit:
+
+* Org Unit: BR1 / BR3
+* Stop Depth: 0
+* Max Depth: 2
+* Exclude: Off
+
+=== Float Everywhere Except BM1
+
+This would allow an item to float anywhere except for BM1. It accomplishes this with two members.
+
+The first includes all org units, just like Float Everywhere:
+
+* Org Unit: CONS
+* Stop Depth: 0
+* Max Depth: Unset
+* Exclude: Off
+
+The second excludes BM1:
+
+* Org Unit: BM1
+* Stop Depth: 0
+* Max Depth: Unset
+* Exclude: On
+
+That works because excludes are applied first.
+
+=== Float into, but not out of, BR2
+
+This would allow an item to float into BR2, but once there it would never leave. Why you would want to allow items to float to but not from a single library I dunno, but here it is. This takes advantage of the fact that the rules say where we can float *to*, but outside of stop depth don't care where we are floating *from*.
+
+One member:
+
+* Org Unit: BR2
+* Stop Depth: 0
+* Max Depth: Unset
+* Exclude: Off