diff --git a/module/bug/js/browse.js b/module/bug/js/browse.js
index 4dc58de983..db0315a2f4 100644
--- a/module/bug/js/browse.js
+++ b/module/bug/js/browse.js
@@ -11,10 +11,11 @@ $(document).ready(function()
}).on('keyup change paste', 'input', function()
{
var val = $(this).val().toLowerCase();
+ if(val == '') return $('.assign-menu > .option').removeClass('hide');
$('.assign-menu > .option').each(function()
{
var $option = $(this);
- $option.toggleClass('hide', $option.text().toLowerCase().indexOf(val) < 0 && $option.data('key').toLowerCase().indexOf(val) < 0);
+ $option.toggleClass('hide', $option.text().toLowerCase().indexOf(val) < 0 || $option.data('key').toLowerCase().indexOf(val) < 0);
});
});
});
diff --git a/module/product/control.php b/module/product/control.php
index dfc74b85ee..deec94e732 100644
--- a/module/product/control.php
+++ b/module/product/control.php
@@ -168,7 +168,7 @@ class product extends control
$this->view->moduleTree = $this->tree->getTreeMenu($productID, $viewType = 'story', $startModuleID = 0, array('treeModel', 'createStoryLink'));
$this->view->parentModules = $this->tree->getParents($moduleID);
$this->view->pager = $pager;
- $this->view->users = $this->user->getPairs('noletter');
+ $this->view->users = $this->user->getPairs('nodeleted|noletter|pofirst');
$this->view->orderBy = $orderBy;
$this->view->browseType = $browseType;
$this->view->moduleID = $moduleID;
diff --git a/module/product/css/browse.css b/module/product/css/browse.css
new file mode 100644
index 0000000000..c48171968a
--- /dev/null
+++ b/module/product/css/browse.css
@@ -0,0 +1,4 @@
+.assign-menu {min-width: 150px; overflow: hidden; max-height: 305px}
+.assign-menu.with-search {padding-bottom: 34px;}
+.assign-menu > .assign-search {padding: 0; position: absolute; z-index: 0; bottom: 0; left: 0; right: 0}
+.assign-menu > .assign-search .input-group-addon {position: absolute; right: 10px; top: 0; z-index: 10; background: none; border: none; color: #666}
diff --git a/module/product/js/browse.js b/module/product/js/browse.js
index 55a80528c5..1f9386fd99 100644
--- a/module/product/js/browse.js
+++ b/module/product/js/browse.js
@@ -1,4 +1,19 @@
$(function()
{
if(browseType == 'bysearch') ajaxGetSearchForm();
+
+ $('.assign-search').click(function(e)
+ {
+ e.stopPropagation();
+ return false;
+ }).on('keyup change paste', 'input', function()
+ {
+ var val = $(this).val().toLowerCase();
+ if(val == '') return $('.assign-menu > .option').removeClass('hide');
+ $('.assign-menu > .option').each(function()
+ {
+ var $option = $(this);
+ $option.toggleClass('hide', $option.text().toLowerCase().indexOf(val) < 0 || $option.data('key').toLowerCase().indexOf(val) < 0);
+ });
+ });
})
diff --git a/module/product/view/browse.html.php b/module/product/view/browse.html.php
index cfd30b5bf2..efd88d4008 100644
--- a/module/product/view/browse.html.php
+++ b/module/product/view/browse.html.php
@@ -220,6 +220,28 @@
{
echo '
' . html::a('javascript:;', $lang->story->stageAB, '', $class) . '';
}
+
+ if(common::hasPriv('story', 'batchAssignTo'))
+ {
+ $withSearch = count($users) > 10;
+ $actionLink = $this->createLink('story', 'batchAssignTo', "productID=$productID");
+ echo html::select('assignedTo', $users, '', 'class="hidden"');
+ echo "";
+ }
+ else
+ {
+ echo '' . html::a('javascript:;', $lang->story->assignedTo, '', $class) . '';
+ }
?>
diff --git a/module/project/js/task.js b/module/project/js/task.js
index d01f31c2d2..ba462632b8 100644
--- a/module/project/js/task.js
+++ b/module/project/js/task.js
@@ -11,10 +11,11 @@ $(function()
}).on('keyup change paste', 'input', function()
{
var val = $(this).val().toLowerCase();
+ if(val == '') return $('.assign-menu > .option').removeClass('hide');
$('.assign-menu > .option').each(function()
{
var $option = $(this);
- $option.toggleClass('hide', $option.text().toLowerCase().indexOf(val) < 0 && $option.data('key').toLowerCase().indexOf(val) < 0);
+ $option.toggleClass('hide', $option.text().toLowerCase().indexOf(val) < 0 || $option.data('key').toLowerCase().indexOf(val) < 0);
});
});
});
diff --git a/module/story/control.php b/module/story/control.php
index ef9f19d7f7..b37686b049 100644
--- a/module/story/control.php
+++ b/module/story/control.php
@@ -799,6 +799,28 @@ class story extends control
die(js::locate($this->session->storyList, 'parent'));
}
+ /**
+ * Batch assign to.
+ *
+ * @access public
+ * @return void
+ */
+ public function batchAssignTo()
+ {
+ if(!empty($_POST) && isset($_POST['storyIDList']))
+ {
+ $allChanges = $this->story->batchAssignTo();
+ if(dao::isError()) die(js::error(dao::getError()));
+ foreach($allChanges as $storyID => $changes)
+ {
+ $actionID = $this->action->create('story', $storyID, 'Edited');
+ $this->action->logHistory($actionID, $changes);
+ $this->sendmail($storyID, $actionID);
+ }
+ }
+ die(js::locate($this->session->storyList));
+ }
+
/**
* Tasks of a story.
*
diff --git a/module/story/model.php b/module/story/model.php
index 4d38d13da1..0b4ae99010 100644
--- a/module/story/model.php
+++ b/module/story/model.php
@@ -768,6 +768,36 @@ class storyModel extends model
return $allChanges;
}
+ /**
+ * Batch assign to.
+ *
+ * @access public
+ * @return array
+ */
+ public function batchAssignTo()
+ {
+ $now = helper::now();
+ $allChanges = array();
+ $storyIDList = $this->post->storyIDList;
+ $assignedTo = $this->post->assignedTo;
+ $oldStories = $this->getByList($storyIDList);
+ foreach($storyIDList as $storyID)
+ {
+ $oldStory = $oldStories[$storyID];
+ if($assignedTo == $oldStory->assignedTo) continue;
+
+ $story = new stdclass();
+ $story->lastEditedBy = $this->app->user->account;
+ $story->lastEditedDate = $now;
+ $story->assignedTo = $assignedTo;
+ $story->assignedDate = $now;
+
+ $this->dao->update(TABLE_STORY)->data($story)->autoCheck()->where('id')->eq((int)$storyID)->exec();
+ if(!dao::isError()) $allChanges[$storyID] = common::createChanges($oldStory, $story);
+ }
+ return $allChanges;
+ }
+
/**
* Activate a story.
*