[open-ils-commits] [GIT] Evergreen ILS branch master updated. fec48cdd66f2338a3cea762e840352d448426b3d

Evergreen Git git at git.evergreen-ils.org
Mon Apr 2 13:34:31 EDT 2012


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Evergreen ILS".

The branch, master has been updated
       via  fec48cdd66f2338a3cea762e840352d448426b3d (commit)
       via  69bf18f784625f011cda61ec7b82a1785ae2c5ae (commit)
       via  218722deb2a57f8b1f94869e12602c1286c10aae (commit)
       via  3ebff585f662cd57890113e02ef557e127facaf3 (commit)
       via  0ea00f3eef4aa1da707563784386fb73a170e1ce (commit)
       via  43999e0c9c6c6334e6c5e7b70a9c4c43327a5844 (commit)
       via  3f4ca6dd32b5ce2e36c91af836ee33cd5546fa1b (commit)
       via  d047646aff7f5aca23ac2a86422e3c869ea3d405 (commit)
      from  1257ffe6071a2f9d77bb877f0fd6506edb88c11f (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit fec48cdd66f2338a3cea762e840352d448426b3d
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Apr 2 13:41:00 2012 -0400

    Stamping upgrade script for "Org unit selective hiding and sorting in tpac library selector"
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>

diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql
index 34224b1..734f3a9 100644
--- a/Open-ILS/src/sql/Pg/002.schema.config.sql
+++ b/Open-ILS/src/sql/Pg/002.schema.config.sql
@@ -87,7 +87,7 @@ CREATE TRIGGER no_overlapping_deps
     BEFORE INSERT OR UPDATE ON config.db_patch_dependencies
     FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('deprecates');
 
-INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0701', :eg_version); -- sprater/bshum/tsbere
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0702', :eg_version); -- berick/miker
 
 CREATE TABLE config.bib_source (
 	id		SERIAL	PRIMARY KEY,
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.org_unit_opac_vis_and_sorting.sql b/Open-ILS/src/sql/Pg/upgrade/0702.schema.org_unit_opac_vis_and_sorting.sql
similarity index 95%
rename from Open-ILS/src/sql/Pg/upgrade/XXXX.schema.org_unit_opac_vis_and_sorting.sql
rename to Open-ILS/src/sql/Pg/upgrade/0702.schema.org_unit_opac_vis_and_sorting.sql
index fd1c002..8c6cd4f 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.org_unit_opac_vis_and_sorting.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/0702.schema.org_unit_opac_vis_and_sorting.sql
@@ -1,6 +1,6 @@
 BEGIN;
 
---SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+SELECT evergreen.upgrade_deps_block_check('0702', :eg_version);
 
 INSERT INTO config.global_flag (name, enabled, label) 
     VALUES (

commit 69bf18f784625f011cda61ec7b82a1785ae2c5ae
Author: Bill Erickson <berick at esilibrary.com>
Date:   Fri Mar 30 09:38:56 2012 -0400

    Custom Org Tree : Admin UI honors "activate" on new trees
    
    Previously, activating a new tree would work, but the UI did not update
    to reflect it.  Now the activate link correctly turns into a de-activate
    link and vice versa.
    
    Signed-off-by: Bill Erickson <berick at esilibrary.com>
    Signed-off-by: Mike Rylander <mrylander at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/conify/global/actor/org_unit_custom_tree.js b/Open-ILS/web/js/ui/default/conify/global/actor/org_unit_custom_tree.js
index cc835f7..695a9c4 100644
--- a/Open-ILS/web/js/ui/default/conify/global/actor/org_unit_custom_tree.js
+++ b/Open-ILS/web/js/ui/default/conify/global/actor/org_unit_custom_tree.js
@@ -247,7 +247,15 @@ function deleteSelected() {
 
 function activateTree() {
     mTree.active('t');
-    if (mTree.isnew()) return;
+
+    if (mTree.isnew()) {
+        // before the tree exists, we can only activate the local copy
+        // the next save event will activate it
+        openils.Util.hide(dojo.byId('activate-tree'));
+        openils.Util.show(dojo.byId('deactivate-tree'), 'inline');
+        return;
+    }
+
     pcrud.update(mTree, {
         oncomplete : function() {
             openils.Util.hide(dojo.byId('activate-tree'));
@@ -258,7 +266,13 @@ function activateTree() {
 
 function deactivateTree() {
     mTree.active('f');
-    if (mTree.isnew()) return;
+
+    if (mTree.isnew()) {
+        openils.Util.hide(dojo.byId('deactivate-tree'));
+        openils.Util.show(dojo.byId('activate-tree'), 'inline');
+        return;
+    }
+
     pcrud.update(mTree, {
         oncomplete : function() {
             openils.Util.hide(dojo.byId('deactivate-tree'));

commit 218722deb2a57f8b1f94869e12602c1286c10aae
Author: Bill Erickson <berick at esilibrary.com>
Date:   Wed Mar 21 14:34:19 2012 -0400

    TPac: non-inherited org unit visibility : hide copy counts
    
    Avoid showing copy counts for non-opac-visible org units in the search
    results and record details page.  This is important when the
    'opac.org_unit.non_inheritied_visibility' global flag is enabled, as org
    units along the tree may be non-visible.
    
    Signed-off-by: Bill Erickson <berick at esilibrary.com>
    Signed-off-by: Mike Rylander <mrylander at gmail.com>

diff --git a/Open-ILS/src/templates/opac/parts/record/copy_counts.tt2 b/Open-ILS/src/templates/opac/parts/record/copy_counts.tt2
index a573161..cd59442 100644
--- a/Open-ILS/src/templates/opac/parts/record/copy_counts.tt2
+++ b/Open-ILS/src/templates/opac/parts/record/copy_counts.tt2
@@ -7,7 +7,12 @@
         WHILE depth < depths;
             ou_avail = ctx.copy_summary.$depth.available;
             ou_id = ctx.copy_summary.$depth.org_unit;
-            ou_name = ctx.get_aou(ou_id).name;
+            cp_org_unit = ctx.get_aou(ou_id);
+            IF cp_org_unit.opac_visible == 'f' AND !ctx.is_staff;
+                depth = depth + 1;
+                NEXT;
+            END;
+            ou_name = cp_org_unit.name;
             displayed_ous.$ou_name = 1;
     %]
     <li>
diff --git a/Open-ILS/src/templates/opac/parts/result/copy_counts.tt2 b/Open-ILS/src/templates/opac/parts/result/copy_counts.tt2
index c183744..639877e 100644
--- a/Open-ILS/src/templates/opac/parts/result/copy_counts.tt2
+++ b/Open-ILS/src/templates/opac/parts/result/copy_counts.tt2
@@ -7,11 +7,13 @@
         IF attrs.copy_counts.$depth.count > 0;
 %]
 <div class="result_count">
+[% IF ctx.get_aou(attrs.copy_counts.$depth.org_unit).opac_visible == 't' AND !ctx.is_staff %]
     [% l('[_1] of [quant,_2,copy,copies] available at [_3].',
         attrs.copy_counts.$depth.available,
         attrs.copy_counts.$depth.count,
         ou_name) | html
     %]
+[% END %]
 </div>
 [%-     END;
     depth = depth + 1;

commit 3ebff585f662cd57890113e02ef557e127facaf3
Author: Bill Erickson <berick at esilibrary.com>
Date:   Tue Mar 20 15:16:09 2012 -0400

    Custom Org Tree : Admin UI
    
    New UI for managing custom org unit trees.  (Only OPAC is supported for
    now).  Custom trees default to the same shape as the full org unit tree.
    Staff can remove nodes and re-order nodes.  Staff can also replace
    deleted nodes by dragging them from the reference tree on the left.
    
    Admin -> Server Admin -> Custom Org Unit Trees
    
    Signed-off-by: Bill Erickson <berick at esilibrary.com>
    Signed-off-by: Mike Rylander <mrylander at gmail.com>

diff --git a/Open-ILS/src/templates/conify/global/actor/org_unit_custom_tree.tt2 b/Open-ILS/src/templates/conify/global/actor/org_unit_custom_tree.tt2
new file mode 100644
index 0000000..9d259b0
--- /dev/null
+++ b/Open-ILS/src/templates/conify/global/actor/org_unit_custom_tree.tt2
@@ -0,0 +1,73 @@
+[% WRAPPER base.tt2 %]
+[% ctx.page_title = l('Org Unit Custom Tree') %]
+<script type="text/javascript" src='[% ctx.media_prefix %]/js/ui/default/conify/global/actor/org_unit_custom_tree.js'> </script>
+<link rel='stylesheet' type='text/css' href='[% ctx.media_prefix %]/js/dojo/dojo/resources/dnd.css'/>
+<link rel='stylesheet' type='text/css' href='[% ctx.media_prefix %]/js/dojo/dojo/resources/dndDefault.css'/>
+
+<style>
+    #wrapper     {width : 100%; height: 100%}
+    .block       {height: 100%; float : left; vertical-align : top; text-align: left; overflow: auto;}
+    #left-block  {border-right: 2px solid #333; margin-right: 20px; padding-right: 40px; max-width 40%}
+    #right-block  {max-width: 58%}
+    .action-wrapper { padding-right: 5px; margin-right: 5px; border-right: 2px dotted #333; }
+    .tree-actions {
+        width : 98%; 
+        padding: 5px; 
+        margin: 5px; 
+        background-color:#E7A555;
+        border-bottom: 2px solid #4A4747;
+    }
+</style>
+
+<h2>[% l('Org Unit Custom Tree') %]</h2>
+<ul>
+    <li>[% l('To add new nodes to the custom tree, drag them from the full tree on the left') %]</li>
+    <li>[% l('Changes to custom org trees may require web server (apache) reload before taking effect') %]</li>
+</ul>
+<hr/>
+
+<div id='wrapper'>
+    <div id='left-block' class='block'>
+        <h3>[% l('Full Org Unit Tree') %]</h3>
+        <div class='tree-actions'>
+            <a href='javascript:;' onClick='realTree.expandAll()'>[% l('Expand') %]</a>&nbsp;/&nbsp;<a 
+                href='javascript:;' onClick='realTree.collapseAll()'>[% l('Collapse') %]</a>
+        </div>
+        <div id='real-tree'></div>
+    </div>
+    <div id='right-block' class='block'>
+        <table>
+            <td style='padding-right: 10px;'>
+                <h3>[% l('Custom Unit Tree: ') %]</h3>
+            </td>
+            <td style='vertical-align: bottom'>
+                <!-- 'opac' is currently the only purpose -->
+                <select jsId='treePurposeSelector' dojoType='dijit.form.FilteringSelect' disabled='disabled' onChange='drawMagicTree()'>
+                    <option value='opac'>[% l('OPAC') %]</option>
+                </select>
+            </td>
+        </tr></table>
+        <div class='tree-actions'>
+            <span class='action-wrapper'>
+                <a href='javascript:;' onClick='magicTree.expandAll()'>[% l('Expand') %]</a>&nbsp;/&nbsp;<a 
+                    href='javascript:;' onClick='magicTree.collapseAll()'>[% l('Collapse') %]</a>
+            </span>
+            <span class='action-wrapper'>
+                <a href='javascript:;' onClick='deleteSelected()'>[% l('Delete Selected') %]</a>
+            </span>
+            <span class='action-wrapper'>
+                <a id='activate-tree'   href='javascript:;' onClick='activateTree()'>[% l('Activate Tree') %]</a>
+                <a id='deactivate-tree' href='javascript:;' onClick='deactivateTree()' class='hidden'>[% l('Deactivate Tree') %]</a>
+            </span>
+            <span>
+                <a href='javascript:;' onClick='applyChanges()'>[% l('Apply Changes') %]</a>
+            </span>
+        </div>
+        <div id='magic-tree'></div>
+    </div>
+</div>
+
+<div jsId="progressDialog" dojoType="openils.widget.ProgressDialog"></div>
+[% END %]
+
+
diff --git a/Open-ILS/web/js/ui/default/conify/global/actor/org_unit_custom_tree.js b/Open-ILS/web/js/ui/default/conify/global/actor/org_unit_custom_tree.js
new file mode 100644
index 0000000..cc835f7
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/conify/global/actor/org_unit_custom_tree.js
@@ -0,0 +1,284 @@
+dojo.require('dijit.form.Button');
+dojo.require('dijit.form.FilteringSelect');
+dojo.require('dojo.data.ItemFileReadStore');
+dojo.require('dojo.data.ItemFileWriteStore');
+dojo.require('dijit.Tree');
+dojo.require('dijit.tree.TreeStoreModel');
+dojo.require("dijit._tree.dndSource");
+dojo.require('fieldmapper.Fieldmapper');
+dojo.require('fieldmapper.OrgUtils');
+dojo.require('openils.User');
+dojo.require('openils.Util');
+dojo.require('openils.PermaCrud');
+dojo.require('openils.widget.ProgressDialog');
+
+var realTree; // dijit.Tree
+var magicTree; // dijit.Tree
+var mTree; // aouct object
+var pcrud;
+var virtId = -1;
+var realOrgList = [];
+var ctNodes = [];
+
+dojo.declare(
+    'openils.actor.OrgUnitCustomTreeSource', dijit._tree.dndSource, {
+    itemCreator : function(nodes, etc) {
+        var items = this.inherited(arguments);
+        dojo.forEach(items, function(item) {item.shortname = item.name});
+        return items;
+    }
+});
+
+dojo.declare(
+    'openils.actor.OrgUnitCustomTreeStoreModel', dijit.tree.TreeStoreModel, {
+    mayHaveChildren : function(item) { return true },
+});
+
+function drawPage() {
+    pcrud = new openils.PermaCrud({authtoken : openils.User.authtoken});
+
+    // real org unit list.  Not write-able.  Used only as a source.
+    realOrgList = openils.Util.objectValues(
+        fieldmapper.aou.OrgCache).map(function(obj) { return obj.org });
+
+    var store = new dojo.data.ItemFileReadStore(
+        {data : fieldmapper.aou.toStoreData(realOrgList)});
+            
+    var model = new dijit.tree.TreeStoreModel({
+        store: store,
+        query: {_top : 'true'}
+    });
+
+    realTree = new dijit.Tree(
+        {   model: model,
+            expandAll: function() {treeDoAll(this)},
+            collapseAll: function() {treeDoAll(this, true)},
+            dndController : dijit._tree.dndSource,
+            persist : false,
+        }, 
+        'real-tree'
+    );
+
+    realTree.expandAll();
+    drawMagicTree();
+}
+
+
+// composed of org units.  Write-able.
+function drawMagicTree() {
+    var orgList = realOrgList;
+    var query = {_top : 'true'};
+
+    var mTreeRes = pcrud.search('aouct', 
+        {purpose : treePurposeSelector.attr('value')});
+
+    if (mTreeRes.length) {
+        mTree = mTreeRes[0];
+        if (openils.Util.isTrue(mTree.active())) {
+            openils.Util.hide(dojo.byId('activate-tree'));
+            openils.Util.show(dojo.byId('deactivate-tree'), 'inline');
+        }
+        ctNodes = pcrud.search('aouctn', {tree : mTree.id()});
+        if (ctNodes.length) {
+            orgList = [];
+            // create an org tree from the custom tree nodes
+           
+            dojo.forEach(ctNodes, 
+                function(node) {
+                    // deep clone to avoid globalOrgTree clobbering
+                    var org = JSON2js(js2JSON( 
+                        fieldmapper.aou.findOrgUnit(node.org_unit())
+                    ));
+                    org.parent_ou(null);
+                    org.children([]);
+                    if (node.parent_node()) {
+                        org.parent_ou(
+                            ctNodes.filter(
+                                function(n) {return n.id() == node.parent_node()}
+                            )[0].org_unit()
+                        );
+                    }
+                    orgList.push(org);
+                }
+            );
+            var root = ctNodes.filter(function(n) {return n.parent_node() == null})[0];
+            query = {id : root.org_unit()+''}
+        }
+    } else {
+
+        mTree = new fieldmapper.aouct();
+        mTree.isnew(true);
+        mTree.purpose(treePurposeSelector.attr('value'));
+        mTree.active(false);
+    }
+
+    var store = new dojo.data.ItemFileWriteStore(
+        {data : fieldmapper.aou.toStoreData(orgList)});
+
+    var model = new openils.actor.OrgUnitCustomTreeStoreModel({
+        store : store,
+        query : query
+    });
+
+    magicTree = new dijit.Tree(
+        {   model: model,
+            expandAll: function() {treeDoAll(this)},
+            collapseAll: function() {treeDoAll(this, true)},
+            dndController : openils.actor.OrgUnitCustomTreeSource,
+            dragThreshold : 8,
+            betweenThreshold : 5,
+            persist : false,
+        }, 
+        'magic-tree'
+    );
+
+    magicTree.expandAll();
+}
+
+// 1. create the tree if necessary
+// 2. translate the dijit.tree nodes into aouctn's
+// 3. delete the existing aouctn's
+// 4. create the new aouctn's
+function applyChanges() {
+    progressDialog.show();
+
+    if (mTree.isnew()) {
+
+        pcrud.create(mTree, {
+            oncomplete : function(r, objs) {
+                mTree = objs[0];
+                applyChanges2();
+            }
+        });
+    
+    } else {
+        if (ctNodes.length) { 
+            console.log('Deleting ' + ctNodes.length + ' nodes');
+            pcrud.eliminate(ctNodes, {oncomplete : applyChanges2});
+        } else {
+            applyChanges2();
+        }
+    }
+}
+
+function applyChanges2() {
+    ctNodes = [];
+    var newCtNodes = [];
+    var nodeList = [];
+    var sorder = 0;
+    var prevTn;
+    var progress = 0;
+
+    function flatten(node) {
+        nodeList.push(node);
+        dojo.forEach(node.getChildren(), flatten);
+    }
+    flatten(magicTree.rootNode);
+
+    // traverse the nodes, creating new aoucnt's as we go
+    function traverseAndCreate(node) {
+        var item = node.item;
+
+        var tn = new fieldmapper.aouctn();
+        tn.tree(mTree.id());
+        tn.org_unit(item.id[0])
+
+        var pnode = node.getParent();
+        if (pnode) {
+            // find the newly created parent node and extract the ID 
+            var ptn = ctNodes.filter(function(n) {
+                return n.org_unit() == pnode.item.id[0]})[0];
+            tn.parent_node(ptn.id());
+        }
+
+        // if the last node was our previous sibling
+        if (prevTn && prevTn.parent_node() == tn.parent_node()) {
+            tn.sibling_order(++sorder);
+        } else { sorder = 0; }
+
+        console.log("Creating new node for org unit " + tn.org_unit());
+
+        // create the new node, then process the children
+        pcrud.create(tn, {
+            oncomplete : function(r, objs) {
+                var newTn = objs[0];
+                ctNodes.push(newTn);
+                prevTn = newTn;
+                if (nodeList.length == 0) {
+                    progressDialog.hide();
+                    location.href = location.href;
+                } else {
+                    progressDialog.update({maximum : nodeList.length, progress : ++progress});
+                    traverseAndCreate(nodeList.shift());
+                }
+            }
+        });
+    }
+    traverseAndCreate(nodeList.shift());
+}
+
+function deleteSelected() {
+    var toDelete = [];
+
+    function collectChildren(item) {
+        toDelete.push(item);
+        magicTree.model.store.fetch({
+            query : {parent_ou : item.id[0]+''},
+            onComplete : function(list) { 
+                dojo.forEach(list, collectChildren) 
+            }
+        });
+    }
+
+    magicTree.dndController.getSelectedItems().forEach(
+        function(item) {
+            if (item === magicTree.model.root) return
+            collectChildren(item);
+            // delete node plus children, starting at the leaf nodes
+            dojo.forEach(toDelete.reverse(),
+                function(i) {
+                    console.log('Deleting item ' + i.id);
+                    magicTree.model.store.deleteItem(i)
+                }
+            );
+        }
+    );
+}
+
+function activateTree() {
+    mTree.active('t');
+    if (mTree.isnew()) return;
+    pcrud.update(mTree, {
+        oncomplete : function() {
+            openils.Util.hide(dojo.byId('activate-tree'));
+            openils.Util.show(dojo.byId('deactivate-tree'), 'inline');
+        }
+    });
+}
+
+function deactivateTree() {
+    mTree.active('f');
+    if (mTree.isnew()) return;
+    pcrud.update(mTree, {
+        oncomplete : function() {
+            openils.Util.hide(dojo.byId('deactivate-tree'));
+            openils.Util.show(dojo.byId('activate-tree'), 'inline');
+        }
+    });
+}
+
+// modified from 
+// http://stackoverflow.com/questions/2161032/expanding-all-nodes-in-dijit-tree
+function treeDoAll(tree, collapse) {
+    function expand(node) {
+        if (collapse) tree._collapseNode(node);
+        else tree._expandNode(node);
+        var childBranches = dojo.filter(node.getChildren() || [], 
+            function(node) { return node.isExpandable });
+        var def = new dojo.Deferred();
+        defs = dojo.map(childBranches, expand);
+    }
+    return expand(tree.rootNode);
+}
+
+openils.Util.addOnLoad(drawPage);
diff --git a/Open-ILS/web/opac/locale/en-US/lang.dtd b/Open-ILS/web/opac/locale/en-US/lang.dtd
index 024419d..e9385dd 100644
--- a/Open-ILS/web/opac/locale/en-US/lang.dtd
+++ b/Open-ILS/web/opac/locale/en-US/lang.dtd
@@ -770,6 +770,7 @@
 <!ENTITY staff.main.menu.admin.server_admin.conify.config_actor_sip_fields "Actor Stat Cat Sip Fields">
 <!ENTITY staff.main.menu.admin.server_admin.conify.config_asset_sip_fields "Asset Stat Cat Sip Fields">
 <!ENTITY staff.main.menu.admin.server_admin.conify.config_usr_activity_type "User Activity Types">
+<!ENTITY staff.main.menu.admin.server_admin.conify.actor.org_unit_custom_tree "Custom Org Unit Trees">
 <!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">
 
diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu.js b/Open-ILS/xul/staff_client/chrome/content/main/menu.js
index 4ca96d8..113ea93 100644
--- a/Open-ILS/xul/staff_client/chrome/content/main/menu.js
+++ b/Open-ILS/xul/staff_client/chrome/content/main/menu.js
@@ -750,6 +750,10 @@ main.menu.prototype = {
                 ['oncommand'],
                 function(event) { open_eg_web_page('conify/global/config/usr_activity_type', null, event); }
             ],
+            'cmd_server_admin_actor_org_unit_custom_tree' : [
+                ['oncommand'],
+                function(event) { open_eg_web_page('conify/global/actor/org_unit_custom_tree', null, event); }
+            ],
             'cmd_local_admin_external_text_editor' : [
                 ['oncommand'],
                 function() {
diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
index bd98d4b..5316bbf 100644
--- a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
+++ b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
@@ -251,6 +251,9 @@
     <command id="cmd_server_admin_config_usr_activity_type"
              perm="ADMIN_USER_ACTIVITY_TYPE VIEW_USER_ACTIVITY_TYPE"
              />
+    <command id="cmd_server_admin_actor_org_unit_custom_tree"
+             perm="ADMIN_ORG_UNIT_CUSTOM_TREE VIEW_ORG_UNIT_CUSTOM_TREE"
+             />
 
     <command id="cmd_hotkeys_toggle" />
     <command id="cmd_hotkeys_set" />
@@ -550,6 +553,7 @@
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.config_actor_sip_fields;" command="cmd_server_admin_config_actor_sip_fields"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.config_asset_sip_fields;" command="cmd_server_admin_config_asset_sip_fields"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.config_usr_activity_type;" command="cmd_server_admin_config_usr_activity_type"/>
+                <menuitem label="&staff.main.menu.admin.server_admin.conify.actor.org_unit_custom_tree;" command="cmd_server_admin_actor_org_unit_custom_tree"/>
                 <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" />

commit 0ea00f3eef4aa1da707563784386fb73a170e1ce
Author: Bill Erickson <berick at esilibrary.com>
Date:   Mon Mar 19 16:31:08 2012 -0400

    Custom Org Tree : tpac
    
    If configured and active, the tpac will now use the custom 'opac' tree
    for the org unit selectors.  This will be true in both the public
    catalog and the staff client catalog.  The staff client catalog will
    continue to display hidden org units.
    
    If the opac.org_unit.non_inheritied_visibility global flag is set, the
    public catalog will display child nodes of hidden org units in the
    custom tree.
    
    Signed-off-by: Bill Erickson <berick at esilibrary.com>
    Signed-off-by: Mike Rylander <mrylander at gmail.com>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm
index f46587f..3fd9983 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm
@@ -122,6 +122,47 @@ sub init_ro_object_cache {
         return [ values %{$cache{map}{aou}} ];
     };
 
+    $ro_object_subs->{aouct_tree} = sub {
+
+        # fetch the org unit tree
+        unless(exists $cache{aouct_tree}) {
+            $cache{aouct_tree} = undef;
+
+            my $tree_id = $e->search_actor_org_unit_custom_tree(
+                {purpose => 'opac', active => 't'},
+                {idlist => 1}
+            )->[0];
+
+            if ($tree_id) {
+                my $node_tree = $e->search_actor_org_unit_custom_tree_node([
+                {parent_node => undef, tree => $tree_id},
+                {   flesh        => -1,
+                    flesh_fields => {aouctn => ['children', 'org_unit']},
+                    order_by     => {aouctn => 'sibling_order'}
+                }
+                ])->[0];
+
+                # tree-ify the org units.  note that since the orgs are fleshed
+                # upon retrieval, this org tree will not clobber ctx->{aou_tree}.
+                my @nodes = ($node_tree);
+                while (my $node = shift(@nodes)) {
+                    my $aou = $node->org_unit;
+                    $aou->children([]);
+                    for my $cnode (@{$node->children}) {
+                        my $child_org = $cnode->org_unit;
+                        $child_org->parent_ou($aou->id);
+                        $child_org->ou_type( $ro_object_subs->{get_aout}->($child_org->ou_type) );
+                        push(@{$aou->children}, $child_org);
+                        push(@nodes, $cnode);
+                    }
+                }
+
+                $cache{aouct_tree} = $node_tree->org_unit;
+            }
+        }
+
+        return $cache{aouct_tree};
+    };
 
     # turns an ISO date into something TT can understand
     $ro_object_subs->{parse_datetime} = sub {
diff --git a/Open-ILS/src/templates/opac/parts/org_selector.tt2 b/Open-ILS/src/templates/opac/parts/org_selector.tt2
index 49775c4..3e32040 100644
--- a/Open-ILS/src/templates/opac/parts/org_selector.tt2
+++ b/Open-ILS/src/templates/opac/parts/org_selector.tt2
@@ -7,7 +7,7 @@
 # Use of PROCESS results in internal variables, such as value or org_unit, to "leak" out
 
 BLOCK build_org_selector;
-    node_stack = [{org => org_unit || ctx.aou_tree}];
+    node_stack = [{org => org_unit || ctx.aouct_tree || ctx.aou_tree}];
     inherited_vis = ctx.get_cgf('opac.org_unit.non_inheritied_visibility').enabled == 'f';
 
     IF !name; 
@@ -79,26 +79,22 @@ BLOCK build_org_selector;
                 disabled = 'disabled="disabled"';
             ELSIF node_value == value;
                 selected = 'selected="selected"';
-            END;
+            END; 
 
-            pad_depth = org_unit.ou_type.depth;
+            pad_depth = 0;
 
-            # copy loc groups appear as children of the owner (current) org
+            # copy loc groups appear as children of the owning org unit
             SET pad_depth = pad_depth + 1 IF loc_grp;
 
-            # for each parent org unit that is hidden, decrease the pad depth by one.  
-            IF !ctx.is_staff; 
-                porg = ctx.get_aou(org_unit.parent_ou);
-                WHILE porg;
-                    SET pad_depth = pad_depth - 1 IF porg.opac_visible == 'f';
-                    porg = ctx.get_aou(porg.parent_ou);
-                END;
+            # determine the depth by calculating the distance from the root
+            porg = ctx.get_aou(org_unit.parent_ou);
+            WHILE porg;
+                SET pad_depth = pad_depth + 1 IF porg.opac_visible == 't' OR ctx.is_staff;
+                porg = ctx.get_aou(porg.parent_ou);
             END;
 
             pad_depth = pad_depth * 2;
-            display_name = loc_grp ? loc_grp.name : org_unit.name;
-            
-            %] 
+            display_name = loc_grp ? loc_grp.name : org_unit.name %]
 
             <option value='[% node_value %]' [% selected %] [% disabled %] [% css_class %]> 
                 [% '&nbsp;' FOR [0..pad_depth]; display_name | html %]

commit 43999e0c9c6c6334e6c5e7b70a9c4c43327a5844
Author: Bill Erickson <berick at esilibrary.com>
Date:   Mon Mar 19 16:30:52 2012 -0400

    Custom Org Tree : DB and IDL
    
    Support for building custom org unit hierarchies for display purposes.
    Initially, this is meant to support custom OPAC trees, but the design
    leaves from for other types of trees.
    
    The only restrictions to custom org trees is that no org unit appear
    more once and that they in fact be tree-shaped.  Otherwise, any node
    can be the parent/child of any other node, regardless of ou_type, etc.
    
    Signed-off-by: Bill Erickson <berick at esilibrary.com>
    Signed-off-by: Mike Rylander <mrylander at gmail.com>

diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index c9357ba..10b5837 100644
--- a/Open-ILS/examples/fm_IDL.xml
+++ b/Open-ILS/examples/fm_IDL.xml
@@ -4984,6 +4984,45 @@ SELECT  usr,
             </actions>
         </permacrud>
 	</class>
+	<class id="aouct" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="actor::org_unit_custom_tree" oils_persist:tablename="actor.org_unit_custom_tree" reporter:label="Org Unit Custom Tree">
+		<fields oils_persist:primary="id" oils_persist:sequence="actor.org_unit_custom_tree_id_seq">
+			<field reporter:label="ID" name="id" reporter:datatype="id"/>
+			<field reporter:label="Active" name="active" reporter:datatype="bool"/>
+			<field reporter:label="Purpose" name="purpose" reporter:datatype="text"/>
+		</fields>
+		<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+			<actions>
+				<create permission="ADMIN_ORG_UNIT_CUSTOM_TREE" global_required="true" />
+				<retrieve/>
+				<update permission="ADMIN_ORG_UNIT_CUSTOM_TREE" global_required="true" />
+				<delete permission="ADMIN_ORG_UNIT_CUSTOM_TREE" global_required="true" />
+			</actions>
+		</permacrud>
+	</class>
+	<class id="aouctn" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="actor::org_unit_custom_tree_node" oils_persist:tablename="actor.org_unit_custom_tree_node" reporter:label="Org Unit Custom Tree Node">
+		<fields oils_persist:primary="id" oils_persist:sequence="actor.org_unit_custom_tree_node_id_seq">
+			<field reporter:label="ID" name="id" reporter:datatype="id"/>
+			<field reporter:label="Tree" name="tree" reporter:datatype="link"/>
+			<field reporter:label="Org Unit" name="org_unit" reporter:datatype="link"/>
+			<field reporter:label="Parent" name="parent_node" reporter:datatype="link"/>
+			<field reporter:label="Sibling Sort Order" name="sibling_order" reporter:datatype="int"/>
+			<field reporter:label="Children" name="children" reporter:datatype="link" oils_persist:virtual="true" />
+		</fields>
+		<links>
+			<link field="tree" reltype="has_a" key="id" map="" class="aouct"/>
+			<link field="org_unit" reltype="has_a" key="id" map="" class="aou"/>
+			<link field="parent_node" reltype="has_a" key="id" map="" class="aouctn"/>
+			<link field="children" reltype="has_many" key="parent_node" map="" class="aouctn"/>
+		</links>
+		<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+			<actions>
+				<create permission="ADMIN_ORG_UNIT_CUSTOM_TREE" global_required="true" />
+				<retrieve/>
+				<update permission="ADMIN_ORG_UNIT_CUSTOM_TREE" global_required="true" />
+				<delete permission="ADMIN_ORG_UNIT_CUSTOM_TREE" global_required="true" />
+			</actions>
+		</permacrud>
+	</class>
 	<class id="ccnb" controller="open-ils.cstore" oils_obj:fieldmapper="container::call_number_bucket" oils_persist:tablename="container.call_number_bucket" reporter:label="Call Number Bucket">
 		<fields oils_persist:primary="id" oils_persist:sequence="container.call_number_bucket_id_seq">
 			<field name="items" oils_persist:virtual="true" reporter:datatype="link"/>
diff --git a/Open-ILS/src/sql/Pg/005.schema.actors.sql b/Open-ILS/src/sql/Pg/005.schema.actors.sql
index 4f9f39b..21363b9 100644
--- a/Open-ILS/src/sql/Pg/005.schema.actors.sql
+++ b/Open-ILS/src/sql/Pg/005.schema.actors.sql
@@ -665,4 +665,20 @@ CREATE UNIQUE INDEX label_once_per_ws ON actor.toolbar (ws, label) WHERE ws IS N
 CREATE UNIQUE INDEX label_once_per_org ON actor.toolbar (org, label) WHERE org IS NOT NULL;
 CREATE UNIQUE INDEX label_once_per_usr ON actor.toolbar (usr, label) WHERE usr IS NOT NULL;
 
+CREATE TYPE actor.org_unit_custom_tree_purpose AS ENUM ('opac');
+CREATE TABLE actor.org_unit_custom_tree (
+    id              SERIAL  PRIMARY KEY,
+    active          BOOLEAN DEFAULT FALSE,
+    purpose         actor.org_unit_custom_tree_purpose NOT NULL DEFAULT 'opac' UNIQUE
+);
+
+CREATE TABLE actor.org_unit_custom_tree_node (
+    id              SERIAL  PRIMARY KEY,
+    tree            INTEGER REFERENCES actor.org_unit_custom_tree (id) DEFERRABLE INITIALLY DEFERRED,
+	org_unit        INTEGER NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
+	parent_node     INTEGER REFERENCES actor.org_unit_custom_tree_node (id) DEFERRABLE INITIALLY DEFERRED,
+    sibling_order   INTEGER NOT NULL DEFAULT 0,
+    CONSTRAINT aouctn_once_per_org UNIQUE (tree, org_unit)
+);
+
 COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.org_unit_opac_vis_and_sorting.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.org_unit_opac_vis_and_sorting.sql
index 115613a..fd1c002 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.org_unit_opac_vis_and_sorting.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.org_unit_opac_vis_and_sorting.sql
@@ -14,11 +14,32 @@ INSERT INTO config.global_flag (name, enabled, label)
         )
     );
 
+CREATE TYPE actor.org_unit_custom_tree_purpose AS ENUM ('opac');
+
+CREATE TABLE actor.org_unit_custom_tree (
+    id              SERIAL  PRIMARY KEY,
+    active          BOOLEAN DEFAULT FALSE,
+    purpose         actor.org_unit_custom_tree_purpose NOT NULL DEFAULT 'opac' UNIQUE
+);
+
+CREATE TABLE actor.org_unit_custom_tree_node (
+    id              SERIAL  PRIMARY KEY,
+    tree            INTEGER REFERENCES actor.org_unit_custom_tree (id) DEFERRABLE INITIALLY DEFERRED,
+	org_unit        INTEGER NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
+	parent_node     INTEGER REFERENCES actor.org_unit_custom_tree_node (id) DEFERRABLE INITIALLY DEFERRED,
+    sibling_order   INTEGER NOT NULL DEFAULT 0,
+    CONSTRAINT aouctn_once_per_org UNIQUE (tree, org_unit)
+);
+    
+
 COMMIT;
 
 /* UNDO
 BEGIN;
 DELETE FROM config.global_flag WHERE name = 'opac.org_unit.non_inheritied_visibility';
+DROP TABLE actor.org_unit_custom_tree_node;
+DROP TABLE actor.org_unit_custom_tree;
+DROP TYPE actor.org_unit_custom_tree_purpose;
 COMMIT;
 */
 

commit 3f4ca6dd32b5ce2e36c91af836ee33cd5546fa1b
Author: Bill Erickson <berick at esilibrary.com>
Date:   Tue Mar 13 12:15:33 2012 -0400

    Repaired non-slim org unit fetching bug
    
    Fixed an old thinko.  When fetching a full org unit object, set the
    children org unit array value instead of clobbering the function.
    
    Signed-off-by: Bill Erickson <berick at esilibrary.com>
    Signed-off-by: Mike Rylander <mrylander at gmail.com>

diff --git a/Open-ILS/web/js/dojo/fieldmapper/OrgUtils.js b/Open-ILS/web/js/dojo/fieldmapper/OrgUtils.js
index 8a1000c..87582d3 100644
--- a/Open-ILS/web/js/dojo/fieldmapper/OrgUtils.js
+++ b/Open-ILS/web/js/dojo/fieldmapper/OrgUtils.js
@@ -56,7 +56,7 @@ if(!dojo._hasResource["fieldmapper.OrgUtils"]){
         if(!(o && o.id)) {
             throw new Error("fieldmapper.aou.LoadOrg(): No org unit found with ID " + id);
         }
-        o.children = fieldmapper.aou.OrgCache[o.id()].children;
+        o.children(slim_o.org.children());
         fieldmapper.aou.OrgCache[o.id()] = { loaded : true, org : o };
         return o;
     };

commit d047646aff7f5aca23ac2a86422e3c869ea3d405
Author: Bill Erickson <berick at esilibrary.com>
Date:   Mon Mar 12 12:22:22 2012 -0400

    TPac: non-inherited org unit visibility
    
    Adds support for displaying org units that are children of hidden org
    units in the tpac org unit selector.  A new global flag was added to
    control this behavior called "opac.org_unit.non_inheritied_visibility" /
    "Org Units Do Not Inherit Visibility".
    
    To avoid confusion / distorted org unit trees, children of hidden org
    units are left-padded one less for each hidden parent org unit.  For
    example, in the stock org tree, if Sys2 is opac_visible=false (and the
    global flag is enabled), the tree in the tpac would appear like so:
    
    Cons
    - Sys 1
    -- BR1
    --- SL1
    -- BR2
    - BR3
    - BR4
    
    Similarly, if CONS was also hidden, the whole tree would be shifted left
    by 1 pad depth.
    
    Signed-off-by: Bill Erickson <berick at esilibrary.com>
    Signed-off-by: Mike Rylander <mrylander at gmail.com>

diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
index 6c97858..2d3c8ec 100644
--- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql
+++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
@@ -11541,3 +11541,15 @@ INSERT INTO action_trigger.environment (event_def, path)
 INSERT INTO actor.toolbar(org,label,layout) VALUES
     ( 1, 'circ', '["circ_checkout","circ_checkin","toolbarseparator.1","search_opac","copy_status","toolbarseparator.2","patron_search","patron_register","toolbarspacer.3","hotkeys_toggle"]' ),
     ( 1, 'cat', '["circ_checkin","toolbarseparator.1","search_opac","copy_status","toolbarseparator.2","create_marc","authority_manage","retrieve_last_record","toolbarspacer.3","hotkeys_toggle"]' );
+
+INSERT INTO config.global_flag (name, enabled, label) 
+    VALUES (
+        'opac.org_unit.non_inheritied_visibility',
+        FALSE,
+        oils_i18n_gettext(
+            'opac.org_unit.non_inheritied_visibility',
+            'Org Units Do Not Inherit Visibility',
+            'cgf',
+            'label'
+        )
+    );
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.org_unit_opac_vis_and_sorting.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.org_unit_opac_vis_and_sorting.sql
new file mode 100644
index 0000000..115613a
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.org_unit_opac_vis_and_sorting.sql
@@ -0,0 +1,24 @@
+BEGIN;
+
+--SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+INSERT INTO config.global_flag (name, enabled, label) 
+    VALUES (
+        'opac.org_unit.non_inheritied_visibility',
+        FALSE,
+        oils_i18n_gettext(
+            'opac.org_unit.non_inheritied_visibility',
+            'Org Units Do Not Inherit Visibility',
+            'cgf',
+            'label'
+        )
+    );
+
+COMMIT;
+
+/* UNDO
+BEGIN;
+DELETE FROM config.global_flag WHERE name = 'opac.org_unit.non_inheritied_visibility';
+COMMIT;
+*/
+
diff --git a/Open-ILS/src/templates/opac/parts/org_selector.tt2 b/Open-ILS/src/templates/opac/parts/org_selector.tt2
index fd9c8c0..49775c4 100644
--- a/Open-ILS/src/templates/opac/parts/org_selector.tt2
+++ b/Open-ILS/src/templates/opac/parts/org_selector.tt2
@@ -8,6 +8,8 @@
 
 BLOCK build_org_selector;
     node_stack = [{org => org_unit || ctx.aou_tree}];
+    inherited_vis = ctx.get_cgf('opac.org_unit.non_inheritied_visibility').enabled == 'f';
+
     IF !name; 
         name = 'loc';
         IF show_loc_groups; name = 'locg'; END;
@@ -30,8 +32,10 @@ BLOCK build_org_selector;
             css_class = '';
             disabled = '';
             selected = '';
+            visible = org_unit.opac_visible == 't';
 
-            NEXT UNLESS ctx.is_staff || org_unit.opac_visible == 't';
+            # org and all children are invisible.
+            NEXT IF !visible AND inherited_vis AND !ctx.is_staff;
 
             IF !loc_grp; # processing an org unit
 
@@ -57,11 +61,15 @@ BLOCK build_org_selector;
                 FOR grp IN top_loc_groups;
                     node_stack.push({org => org_unit, loc_grp => grp});
                 END;
+
             END;
 
+            # This org unit is not publicly visible (though its children may be).
+            NEXT UNLESS ctx.is_staff OR visible;
+
             node_value = ou_id;
-            IF loc_grp; node_value = node_value _ ':' _ loc_grp.id; END;
             IF loc_grp;
+                node_value = node_value _ ':' _ loc_grp.id; 
                 css_class = 'class="loc_grp"';
             ELSE;
                 css_class = 'class="org_unit"';
@@ -71,20 +79,31 @@ BLOCK build_org_selector;
                 disabled = 'disabled="disabled"';
             ELSIF node_value == value;
                 selected = 'selected="selected"';
-            END %] 
+            END;
+
+            pad_depth = org_unit.ou_type.depth;
+
+            # copy loc groups appear as children of the owner (current) org
+            SET pad_depth = pad_depth + 1 IF loc_grp;
+
+            # for each parent org unit that is hidden, decrease the pad depth by one.  
+            IF !ctx.is_staff; 
+                porg = ctx.get_aou(org_unit.parent_ou);
+                WHILE porg;
+                    SET pad_depth = pad_depth - 1 IF porg.opac_visible == 'f';
+                    porg = ctx.get_aou(porg.parent_ou);
+                END;
+            END;
+
+            pad_depth = pad_depth * 2;
+            display_name = loc_grp ? loc_grp.name : org_unit.name;
+            
+            %] 
 
             <option value='[% node_value %]' [% selected %] [% disabled %] [% css_class %]> 
-            [%
-                # loc_grp's are displayed as children of the current org
-                depth = org_unit.ou_type.depth;
-                IF loc_grp; depth = depth + 1; END;
-                pad = depth * 2;
-                FOR idx IN [0..pad]; '&nbsp;'; END;
-                loc_grp ? loc_grp.name : org_unit.name | html ;
-            %]
+                [% '&nbsp;' FOR [0..pad_depth]; display_name | html %]
             </option> 
-            [%
-        END;
-    %]
+
+        [% END %]
     </select>
 [% END %]

-----------------------------------------------------------------------

Summary of changes:
 Open-ILS/examples/fm_IDL.xml                       |   39 +++
 .../perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm   |   41 +++
 Open-ILS/src/sql/Pg/002.schema.config.sql          |    2 +-
 Open-ILS/src/sql/Pg/005.schema.actors.sql          |   16 +
 Open-ILS/src/sql/Pg/950.data.seed-values.sql       |   12 +
 .../0702.schema.org_unit_opac_vis_and_sorting.sql  |   45 +++
 .../conify/global/actor/org_unit_custom_tree.tt2   |   73 +++++
 Open-ILS/src/templates/opac/parts/org_selector.tt2 |   45 ++-
 .../templates/opac/parts/record/copy_counts.tt2    |    7 +-
 .../templates/opac/parts/result/copy_counts.tt2    |    2 +
 Open-ILS/web/js/dojo/fieldmapper/OrgUtils.js       |    2 +-
 .../conify/global/actor/org_unit_custom_tree.js    |  298 ++++++++++++++++++++
 Open-ILS/web/opac/locale/en-US/lang.dtd            |    1 +
 .../xul/staff_client/chrome/content/main/menu.js   |    4 +
 .../chrome/content/main/menu_frame_menus.xul       |    4 +
 15 files changed, 573 insertions(+), 18 deletions(-)
 create mode 100644 Open-ILS/src/sql/Pg/upgrade/0702.schema.org_unit_opac_vis_and_sorting.sql
 create mode 100644 Open-ILS/src/templates/conify/global/actor/org_unit_custom_tree.tt2
 create mode 100644 Open-ILS/web/js/ui/default/conify/global/actor/org_unit_custom_tree.js


hooks/post-receive
-- 
Evergreen ILS


More information about the open-ils-commits mailing list