diff --git a/include/class.dept.php b/include/class.dept.php
index e1ee486a3e1c095589e5c40043d0dd2ce5541feb..8abf4919468b4c9a9df8ec3f3dc84e37187f9c6c 100644
--- a/include/class.dept.php
+++ b/include/class.dept.php
@@ -20,6 +20,10 @@ class Dept extends VerySimpleModel {
         'table' => DEPT_TABLE,
         'pk' => array('id'),
         'joins' => array(
+            'parent' => array(
+                'constraint' => array('pid' => 'Dept.id'),
+                'null' => true,
+            ),
             'email' => array(
                 'constraint' => array('email_id' => 'EmailModel.email_id'),
                 'null' => true,
@@ -90,6 +94,10 @@ class Dept extends VerySimpleModel {
         return _H(sprintf('dept.%s.%s', $subtag, $this->getId()));
     }
 
+    function getFullName() {
+        return self::getNameById($this->getId());
+    }
+
     function getEmailId() {
         return $this->email_id;
     }
@@ -343,6 +351,34 @@ class Dept extends VerySimpleModel {
         return $this->getName();
     }
 
+    function getParent() {
+        return static::lookup($this->ht['pid']);
+    }
+
+    /**
+     * getFullPath
+     *
+     * Utility function to retrieve a '/' separated list of department IDs
+     * in the ancestry of this department. This is used to populate the
+     * `path` field in the database and is used for access control rather
+     * than the ID field since nesting of departments is necessary and
+     * department access can be cascaded.
+     *
+     * Returns:
+     * Slash-separated string of ID ancestry of this department. The string
+     * always starts and ends with a slash, and will always contain the ID
+     * of this department last.
+     */
+    function getFullPath() {
+        $path = '';
+        if ($p = $this->getParent())
+            $path .= $p->getFullPath();
+        else
+            $path .= '/';
+        $path .= $this->getId() . '/';
+        return $path;
+    }
+
     /*----Static functions-------*/
 	static function getIdByName($name) {
         $row = static::objects()
@@ -354,11 +390,8 @@ class Dept extends VerySimpleModel {
     }
 
     function getNameById($id) {
-
-        if($id && ($dept=static::lookup($id)))
-            $name= $dept->getName();
-
-        return $name;
+        $names = static::getDepartments();
+        return $names[$id];
     }
 
     function getDefaultDeptName() {
@@ -371,10 +404,11 @@ class Dept extends VerySimpleModel {
             : null;
     }
 
-    static function getDepartments( $criteria=null) {
+    static function getDepartments( $criteria=null, $localize=true) {
         static $depts = null;
 
         if (!isset($depts) || $criteria) {
+            // XXX: This will upset the static $depts array
             $depts = array();
             $query = self::objects();
             if (isset($criteria['publiconly']))
@@ -394,17 +428,39 @@ class Dept extends VerySimpleModel {
             }
 
             $query->order_by('name')
-                ->values_flat('id', 'name');
+                ->values('id', 'pid', 'name', 'parent');
 
-            $names = array();
             foreach ($query as $row)
-                $names[$row[0]] = $row[1];
+                $depts[$row['id']] = $row;
+
+            $localize_this = function($id, $default) use ($localize) {
+                if (!$localize)
+                    return $default;
+
+                $tag = _H("dept.name.{$id}");
+                $T = CustomDataTranslation::translate($tag);
+                return $T != $tag ? $T : $default;
+            };
 
-            // Fetch local names
-            foreach (CustomDataTranslation::getDepartmentNames(array_keys($names)) as $id=>$name) {
-                // Translate the department
-                $names[$id] = $name;
+            // Resolve parent names
+            $names = array();
+            foreach ($depts as $id=>$info) {
+                $name = $info['name'];
+                $loop = array($id=>true);
+                $parent = false;
+                while ($info['pid'] && ($info = $depts[$info['pid']])) {
+                    $name = sprintf('%s / %s', $info['name'], $name);
+                    if (isset($loop[$info['pid']]))
+                        break;
+                    $loop[$info['pid']] = true;
+                    $parent = $info;
+                }
+                // Fetch local names
+                $names[$id] = $localize_this($id, $name);
             }
+            asort($names);
+
+            // TODO: Use locale-aware sorting mechanism
 
             if ($criteria)
                 return $names;
@@ -460,9 +516,13 @@ class Dept extends VerySimpleModel {
         if (!$vars['ispublic'] && $cfg && ($vars['id']==$cfg->getDefaultDeptId()))
             $errors['ispublic']=__('System default department cannot be private');
 
+        if ($vars['pid'] && !($p = static::lookup($vars['pid'])))
+            $errors['pid'] = __('Department selection is required');
+
         if ($errors)
             return false;
 
+        $this->pid = $vars['pid'];
         $this->updated = SqlFunction::NOW();
         $this->ispublic = isset($vars['ispublic'])?$vars['ispublic']:0;
         $this->email_id = isset($vars['email_id'])?$vars['email_id']:0;
diff --git a/include/class.orm.php b/include/class.orm.php
index 37178b276c6513cedd70458ca340f7020f19adc1..6ed7d52f164bd5f2687207ab78a1bb425759a3db 100644
--- a/include/class.orm.php
+++ b/include/class.orm.php
@@ -1243,17 +1243,8 @@ class InstrumentedList extends ModelInstanceManager {
     }
 
     // QuerySet overriedes
-    function filter() {
-        return call_user_func_array(array($this->objects(), 'filter'), func_get_args());
-    }
-    function exclude() {
-        return call_user_func_array(array($this->objects(), 'exclude'), func_get_args());
-    }
-    function order_by() {
-        return call_user_func_array(array($this->objects(), 'order_by'), func_get_args());
-    }
-    function limit($how) {
-        return $this->objects()->limit($how);
+    function __call($what, $how) {
+        return call_user_func_array(array($this->objects(), $what), $how);
     }
 }
 
diff --git a/include/class.staff.php b/include/class.staff.php
index 048db0984691bb4837a9bbf9f3480757a1cb91f1..9a8ac128b05ca7f6842c0ea361c2bb3f071608c8 100644
--- a/include/class.staff.php
+++ b/include/class.staff.php
@@ -208,28 +208,50 @@ implements AuthenticatedUser {
     }
 
     function getDepartments() {
+        // TODO: Cache this in the agent's session as it is unlikely to
+        //       change while logged in
 
         if (!isset($this->departments)) {
 
             // Departments the staff is "allowed" to access...
             // based on the group they belong to + user's primary dept + user's managed depts.
-            $dept_ids = array();
-            $depts = Dept::objects()
-                ->filter(Q::any(array(
-                    'id' => $this->dept_id,
-                    'groups__group_id' => $this->group_id,
-                    'manager_id' => $this->getId(),
-                )))
+            $sql='SELECT DISTINCT d.id FROM '.STAFF_TABLE.' s '
+                .' LEFT JOIN '.GROUP_DEPT_TABLE.' g ON (s.group_id=g.group_id) '
+                .' INNER JOIN '.DEPT_TABLE.' d ON (LOCATE(CONCAT("/", s.dept_id, "/"), d.path) OR d.manager_id=s.staff_id OR LOCATE(CONCAT("/", g.dept_id, "/"), d.path)) '
+                .' WHERE s.staff_id='.db_input($this->getId());
+            $depts = array();
+            if (($res=db_query($sql)) && db_num_rows($res)) {
+                while(list($id)=db_fetch_row($res))
+                    $depts[] = $id;
+            }
+
+            /* ORM method — about 2.0ms slower
+            $q = Q::any(array(
+                'path__contains' => '/'.$this->dept_id.'/',
+                'manager_id' => $this->getId(),
+            ));
+            // Add in group access
+            foreach ($this->group->depts->values_flat('dept_id') as $row) {
+                // Skip primary dept
+                if ($row[0] == $this->dept_id)
+                    continue;
+                $q->add(new Q(array('path__contains'=>'/'.$row[0].'/')));
+            }
+
+            $dept_ids = Dept::objects()
+                ->filter($q)
+                ->distinct('id')
                 ->values_flat('id');
 
-            foreach ($depts as $row)
-                $dept_ids[] = $row[0];
+            foreach ($dept_ids as $row)
+                $depts[] = $row[0];
+            */
 
-            if (!$dept_ids) { //Neptune help us! (fallback)
-                $dept_ids = array_merge($this->getGroup()->getDepartments(), array($this->getDeptId()));
+            if (!$depts) { //Neptune help us! (fallback)
+                $depts = array_merge($this->getGroup()->getDepartments(), array($this->getDeptId()));
             }
 
-            $this->departments = array_filter(array_unique($dept_ids));
+            $this->departments = $depts;
         }
 
         return $this->departments;
diff --git a/include/class.ticket.php b/include/class.ticket.php
index eb46e86c4278659806e2f54349063885b08c7964..44d3d7a7dac4a5722073fe1ab521bb42333e6d70 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -487,7 +487,7 @@ class Ticket {
     function getDeptName() {
 
         if(!$this->ht['dept_name'] && ($dept = $this->getDept()))
-            $this->ht['dept_name'] = $dept->getName();
+            $this->ht['dept_name'] = $dept->getFullName();
 
        return $this->ht['dept_name'];
     }
diff --git a/include/class.translation.php b/include/class.translation.php
index 141e13051c98dd709bce0a4bcc705a1aaba7751e..1c09dbb6aa29a31b63df9ed210a9dd582aa1534e 100644
--- a/include/class.translation.php
+++ b/include/class.translation.php
@@ -1032,29 +1032,6 @@ class CustomDataTranslation extends VerySimpleModel {
 
         return static::objects()->filter($criteria)->all();
     }
-
-    static function getDepartmentNames($ids) {
-        global $cfg;
-
-        $tags = array();
-        $names = array();
-        foreach ($ids as $i)
-            $tags[_H('dept.name.'.$i)] = $i;
-
-        if (($lang = Internationalization::getCurrentLanguage())
-            && $lang != $cfg->getPrimaryLanguage()
-        ) {
-            foreach (CustomDataTranslation::objects()->filter(array(
-                'object_hash__in'=>array_keys($tags),
-                'lang'=>$lang
-                )) as $translation
-            ) {
-                $names[$tags[$translation->object_hash]] = $translation->text;
-            }
-        }
-        return $names;
-    }
-
 }
 
 class CustomTextDomain {
diff --git a/include/staff/department.inc.php b/include/staff/department.inc.php
index 6828b06925d9bdee216ed73825707b5e8eafeae7..9b6d9d4636ef0740e6f13d5464402d6152b06fae 100644
--- a/include/staff/department.inc.php
+++ b/include/staff/department.inc.php
@@ -51,6 +51,23 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info);
         </tr>
     </thead>
     <tbody>
+        <tr>
+            <td width="180">
+                <?php echo __('Parent');?>:
+            </td>
+            <td>
+                <select name="pid">
+                    <option value="">&mdash; <?php echo __('Top-Level Deptartment'); ?> &mdash;</option>
+<?php foreach (Dept::getDepartments() as $id=>$name) {
+    if ($info['id'] && $id == $info['id'])
+        continue; ?>
+                    <option value="<?php echo $id; ?>" <?php
+                    if ($info['pid'] == $id) echo 'selected="selected"';
+                    ?>><?php echo $name; ?></option>
+<?php } ?>
+                </select>
+            </td>
+        </tr>
         <tr>
             <td width="180" class="required">
                 <?php echo __('Name');?>:
diff --git a/include/staff/departments.inc.php b/include/staff/departments.inc.php
index 20018b8dabde7baa4df3475a0951cd8681dca5f6..4b58fea69028f9636061a7cde8a041442cd24f35 100644
--- a/include/staff/departments.inc.php
+++ b/include/staff/departments.inc.php
@@ -100,7 +100,7 @@ $qstr.='&order='.($order=='DESC'?'ASC':'DESC');
                   <?php echo $default? 'disabled="disabled"' : ''; ?> >
                 </td>
                 <td><a href="departments.php?id=<?php echo $id; ?>"><?php
-                echo $dept->getName(); ?></a>&nbsp;<?php echo $default; ?></td>
+                echo Dept::getNameById($id); ?></a>&nbsp;<?php echo $default; ?></td>
                 <td><?php echo $dept->isPublic() ? __('Public') :'<b>'.__('Private').'</b>'; ?></td>
                 <td>&nbsp;&nbsp;
                     <b>
diff --git a/include/staff/helptopic.inc.php b/include/staff/helptopic.inc.php
index 21dd951f3fa1d6f1ab48343775ab53f79b1c688c..ee8616919d13d61a0b3f38d6db876f93011115f0 100644
--- a/include/staff/helptopic.inc.php
+++ b/include/staff/helptopic.inc.php
@@ -118,12 +118,9 @@ if ($info['form_id'] == Topic::FORM_USE_PARENT) echo 'selected="selected"';
                 <select name="dept_id">
                     <option value="0">&mdash; <?php echo __('System Default'); ?> &mdash;</option>
                     <?php
-                    if (($depts=Dept::getDepartments())) {
-                        foreach ($depts as $id => $name) {
-                            $selected=($info['dept_id'] && $id==$info['dept_id'])?'selected="selected"':'';
-                            echo sprintf('<option value="%d" %s>%s</option>',
-                                    $id, $selected, $name);
-                        }
+                    foreach (Dept::getDepartments() as $id=>$name) {
+                        $selected=($info['dept_id'] && $id==$info['dept_id'])?'selected="selected"':'';
+                        echo sprintf('<option value="%d" %s>%s</option>',$id,$selected,$name);
                     }
                     ?>
                 </select>
diff --git a/include/staff/templates/sequence-manage.tmpl.php b/include/staff/templates/sequence-manage.tmpl.php
index 6827cd6267d5d5f0c8cbae54a0af44cf162c91be..10ac7391ba63dba74a9f07ccb21fbe15e2dfba58 100644
--- a/include/staff/templates/sequence-manage.tmpl.php
+++ b/include/staff/templates/sequence-manage.tmpl.php
@@ -20,7 +20,7 @@ foreach ($sequences as $e) {
         <i class="icon-sort-by-order"></i>
         <div style="display:inline-block" class="name"> <?php echo $e->getName(); ?> </div>
         <div class="manage-buttons pull-right">
-            <span class="faded">next</span>
+            <span class="faded"><?php echo __('next'); ?></span>
             <span class="current"><?php echo $e->current(); ?></span>
         </div>
         <div class="button-group">
diff --git a/include/upgrader/streams/core.sig b/include/upgrader/streams/core.sig
index 081d688556ac2572177f83cc5301da6512f45267..2888135a10a7347cf981c5bc3bd82404849bde5d 100644
--- a/include/upgrader/streams/core.sig
+++ b/include/upgrader/streams/core.sig
@@ -1 +1 @@
-e675900fdac39ec981a8d65fc82907b7
+e7a338f95ed622678123386a8a59dfc0
diff --git a/include/upgrader/streams/core/36f6b328-e675900f.cleanup.sql b/include/upgrader/streams/core/36f6b328-e7a338f9.cleanup.sql
similarity index 100%
rename from include/upgrader/streams/core/36f6b328-e675900f.cleanup.sql
rename to include/upgrader/streams/core/36f6b328-e7a338f9.cleanup.sql
diff --git a/include/upgrader/streams/core/36f6b328-e675900f.patch.sql b/include/upgrader/streams/core/36f6b328-e7a338f9.patch.sql
similarity index 92%
rename from include/upgrader/streams/core/36f6b328-e675900f.patch.sql
rename to include/upgrader/streams/core/36f6b328-e7a338f9.patch.sql
index 651b4cd407e8bc7941e6e00be2567bb769befcd7..0150b4fd1b4ad4e7a7106e56c32f0140406e6ca4 100644
--- a/include/upgrader/streams/core/36f6b328-e675900f.patch.sql
+++ b/include/upgrader/streams/core/36f6b328-e7a338f9.patch.sql
@@ -1,6 +1,6 @@
 /**
  * @version v1.9.6
- * @signature e675900fdac39ec981a8d65fc82907b7
+ * @signature e7a338f95ed622678123386a8a59dfc0
  * @title Add support for ticket tasks
  *
  * This patch adds ability to thread anything and introduces tasks
@@ -110,7 +110,14 @@ UPDATE `%TABLE_PREFIX%config`
     SET `key` = 'ticket_sequence_id'
     WHERE `key` = 'sequence_id'  AND `namespace` = 'core';
 
+ALTER TABLE `%TABLE_PREFIX%department`
+  ADD `pid` int(11) unsigned default NULL AFTER `id`,
+  ADD `path` varchar(128) NOT NULL default '/' AFTER `message_auto_response`;
+
+UPDATE `%TABLE_PREFIX%department`
+  SET `path` = CONCAT('/', id, '/');
+
 -- Set new schema signature
 UPDATE `%TABLE_PREFIX%config`
-    SET `value` = 'e675900fdac39ec981a8d65fc82907b7'
+    SET `value` = 'e7a338f95ed622678123386a8a59dfc0'
     WHERE `key` = 'schema_signature' AND `namespace` = 'core';
diff --git a/include/upgrader/streams/core/36f6b328-e675900f.task.php b/include/upgrader/streams/core/36f6b328-e7a338f9.task.php
similarity index 100%
rename from include/upgrader/streams/core/36f6b328-e675900f.task.php
rename to include/upgrader/streams/core/36f6b328-e7a338f9.task.php
diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql
index d17ef3cdb1c998a20946d10787a955dcce2fe37b..9cc9a40035c59a836bddd2d926bf67023a1ef544 100644
--- a/setup/inc/streams/core/install-mysql.sql
+++ b/setup/inc/streams/core/install-mysql.sql
@@ -200,6 +200,7 @@ CREATE TABLE `%TABLE_PREFIX%list_items` (
 DROP TABLE IF EXISTS `%TABLE_PREFIX%department`;
 CREATE TABLE `%TABLE_PREFIX%department` (
   `id` int(11) unsigned NOT NULL auto_increment,
+  `pid` int(11) unsigned default NULL,
   `tpl_id` int(10) unsigned NOT NULL default '0',
   `sla_id` int(10) unsigned NOT NULL default '0',
   `email_id` int(10) unsigned NOT NULL default '0',
@@ -211,6 +212,7 @@ CREATE TABLE `%TABLE_PREFIX%department` (
   `group_membership` tinyint(1) NOT NULL default '0',
   `ticket_auto_response` tinyint(1) NOT NULL default '1',
   `message_auto_response` tinyint(1) NOT NULL default '0',
+  `path` varchar(128) NOT NULL default '/',
   `updated` datetime NOT NULL,
   `created` datetime NOT NULL,
   PRIMARY KEY  (`id`),