Merge branch 'master' of git.zcorp.cc:easycorp/zentaopms
This commit is contained in:
+1
-12
@@ -26,7 +26,7 @@ class form extends baseFixer
|
||||
* 错误信息列表。
|
||||
* Error list.
|
||||
*
|
||||
* @var object
|
||||
* @var array
|
||||
*/
|
||||
public $errors;
|
||||
|
||||
@@ -138,17 +138,6 @@ class form extends baseFixer
|
||||
$this->data->$field = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印表单数据。
|
||||
* Print the form data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function a()
|
||||
{
|
||||
a($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤表单字段数据。
|
||||
* Filter the form field data.
|
||||
|
||||
+29
-52
@@ -3,29 +3,28 @@ namespace zin;
|
||||
|
||||
require_once dirname(__DIR__) . DS . 'formgroup' . DS . 'v1.php';
|
||||
require_once dirname(__DIR__) . DS . 'formrow' . DS . 'v1.php';
|
||||
require_once dirname(__DIR__) . DS . 'formbase' . DS . 'v1.php';
|
||||
|
||||
class form extends wg
|
||||
/**
|
||||
* 通用表单(form)部件类,支持 Ajax 提交
|
||||
* The common form widget class
|
||||
*/
|
||||
class form extends formBase
|
||||
{
|
||||
protected static $defineProps = array(
|
||||
'method?: string',
|
||||
'url?: string',
|
||||
'actions?: array',
|
||||
'target?: string',
|
||||
protected static $defineProps = array
|
||||
(
|
||||
'items?: array',
|
||||
'grid?: bool',
|
||||
'labelWidth?: int',
|
||||
'submitBtnText?: string',
|
||||
'cancelBtnText?: string',
|
||||
'labelWidth?: int'
|
||||
);
|
||||
|
||||
protected static $defaultProps = array(
|
||||
protected static $defaultProps = array
|
||||
(
|
||||
'grid' => true,
|
||||
'method' => 'post',
|
||||
'target' => 'ajax',
|
||||
'actions' => ['submit', 'cancel'],
|
||||
'actionsClass' => 'form-group gap-4 no-label'
|
||||
);
|
||||
|
||||
public function onBuildItem($item)
|
||||
public function onBuildItem(item|array $item): wg
|
||||
{
|
||||
if(!($item instanceof item))
|
||||
{
|
||||
@@ -38,32 +37,26 @@ class form extends wg
|
||||
return new formGroup(inherit($item));
|
||||
}
|
||||
|
||||
protected function buildFormActions()
|
||||
protected function buildActions(): wg
|
||||
{
|
||||
$actions = $this->prop('actions');
|
||||
if(empty($actions)) return NULL;
|
||||
$actions = parent::buildActions();
|
||||
if($this->prop('grid') && !empty($actions)) $actions = div(setClass('form-row'), $actions);
|
||||
return $actions;
|
||||
}
|
||||
|
||||
protected function buildProps(): array
|
||||
{
|
||||
list($grid, $labelWidth) = $this->prop(array('grid', 'labelWidth'));
|
||||
$props = parent::buildProps();
|
||||
if($grid) $props[] = set::class('form-grid');
|
||||
if(!empty($labelWidth)) $props[] = setCssVar('form-label-width', $labelWidth);
|
||||
|
||||
global $lang;
|
||||
foreach($actions as $key => $action)
|
||||
{
|
||||
if($action === 'submit') $actions[$key] = ['text' => $this->prop('submitBtnText') ?? $lang->save, 'btnType' => 'submit', 'type' => 'primary'];
|
||||
elseif($action === 'cancel') $actions[$key] = ['text' => $this->prop('cancelBtnText') ?? $lang->goback, 'url' => html::getGobackLink()];
|
||||
elseif(is_string($action)) $actions[$key] = ['text' => $action];
|
||||
}
|
||||
|
||||
return toolbar
|
||||
(
|
||||
set::class('form-actions form-group gap-4 no-label'),
|
||||
set::items($actions)
|
||||
);
|
||||
return $props;
|
||||
}
|
||||
|
||||
protected function build()
|
||||
protected function buildContent(): array
|
||||
{
|
||||
list($items, $grid, $labelWidth, $url, $target, $method, $id) = $this->prop(['items', 'grid', 'labelWidth', 'url', 'target', 'method', 'id']);
|
||||
|
||||
$actions = $this->buildFormActions();
|
||||
if($grid && !empty($actions)) $actions = div(setClass('form-row'), $actions);
|
||||
list($items, $grid) = $this->prop(['items', 'grid']);
|
||||
|
||||
$list = is_array($items) ? array_map(array($this, 'onBuildItem'), $items) : [];
|
||||
$children = $this->children();
|
||||
@@ -76,23 +69,7 @@ class form extends wg
|
||||
if($item instanceof formGroup) $list[$key] = new formRow($item);
|
||||
}
|
||||
}
|
||||
$isAjax = $target === 'ajax';
|
||||
if($isAjax)
|
||||
{
|
||||
$target = NULL;
|
||||
if(empty($id)) $id = $this->gid;
|
||||
}
|
||||
if(empty($url)) $url = $_SERVER['REQUEST_URI'];
|
||||
|
||||
return h::form
|
||||
(
|
||||
set::class('form load-indicator', $grid ? 'form-grid' : NULL, $isAjax ? 'form-ajax' : ''),
|
||||
set(['id' => $id, 'action' => $url, 'target' => $target, 'method' => $method]),
|
||||
set($this->getRestProps()),
|
||||
empty($labelWidth) ? NULL : setCssVar('form-label-width', $labelWidth),
|
||||
$list,
|
||||
$actions,
|
||||
$isAjax ? zui::ajaxForm(set::_to("#$id")) : NULL
|
||||
);
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* The formBase widget class file of zin module of ZenTaoPMS.
|
||||
*
|
||||
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
|
||||
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
|
||||
* @author sunhao<sunhao@easycorp.ltd>
|
||||
* @package zin
|
||||
* @link http://www.zentao.net
|
||||
*/
|
||||
namespace zin;
|
||||
|
||||
/**
|
||||
* 基础表单(formBase)部件类,支持 Ajax 提交
|
||||
* The formBase widget class
|
||||
*/
|
||||
class formBase extends wg
|
||||
{
|
||||
protected static $defineProps = array
|
||||
(
|
||||
'method?: string',
|
||||
'url?: string',
|
||||
'actions?: array',
|
||||
'actionsClass?: string',
|
||||
'target?: string',
|
||||
'submitBtnText?: string',
|
||||
'cancelBtnText?: string',
|
||||
);
|
||||
|
||||
protected static $defaultProps = array
|
||||
(
|
||||
'method' => 'post',
|
||||
'target' => 'ajax',
|
||||
'actions' => ['submit', 'cancel'],
|
||||
);
|
||||
|
||||
protected function buildActions(): wg
|
||||
{
|
||||
$actions = $this->prop('actions');
|
||||
if(empty($actions)) return NULL;
|
||||
|
||||
global $lang;
|
||||
foreach($actions as $key => $action)
|
||||
{
|
||||
if($action === 'submit') $actions[$key] = ['text' => $this->prop('submitBtnText') ?? $lang->save, 'btnType' => 'submit', 'type' => 'primary'];
|
||||
elseif($action === 'cancel') $actions[$key] = ['text' => $this->prop('cancelBtnText') ?? $lang->goback, 'url' => html::getGobackLink()];
|
||||
elseif(is_string($action)) $actions[$key] = ['text' => $action];
|
||||
}
|
||||
|
||||
return toolbar
|
||||
(
|
||||
set::class('form-actions', $this->prop('actionsClass')),
|
||||
set::items($actions)
|
||||
);
|
||||
}
|
||||
|
||||
protected function buildContent(): array|wg
|
||||
{
|
||||
return $this->children();
|
||||
}
|
||||
|
||||
protected function buildProps(): array
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
protected function build(): wg
|
||||
{
|
||||
list($url, $target, $method, $id) = $this->prop(['url', 'target', 'method', 'id']);
|
||||
|
||||
$isAjax = $target === 'ajax';
|
||||
if($isAjax)
|
||||
{
|
||||
$target = NULL;
|
||||
if(empty($id)) $id = $this->gid;
|
||||
}
|
||||
if(empty($url)) $url = $_SERVER['REQUEST_URI'];
|
||||
|
||||
return h::form
|
||||
(
|
||||
set::class('form load-indicator', $isAjax ? 'form-ajax' : ''),
|
||||
set(['id' => $id, 'action' => $url, 'target' => $target, 'method' => $method]),
|
||||
set($this->getRestProps()),
|
||||
$this->buildProps(),
|
||||
$this->buildContent(),
|
||||
$this->buildActions(),
|
||||
$isAjax ? zui::ajaxForm(set::_to("#$id")) : NULL
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
.nav-tabs>.nav-item.active>a {color: var(--color-primary-500);}
|
||||
.nav-tabs>.nav-item.active>a:after {border-bottom-color: var(--color-primary-500) !important; border-bottom-width: 2px;}
|
||||
+64
-28
@@ -10,6 +10,11 @@ class tabs extends wg
|
||||
'activeID?string'
|
||||
);
|
||||
|
||||
public static function getPageCSS()
|
||||
{
|
||||
return file_get_contents(__DIR__ . DS . 'css' . DS . 'v1.css');
|
||||
}
|
||||
|
||||
protected function getItemID(array $item): string
|
||||
{
|
||||
return isset($item['id']) ? $item['id'] : $this->gid;
|
||||
@@ -17,7 +22,8 @@ class tabs extends wg
|
||||
|
||||
protected function buildLabelView(string $id, array $item)
|
||||
{
|
||||
$isActive = $this->prop('activeID') == $id;
|
||||
if(!isset($item['label'])) return null;
|
||||
$isActive = $this->prop('activeID') == $id || (isset($item['active']) && $item['active']);
|
||||
return li
|
||||
(
|
||||
setClass('nav-item'),
|
||||
@@ -25,6 +31,7 @@ class tabs extends wg
|
||||
a
|
||||
(
|
||||
set('data-toggle', 'tab'),
|
||||
setClass('font-medium'),
|
||||
set::href("#$id"),
|
||||
$item['label']
|
||||
)
|
||||
@@ -33,57 +40,86 @@ class tabs extends wg
|
||||
|
||||
protected function buildContentView(string $id, array $item)
|
||||
{
|
||||
$isActive = $this->prop('activeID') == $id;
|
||||
if(!isset($item['data'])) return null;
|
||||
$isActive = $this->prop('activeID') == $id || (isset($item['active']) && $item['active']);
|
||||
return div
|
||||
(
|
||||
setClass('tab-pane'),
|
||||
setID($id),
|
||||
$isActive ? setClass('active') : null,
|
||||
isset($item['data']) ? $item['data'] : null
|
||||
$item['data']
|
||||
);
|
||||
}
|
||||
|
||||
protected function buildTabHeader($labelViews)
|
||||
{
|
||||
if(empty($labelViews)) return null;
|
||||
|
||||
$isVertical = $this->prop('direction') === 'v';
|
||||
return ul
|
||||
(
|
||||
setClass('nav nav-tabs'),
|
||||
$isVertical ? setClass('nav-stacked') : null,
|
||||
$labelViews
|
||||
);
|
||||
}
|
||||
|
||||
protected function buildTabBody($contentViews)
|
||||
{
|
||||
if(empty($contentViews)) return null;
|
||||
|
||||
return div
|
||||
(
|
||||
setClass('tab-content'),
|
||||
$contentViews
|
||||
);
|
||||
}
|
||||
|
||||
protected function build()
|
||||
{
|
||||
$items = $this->prop('items');
|
||||
$direction = $this->prop('direction');
|
||||
$activeID = $this->prop('activeID');
|
||||
$items = $this->prop('items');
|
||||
// $activeID = $this->prop('activeID');
|
||||
$isVertical = $this->prop('direction') === 'v';
|
||||
|
||||
if(empty($items)) return null;
|
||||
if(empty($items))
|
||||
{
|
||||
return div
|
||||
(
|
||||
set($this->props->skip(array_keys(static::getDefinedProps()))),
|
||||
$isVertical ? setClass('flex') : null,
|
||||
|
||||
$this->children()
|
||||
);
|
||||
}
|
||||
|
||||
$labelViews = array();
|
||||
$contentViews = array();
|
||||
foreach($items as $item)
|
||||
{
|
||||
$id = $this->getItemID($item);
|
||||
$labelViews[] = $this->buildLabelView($id, $item);
|
||||
$contentViews[] = $this->buildContentView($id, $item);
|
||||
|
||||
$labelView = $this->buildLabelView($id, $item);
|
||||
$contentView = $this->buildContentView($id, $item);
|
||||
if(!empty($labelView)) $labelViews[] = $labelView;
|
||||
if(!empty($contentView)) $contentViews[] = $contentView;
|
||||
|
||||
}
|
||||
|
||||
/* There is no active item, then set index 0 to be actived. */
|
||||
if(empty($activeID))
|
||||
{
|
||||
$labelViews[0]->setProp('class', 'active');
|
||||
$contentViews[0]->setProp('class', 'active');
|
||||
}
|
||||
// if(empty($activeID))
|
||||
// {
|
||||
// $labelViews[0]->setProp('class', 'active');
|
||||
// $contentViews[0]->setProp('class', 'active');
|
||||
// }
|
||||
|
||||
return div
|
||||
(
|
||||
set($this->props->skip(array_keys(static::getDefinedProps()))),
|
||||
$direction == 'v' ? setClass('flex') : null,
|
||||
/* Tabs. */
|
||||
ul
|
||||
(
|
||||
setClass('nav nav-tabs'),
|
||||
$direction == 'v' ? setClass('nav-stacked') : null,
|
||||
$labelViews
|
||||
),
|
||||
/* Content. */
|
||||
div
|
||||
(
|
||||
setClass('tab-content'),
|
||||
$contentViews
|
||||
)
|
||||
$isVertical ? setClass('flex') : null,
|
||||
|
||||
$this->buildTabHeader($labelViews),
|
||||
$this->buildTabBody($contentViews),
|
||||
$this->children()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+123
-1872
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
.projectstatistic-block .tab-content {width: 100%}
|
||||
@@ -11,47 +11,380 @@ declare(strict_types=1);
|
||||
|
||||
namespace zin;
|
||||
|
||||
$isChineseLang = in_array($this->app->getClientLang(), array('zh-cn','zh-tw'));
|
||||
$blockNavID = 'nav-' . uniqid();
|
||||
$selected = key($projects);
|
||||
$navTabs = array();
|
||||
foreach($projects as $project)
|
||||
{
|
||||
$navTabs[] = li
|
||||
(
|
||||
set('class', 'nav-item' . ($project->id == $selected ? ' active' : '')),
|
||||
a
|
||||
(
|
||||
set('data-toggle', 'tab'),
|
||||
set('href', "#tab3{$blockNavID}Content{$project->id}"),
|
||||
$project->name
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$tabItems = array();
|
||||
foreach($projects as $project)
|
||||
{
|
||||
$tabItems[] = div
|
||||
(
|
||||
set('class', 'tab-pane' . ($project->id == $selected ? ' active' : '')),
|
||||
set('id', "tab3{$blockNavID}Content{$project->id}"),
|
||||
in_array($project->model, array('scrum', 'kanban', 'agileplus')) ? div
|
||||
(
|
||||
div
|
||||
(
|
||||
set('class', 'flex justify-around'),
|
||||
div
|
||||
(
|
||||
set('class', 'text-center'),
|
||||
h4($lang->block->storyCount),
|
||||
div
|
||||
(
|
||||
div
|
||||
(
|
||||
set('class', 'statistic-title'),
|
||||
$lang->block->allStories . ":",
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'statistic-data'),
|
||||
$project->allStories,
|
||||
)
|
||||
),
|
||||
div
|
||||
(
|
||||
div
|
||||
(
|
||||
set('class', 'statistic-title'),
|
||||
$lang->block->finish . ":",
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'statistic-data'),
|
||||
$project->doneStories,
|
||||
)
|
||||
),
|
||||
div
|
||||
(
|
||||
div
|
||||
(
|
||||
set('class', 'statistic-title'),
|
||||
$lang->project->surplus . ":",
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'statistic-data'),
|
||||
$project->leftStories,
|
||||
)
|
||||
)
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'text-center'),
|
||||
h4($lang->block->investment),
|
||||
div
|
||||
(
|
||||
div
|
||||
(
|
||||
set('class', 'statistic-title'),
|
||||
$lang->block->totalPeople . ":",
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'statistic-data'),
|
||||
$project->teamCount,
|
||||
)
|
||||
),
|
||||
div
|
||||
(
|
||||
div
|
||||
(
|
||||
set('class', 'statistic-title'),
|
||||
$lang->block->estimate . ":",
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'statistic-data'),
|
||||
$project->estimate . $lang->execution->workHourUnit,
|
||||
)
|
||||
),
|
||||
div
|
||||
(
|
||||
div
|
||||
(
|
||||
set('class', 'statistic-title'),
|
||||
$lang->block->consumedHours . ":",
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'statistic-data'),
|
||||
$project->consumed . $lang->execution->workHourUnit,
|
||||
)
|
||||
)
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'text-center'),
|
||||
h4($lang->block->taskCount),
|
||||
div
|
||||
(
|
||||
div
|
||||
(
|
||||
set('class', 'statistic-title'),
|
||||
$lang->block->wait . ":",
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'statistic-data'),
|
||||
$project->waitTasks,
|
||||
)
|
||||
),
|
||||
div
|
||||
(
|
||||
div
|
||||
(
|
||||
set('class', 'statistic-title'),
|
||||
$lang->block->doing . ":",
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'statistic-data'),
|
||||
$project->doingTasks,
|
||||
)
|
||||
),
|
||||
div
|
||||
(
|
||||
div
|
||||
(
|
||||
set('class', 'statistic-title'),
|
||||
$lang->block->done . ":",
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'statistic-data'),
|
||||
$project->rndDoneTasks,
|
||||
)
|
||||
)
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'text-center'),
|
||||
h4($lang->block->bugCount),
|
||||
div
|
||||
(
|
||||
div
|
||||
(
|
||||
set('class', 'col col-title'),
|
||||
$lang->block->totalBug . ":",
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'col col-data'),
|
||||
$project->allBugs,
|
||||
)
|
||||
),
|
||||
div
|
||||
(
|
||||
div
|
||||
(
|
||||
set('class', 'col col-title'),
|
||||
$lang->bug->statusList['resolved'] . ":",
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'col col-data'),
|
||||
$project->doneBugs,
|
||||
)
|
||||
),
|
||||
div
|
||||
(
|
||||
div
|
||||
(
|
||||
set('class', 'col col-title'),
|
||||
$lang->bug->unResolved . ":",
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'col col-data'),
|
||||
$project->leftBugs,
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
(!empty($project->executions) and $project->multiple) ? div
|
||||
(
|
||||
set('class', 'flex'),
|
||||
div
|
||||
(
|
||||
set('class', 'flex-1 text-right'),
|
||||
h4($lang->block->last)
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'flex-1 text-center'),
|
||||
a
|
||||
(
|
||||
set('href', $this->createLink('execution', 'task', "executionID={$project->executions[0]->id}")),
|
||||
set('title', $project->executions[0]->name),
|
||||
$project->executions[0]->name,
|
||||
)
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'flex-1'),
|
||||
div
|
||||
(
|
||||
set('class', 'progress'),
|
||||
span
|
||||
(
|
||||
set('class', 'mr-4'),
|
||||
$project->executions[0]->hours->progress . '%'
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'progress-bar'),
|
||||
set('role', 'progressbar'),
|
||||
setStyle(['width' => $project->executions[0]->hours->progress . '%']),
|
||||
)
|
||||
)
|
||||
)
|
||||
) : null
|
||||
) : div
|
||||
(
|
||||
set('class', 'weekly-row'),
|
||||
div
|
||||
(
|
||||
span
|
||||
(
|
||||
set('class', 'weekly-title'),
|
||||
$lang->project->weekly
|
||||
),
|
||||
span
|
||||
(
|
||||
set('class', 'weekly-stage'),
|
||||
$project->current
|
||||
)
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'flex'),
|
||||
div
|
||||
(
|
||||
set('class', 'flex-1'),
|
||||
div
|
||||
(
|
||||
set('class', 'progress'),
|
||||
span
|
||||
(
|
||||
set('class', 'mr-4'),
|
||||
$lang->project->progress . ' : ' . $project->progress . '%'
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'progress-bar'),
|
||||
set('role', 'progressbar'),
|
||||
setStyle(['width' => $project->progress . '%']),
|
||||
)
|
||||
)
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'flex-1 text-center'),
|
||||
$lang->project->teamCount . ' : ' . $project->teamCount
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'flex-1 text-left'),
|
||||
$lang->project->budget . ' : ' . ($project->budget != 0 ? $project->budget : $this->lang->project->future)
|
||||
),
|
||||
div(set('class', 'flex-1'))
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'flex'),
|
||||
div
|
||||
(
|
||||
set('class', 'flex-1'),
|
||||
$isChineseLang ? $lang->project->pv . '(' . $lang->project->pvTitle . ')' : $lang->project->pv
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'flex-1'),
|
||||
$isChineseLang ? $lang->project->ev . '(' . $lang->project->evTitle . ')' : $lang->project->ev
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'flex-1'),
|
||||
$isChineseLang ? $lang->project->ac . '(' . $lang->project->acTitle . ')' : $lang->project->ac
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'flex-1'),
|
||||
$isChineseLang ? $lang->project->sv . '(' . $lang->project->svTitle . ')' : $lang->project->sv
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'flex-1'),
|
||||
$isChineseLang ? $lang->project->cv . '(' . $lang->project->cvTitle . ')' : $lang->project->cv
|
||||
)
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'flex'),
|
||||
div
|
||||
(
|
||||
set('class', 'flex-1'),
|
||||
$project->pv
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'flex-1'),
|
||||
$project->ev
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'flex-1'),
|
||||
$project->ac
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'flex-1'),
|
||||
$project->sv
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'flex-1'),
|
||||
$project->cv
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
panel
|
||||
(
|
||||
set('class', 'welcome-block'),
|
||||
ul
|
||||
(
|
||||
set('class', 'nav nav-tabs'),
|
||||
li
|
||||
(
|
||||
set('class', 'nav-item'),
|
||||
a
|
||||
(
|
||||
set('href', '#tabCount1'),
|
||||
'标签1'
|
||||
)
|
||||
),
|
||||
li
|
||||
(
|
||||
set('class', 'nav-item'),
|
||||
a
|
||||
(
|
||||
set('href', '#tabCount2'),
|
||||
'标签2'
|
||||
)
|
||||
)
|
||||
),
|
||||
set('class', 'projectstatistic-block'),
|
||||
div
|
||||
(
|
||||
set('class', 'tab-content'),
|
||||
set('class', 'flex'),
|
||||
ul
|
||||
(
|
||||
set('class', 'nav nav-tabs nav-stacked'),
|
||||
$navTabs
|
||||
),
|
||||
div
|
||||
(
|
||||
set('class', 'tab-pane'),
|
||||
set('id', 'tabCount1'),
|
||||
"我是标签1"
|
||||
)
|
||||
div
|
||||
(
|
||||
set('class', 'tab-pane'),
|
||||
set('id', 'tabCount2'),
|
||||
"我是标签2"
|
||||
set('class', 'tab-content'),
|
||||
$tabItems
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
render();
|
||||
|
||||
@@ -88,7 +88,6 @@ html[lang="en"] .product-info .type-info {color: #A6AAB8; text-align: center; po
|
||||
.forty-percent {width: 40%;}
|
||||
</style>
|
||||
<script>
|
||||
<?php $blockNavId = 'nav-' . uniqid(); ?>
|
||||
$(function()
|
||||
{
|
||||
var $nav = $('#<?php echo $blockNavId;?>');
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -391,7 +391,7 @@ class product extends control
|
||||
if(!empty($_POST))
|
||||
{
|
||||
$data = form::data($this->config->product->form->create);
|
||||
$product = $this->productZen->prepareCreateExtras($data);
|
||||
$product = $this->productZen->prepareCreateExtras($data, $this->post->acl, $this->post->uid);
|
||||
|
||||
$productID = $this->product->create($product, $this->post->uid, zget($_POST, 'lineName', ''));
|
||||
if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError()));
|
||||
@@ -422,7 +422,7 @@ class product extends control
|
||||
if(!empty($_POST))
|
||||
{
|
||||
$data = form::data($this->config->product->form->edit);
|
||||
$product = $this->productZen->prepareEditExtras($data);
|
||||
$product = $this->productZen->prepareEditExtras($data, $this->post->acl, $this->post->uid);
|
||||
|
||||
$changes = $this->product->update($productID, $product, $this->post->uid);
|
||||
if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError()));
|
||||
|
||||
+11
-53
@@ -1115,6 +1115,7 @@ class productModel extends model
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取与该产品关联的项目列表。
|
||||
* Get project list by product.
|
||||
*
|
||||
* @param int $productID
|
||||
@@ -1143,6 +1144,7 @@ class productModel extends model
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据产品,获取与该产品关联的项目的统计信息。
|
||||
* Get project stats by product.
|
||||
*
|
||||
* @param int $productID
|
||||
@@ -1152,7 +1154,7 @@ class productModel extends model
|
||||
* @param string $orderBy
|
||||
* @param object $pager
|
||||
* @access public
|
||||
* @return array
|
||||
* @return int[]
|
||||
*/
|
||||
public function getProjectStatsByProduct(int $productID, string $browseType = 'all', string $branch = '0', bool $involved = false, string $orderBy = 'order_desc', object|null $pager = null): array
|
||||
{
|
||||
@@ -1161,8 +1163,6 @@ class productModel extends model
|
||||
|
||||
$projectKeys = array_keys($projects);
|
||||
$stats = array();
|
||||
$hours = array();
|
||||
$emptyHour = array('totalEstimate' => 0, 'totalConsumed' => 0, 'totalLeft' => 0, 'progress' => 0);
|
||||
|
||||
/* Get all tasks and compute totalEstimate, totalConsumed, totalLeft, progress according to them. */
|
||||
$tasks = $this->dao->select('id, project, estimate, consumed, `left`, status, closedReason')
|
||||
@@ -1171,59 +1171,17 @@ class productModel extends model
|
||||
->andWhere('parent')->lt(1)
|
||||
->andWhere('deleted')->eq(0)
|
||||
->fetchGroup('project', 'id');
|
||||
|
||||
/* Compute totalEstimate, totalConsumed, totalLeft. */
|
||||
foreach($tasks as $projectID => $projectTasks)
|
||||
{
|
||||
$hour = (object)$emptyHour;
|
||||
foreach($projectTasks as $task)
|
||||
{
|
||||
if($task->status != 'cancel')
|
||||
{
|
||||
$hour->totalEstimate += $task->estimate;
|
||||
$hour->totalConsumed += $task->consumed;
|
||||
}
|
||||
if($task->status != 'cancel' and $task->status != 'closed') $hour->totalLeft += $task->left;
|
||||
}
|
||||
$hours[$projectID] = $hour;
|
||||
}
|
||||
|
||||
/* Compute totalReal and progress. */
|
||||
$progressList = $this->loadModel('project')->getWaterfallProgress(array_keys($hours));
|
||||
foreach($hours as $projectID => $hour)
|
||||
{
|
||||
$hour->totalEstimate = round($hour->totalEstimate, 1) ;
|
||||
$hour->totalConsumed = round($hour->totalConsumed, 1);
|
||||
$hour->totalLeft = round($hour->totalLeft, 1);
|
||||
$hour->totalReal = $hour->totalConsumed + $hour->totalLeft;
|
||||
$hour->progress = $projects[$projectID]->model == 'waterfall' ? $progressList[$projectID] : ($hour->totalReal ? round($hour->totalConsumed / $hour->totalReal, 2) * 100 : 0);
|
||||
}
|
||||
$hours = $this->loadModel('program')->computeProjectHours($tasks);
|
||||
|
||||
/* Get the number of project teams. */
|
||||
$teams = $this->dao->select('root,count(*) as teams')->from(TABLE_TEAM)
|
||||
->where('root')->in($projectKeys)
|
||||
->andWhere('type')->eq('project')
|
||||
->groupBy('root')
|
||||
->fetchAll('root');
|
||||
$teams = $this->dao->select('t1.root,t1.account')->from(TABLE_TEAM)->alias('t1')
|
||||
->leftJoin(TABLE_USER)->alias('t2')->on('t1.account=t2.account')
|
||||
->where('t1.root')->in($projectKeys)
|
||||
->andWhere('t1.type')->eq('project')
|
||||
->andWhere('t2.deleted')->eq(0)
|
||||
->fetchGroup('root', 'account');
|
||||
|
||||
/* Process projects. */
|
||||
foreach($projects as $key => $project)
|
||||
{
|
||||
if($project->end == '0000-00-00') $project->end = '';
|
||||
|
||||
/* Judge whether the project is delayed. */
|
||||
if($project->status != 'done' and $project->status != 'closed' and $project->status != 'suspended')
|
||||
{
|
||||
$delay = helper::diffDate(helper::today(), $project->end);
|
||||
if($delay > 0) $project->delay = $delay;
|
||||
}
|
||||
|
||||
/* Process the hours. */
|
||||
$project->hours = isset($hours[$project->id]) ? $hours[$project->id] : (object)$emptyHour;
|
||||
|
||||
$project->teamCount = isset($teams[$project->id]) ? $teams[$project->id]->teams : 0;
|
||||
$stats[] = $project;
|
||||
}
|
||||
$stats = $this->program->appendStatToProjects($projects, 'hours,teamCount', array('hours' => $hours, 'teams' => $teams));
|
||||
return $stats;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,8 +46,29 @@ $product->app->user->admin = true;
|
||||
$product->app->moduleName = 'product';
|
||||
$product->app->methodName = 'project';
|
||||
|
||||
r(count($product->getProjectListByProduct($productID = 0, 'all', '', 'order_desc'))) && p() && e('0');
|
||||
r(count($product->getProjectListByProduct($productID = 1, 'all', '', 'order_desc'))) && p() && e('20');
|
||||
r(count($product->getProjectListByProduct($productID = 1, 'all', '0', 'order_desc'))) && p() && e('17');
|
||||
r(count($product->getProjectListByProduct($productID = 1, 'undone', '', 'order_desc'))) && p() && e('15');
|
||||
r(count($product->getProjectListByProduct($productID = 1, 'wait', 'all', 'order_desc'))) && p() && e('5');
|
||||
r(count($product->getProjectListByProduct($productID = 0, 'all', '', false, 'order_desc'))) && p() && e('0');
|
||||
r(count($product->getProjectListByProduct($productID = 1, 'all', '', false, 'order_desc'))) && p() && e('20');
|
||||
r(count($product->getProjectListByProduct($productID = 1, 'all', '0', false, 'order_desc'))) && p() && e('17');
|
||||
r(count($product->getProjectListByProduct($productID = 1, 'undone', '', false, 'order_desc'))) && p() && e('15');
|
||||
r(count($product->getProjectListByProduct($productID = 1, 'wait', 'all', false, 'order_desc'))) && p() && e('5');
|
||||
|
||||
$product->app->loadClass('pager', $static = true);
|
||||
$pager = new pager(0, 50, 1);
|
||||
r(count($product->getProjectListByProduct($productID = 1, 'all', '', false, 'order_desc', $pager))) && p() && e('20');
|
||||
|
||||
$pager = new pager(0, 5, 1);
|
||||
r(count($product->getProjectListByProduct($productID = 1, 'all', '', false, 'order_desc', $pager))) && p() && e('5');
|
||||
|
||||
$product->app->user->admin = false;
|
||||
$product->app->user->view->projects = '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,48,49,50';
|
||||
r(count($product->getProjectListByProduct($productID = 1, 'all', '', true, 'order_desc'))) && p() && e('4');
|
||||
r(count($product->getProjectListByProduct($productID = 1, 'all', '0', true, 'order_desc'))) && p() && e('4');
|
||||
r(count($product->getProjectListByProduct($productID = 1, 'undone', '', true, 'order_desc'))) && p() && e('2');
|
||||
r(count($product->getProjectListByProduct($productID = 1, 'wait', 'all', true, 'order_desc'))) && p() && e('0');
|
||||
|
||||
$product->app->loadClass('pager', $static = true);
|
||||
$pager = new pager(0, 50, 1);
|
||||
r(count($product->getProjectListByProduct($productID = 1, 'all', '', true, 'order_desc', $pager))) && p() && e('4');
|
||||
|
||||
$pager = new pager(0, 5, 1);
|
||||
r(count($product->getProjectListByProduct($productID = 1, 'all', '', true, 'order_desc', $pager))) && p() && e('4');
|
||||
|
||||
@@ -3,55 +3,48 @@
|
||||
include dirname(__FILE__, 5) . "/test/lib/init.php";
|
||||
include dirname(__FILE__, 2) . '/product.class.php';
|
||||
|
||||
zdTable('product')->gen(50);
|
||||
zdTable('user')->gen(50);
|
||||
|
||||
$project = zdTable('project');
|
||||
$project->PM->range('user2,user3');
|
||||
$project->gen(50);
|
||||
|
||||
$task = zdTable('task');
|
||||
$task->project->range('1-20');
|
||||
$task->gen(100);
|
||||
|
||||
$team = zdTable('team');
|
||||
$team->root->range('1-50');
|
||||
$team->type->range('project');
|
||||
$team->account->range('admin,admin,user1,user1');
|
||||
$team->days->range('5');
|
||||
$team->hours->range('6,7');
|
||||
$team->gen(50);
|
||||
|
||||
$stakeholder = zdTable('stakeholder');
|
||||
$stakeholder->objectType->range('project');
|
||||
$stakeholder->objectID->range('1-50');
|
||||
$stakeholder->user->range('user4,user5');
|
||||
$stakeholder->type->range('inside');
|
||||
$stakeholder->from->range('');
|
||||
$stakeholder->gen(50);
|
||||
|
||||
$projectproduct = zdTable('projectproduct');
|
||||
$projectproduct->product->range('1-2');
|
||||
$projectproduct->project->range('1-50');
|
||||
$projectproduct->gen(50);
|
||||
|
||||
/**
|
||||
|
||||
title=productModel->getProjectStatsByProduct();
|
||||
title=productModel->getProjectStatsByProductTest();
|
||||
cid=1
|
||||
pid=1
|
||||
|
||||
测试获取产品1的 browseType为all的信息 >> 项目1;项目11
|
||||
测试获取产品1的 browseType为unclosed的信息 >> 0
|
||||
测试获取产品1的 browseType为unplan的信息 >> 0
|
||||
测试获取产品1的 browseType为allstory的信息 >> 0
|
||||
测试获取产品2的 browseType为all的信息 >> 项目2;项目12
|
||||
测试获取产品2的 browseType为assignedtome的信息 >> 0
|
||||
测试获取产品2的 browseType为openedbyme的信息 >> 0
|
||||
测试获取产品2的 browseType为reviewedbyme的信息 >> 0
|
||||
测试获取产品3的 browseType为all的信息 >> 项目3;项目13
|
||||
测试获取产品3的 browseType为reviewbyme的信息 >> 0
|
||||
测试获取产品3的 browseType为closedbyme的信息 >> 0
|
||||
测试获取产品3的 browseType为draftstory的信息 >> 0
|
||||
测试获取产品4的 browseType为all的信息 >> 项目4;项目14
|
||||
测试获取产品4的 browseType为activestory的信息 >> 0
|
||||
测试获取产品4的 browseType为changedstory的信息 >> 0
|
||||
测试获取产品5的 browseType为all的信息 >> 项目5;项目15
|
||||
测试获取产品5的 browseType为willclose的信息 >> 0
|
||||
测试获取产品5的 browseType为closedstory的信息 >> 0
|
||||
测试获取不存在产品的信息 >> 0
|
||||
|
||||
*/
|
||||
|
||||
$productIDList = array('1', '2', '3', '4', '5', '1000001');
|
||||
$browseType = array('unclosed', 'unplan', 'allstory', 'assignedtome', 'openedbyme', 'reviewedbyme', 'reviewbyme', 'closedbyme', 'draftstory', 'activestory', 'changedstory', 'willclose', 'closedstory');
|
||||
|
||||
$product = new productTest('admin');
|
||||
|
||||
r($product->getProjectStatsByProductTest($productIDList[0])) && p('11;21') && e('项目1;项目11'); // 测试获取产品1的 browseType为all的信息
|
||||
r($product->getProjectStatsByProductTest($productIDList[0], $browseType[0])) && p() && e('0'); // 测试获取产品1的 browseType为unclosed的信息
|
||||
r($product->getProjectStatsByProductTest($productIDList[0], $browseType[1])) && p() && e('0'); // 测试获取产品1的 browseType为unplan的信息
|
||||
r($product->getProjectStatsByProductTest($productIDList[0], $browseType[2])) && p() && e('0'); // 测试获取产品1的 browseType为allstory的信息
|
||||
r($product->getProjectStatsByProductTest($productIDList[1])) && p('12;22') && e('项目2;项目12'); // 测试获取产品2的 browseType为all的信息
|
||||
r($product->getProjectStatsByProductTest($productIDList[1], $browseType[3])) && p() && e('0'); // 测试获取产品2的 browseType为assignedtome的信息
|
||||
r($product->getProjectStatsByProductTest($productIDList[1], $browseType[4])) && p() && e('0'); // 测试获取产品2的 browseType为openedbyme的信息
|
||||
r($product->getProjectStatsByProductTest($productIDList[1], $browseType[5])) && p() && e('0'); // 测试获取产品2的 browseType为reviewedbyme的信息
|
||||
r($product->getProjectStatsByProductTest($productIDList[2])) && p('13;23') && e('项目3;项目13'); // 测试获取产品3的 browseType为all的信息
|
||||
r($product->getProjectStatsByProductTest($productIDList[2], $browseType[6])) && p() && e('0'); // 测试获取产品3的 browseType为reviewbyme的信息
|
||||
r($product->getProjectStatsByProductTest($productIDList[2], $browseType[7])) && p() && e('0'); // 测试获取产品3的 browseType为closedbyme的信息
|
||||
r($product->getProjectStatsByProductTest($productIDList[2], $browseType[8])) && p() && e('0'); // 测试获取产品3的 browseType为draftstory的信息
|
||||
r($product->getProjectStatsByProductTest($productIDList[3])) && p('14;24') && e('项目4;项目14'); // 测试获取产品4的 browseType为all的信息
|
||||
r($product->getProjectStatsByProductTest($productIDList[3], $browseType[9])) && p() && e('0'); // 测试获取产品4的 browseType为activestory的信息
|
||||
r($product->getProjectStatsByProductTest($productIDList[3], $browseType[10])) && p() && e('0'); // 测试获取产品4的 browseType为changedstory的信息
|
||||
r($product->getProjectStatsByProductTest($productIDList[4])) && p('15;25') && e('项目5;项目15'); // 测试获取产品5的 browseType为all的信息
|
||||
r($product->getProjectStatsByProductTest($productIDList[4], $browseType[11])) && p() && e('0'); // 测试获取产品5的 browseType为willclose的信息
|
||||
r($product->getProjectStatsByProductTest($productIDList[4], $browseType[12])) && p() && e('0'); // 测试获取产品5的 browseType为closedstory的信息
|
||||
r($product->getProjectStatsByProductTest($productIDList[5])) && p() && e('0'); // 测试获取不存在产品的信息
|
||||
r($product->getProjectStatsByProductTest($productID = 0, 'all', '', false, 'order_desc')) && p() && e('0');
|
||||
r($product->getProjectStatsByProductTest($productID = 1, 'all', '', false, 'order_desc')) && p('11:totalConsumed,totalEstimate,totalLeft,progress,teamCount') && e('15,30,10,60,1');
|
||||
r($product->getProjectStatsByProductTest($productID = 1, 'all', '', true, 'order_desc')) && p('17:totalConsumed,totalEstimate,totalLeft,progress,teamCount') && e('45,27,11,80,1');
|
||||
|
||||
@@ -699,28 +699,37 @@ class productTest
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get project stats by product.
|
||||
* 测试 getProjectStatsByProduct 方法。
|
||||
* Test getProjectStatsByProduct method.
|
||||
*
|
||||
* @param int $productID
|
||||
* @param string $browseType
|
||||
* @param int $productID
|
||||
* @param string $browseType
|
||||
* @param string $branch
|
||||
* @param bool $involved
|
||||
* @param string $order
|
||||
* @param object|null $pager
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getProjectStatsByProductTest($productID, $browseType = 'all')
|
||||
public function getProjectStatsByProductTest(int $productID, string $browseType = 'all', string $branch = '', bool $involved = false, string $orderBy = 'order_desc', object|null $pager = null): array
|
||||
{
|
||||
$objects = $this->objectModel->getProjectStatsByProduct($productID, $browseType);
|
||||
$objects = $this->objectModel->getProjectStatsByProduct($productID, $browseType, $branch, $involved, $orderBy, $pager);
|
||||
|
||||
foreach($objects as $object) $projects[$object->id] = $object->name;
|
||||
|
||||
|
||||
if(dao::isError())
|
||||
$projects = array();
|
||||
foreach($objects as $object)
|
||||
{
|
||||
return dao::getError();
|
||||
}
|
||||
else
|
||||
{
|
||||
return isset($projects) ? $projects : array();
|
||||
$project = new stdclass();
|
||||
$project->id = $object->id;
|
||||
$project->totalConsumed = $object->hours->totalConsumed;
|
||||
$project->totalEstimate = $object->hours->totalEstimate;
|
||||
$project->totalLeft = $object->hours->totalLeft;
|
||||
$project->progress = $object->hours->progress;
|
||||
$project->teamCount = $object->teamCount;
|
||||
$projects[$project->id] = $project;
|
||||
}
|
||||
|
||||
if(dao::isError()) return dao::getError();
|
||||
return $projects;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+12
-8
@@ -417,38 +417,42 @@ class productZen extends product
|
||||
* 追加创建信息,处理白名单、项目集字段,还有富文本内容处理。
|
||||
* Prepare data for create.
|
||||
*
|
||||
* @param form $data
|
||||
* @param form $data
|
||||
* @param string $acl
|
||||
* @param string $uid
|
||||
* @access protected
|
||||
* @return object
|
||||
*/
|
||||
protected function prepareCreateExtras(form $data): object
|
||||
protected function prepareCreateExtras(form $data, string $acl, string $uid = ''): object
|
||||
{
|
||||
$product = $data->setDefault('createdBy', $this->app->user->account)
|
||||
->setDefault('createdDate', helper::now())
|
||||
->setDefault('createdVersion', $this->config->version)
|
||||
->setIF($this->config->systemMode == 'light', 'program', (int)zget($this->config->global, 'defaultProgram', 0))
|
||||
->setIF($data->rawdata->acl == 'open', 'whitelist', '')
|
||||
->setIF($acl == 'open', 'whitelist', '')
|
||||
->stripTags($this->config->product->editor->create['id'], $this->config->allowedTags)
|
||||
->get();
|
||||
|
||||
return $this->loadModel('file')->processImgURL($product, $this->config->product->editor->create['id'], $data->rawdata->uid);
|
||||
return $this->loadModel('file')->processImgURL($product, $this->config->product->editor->create['id'], $uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理白名单和富文本内容。
|
||||
* Prepare data for edit.
|
||||
*
|
||||
* @param form $data
|
||||
* @param form $data
|
||||
* @param string $acl
|
||||
* @param string $uid
|
||||
* @access protected
|
||||
* @return object
|
||||
*/
|
||||
protected function prepareEditExtras(form $data): object
|
||||
protected function prepareEditExtras(form $data, string $acl, string $uid = ''): object
|
||||
{
|
||||
$product = fixer::input('post')->setIF($data->rawdata->acl == 'open', 'whitelist', '')
|
||||
$product = fixer::input('post')->setIF($acl == 'open', 'whitelist', '')
|
||||
->stripTags($this->config->product->editor->edit['id'], $this->config->allowedTags)
|
||||
->get();
|
||||
|
||||
return $this->loadModel('file')->processImgURL($product, $this->config->product->editor->edit['id'], zget($data->rawdata, 'uid', ''));
|
||||
return $this->loadModel('file')->processImgURL($product, $this->config->product->editor->edit['id'], $uid);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+67
-64
@@ -344,7 +344,7 @@ class programModel extends model
|
||||
->andWhere('deleted')->eq(0)
|
||||
->fetchGroup('execution', 'id');
|
||||
|
||||
$hours = $this->computeProgress($tasks);
|
||||
$hours = $this->computeProjectHours($tasks);
|
||||
$projectHours = $this->getProgressList();
|
||||
|
||||
/* Group data by product. */
|
||||
@@ -517,43 +517,97 @@ class programModel extends model
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute progress for project or execution.
|
||||
* 根据按照项目分组的任务,统计各个项目的工时和进度
|
||||
* Compute hours and progress for project or execution.
|
||||
*
|
||||
* @param array $tasks
|
||||
* @access public
|
||||
* @return void
|
||||
* @return array
|
||||
*/
|
||||
public function computeProgress($tasks)
|
||||
public function computeProjectHours(array $tasks): array
|
||||
{
|
||||
if(empty($tasks)) return array();
|
||||
|
||||
$hours = array();
|
||||
foreach($tasks as $projectID => $projectTasks)
|
||||
{
|
||||
/* Init hour. */
|
||||
$hour = new stdclass();
|
||||
$hour->totalConsumed = 0;
|
||||
$hour->totalEstimate = 0;
|
||||
$hour->totalLeft = 0;
|
||||
|
||||
/* Compute totalEstimate, totalConsumed, totalLeft to them. */
|
||||
foreach($projectTasks as $task)
|
||||
{
|
||||
$hour->totalConsumed += $task->consumed;
|
||||
$hour->totalEstimate += $task->estimate;
|
||||
if($task->status != 'cancel' and $task->status != 'closed') $hour->totalLeft += $task->left;
|
||||
if(strpos('|cancel|closed|done|', "|{$task->status}|") === false) $hour->totalLeft += $task->left;
|
||||
}
|
||||
$hours[$projectID] = $hour;
|
||||
}
|
||||
|
||||
foreach($hours as $hour)
|
||||
/* 计算进度。四舍五入totalEstimate, totalConsumed, totalLeft. */
|
||||
$progressList = $this->loadModel('project')->getWaterfallProgress(array_keys($hours));
|
||||
foreach($hours as $projectID => $hour)
|
||||
{
|
||||
$hour->totalEstimate = round($hour->totalEstimate, 1) ;
|
||||
$hour->totalConsumed = round($hour->totalConsumed, 1);
|
||||
$hour->totalLeft = round($hour->totalLeft, 1);
|
||||
$hour->totalReal = $hour->totalConsumed + $hour->totalLeft;
|
||||
$hour->progress = $hour->totalReal ? round($hour->totalConsumed / $hour->totalReal, 2) * 100 : 0;
|
||||
$hour->progress = zget($progressList, $projectID, ($hour->totalReal ? round($hour->totalConsumed / $hour->totalReal, 2) * 100 : 0)) ;
|
||||
}
|
||||
|
||||
return $hours;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将工时、团队人数、剩余任务数、团队成员等统计信息,追加到对应的项目中。
|
||||
* Append statistics fields to projects.
|
||||
*
|
||||
* @param array $projects
|
||||
* @param string $appendFields hours,teamCount,leftTasks,teamMembers
|
||||
* @param array $data array keys are hours, teams and leftTasks.
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function appendStatToProjects(array $projects, string $appendFields = '', array $data = array()): array
|
||||
{
|
||||
if(empty($projects)) return array();
|
||||
if(empty($appendFields) or empty($data)) return $projects;
|
||||
|
||||
$appendFields = explode(',', $appendFields);
|
||||
$emptyHour = json_decode(json_encode(array('totalEstimate' => 0, 'totalConsumed' => 0, 'totalLeft' => 0, 'progress' => 0)));
|
||||
/* Process projects. */
|
||||
$stats = array();
|
||||
foreach($projects as $projectID => $project)
|
||||
{
|
||||
if(helper::isZeroDate($project->end)) $project->end = '';
|
||||
|
||||
/* Judge whether the project is delayed. */
|
||||
if($project->status != 'done' and $project->status != 'closed' and $project->status != 'suspended')
|
||||
{
|
||||
$delay = empty($project->end) ? 0 : helper::diffDate(helper::today(), $project->end);
|
||||
if($delay > 0) $project->delay = $delay;
|
||||
}
|
||||
|
||||
/* Merge the hours. */
|
||||
if(in_array('hours', $appendFields))
|
||||
{
|
||||
$project->hours = $emptyHour;
|
||||
if(isset($data['hours'])) $project->hours = zget($data['hours'], $project->id, $emptyHour);
|
||||
}
|
||||
|
||||
/* Merge the team and left tasks. */
|
||||
if(in_array('teamCount', $appendFields)) $project->teamCount = isset($data['teams'][$project->id]) ? count($data['teams'][$project->id]) : 0;
|
||||
if(in_array('teamMembers', $appendFields)) $project->teamMembers = isset($data['teams'][$project->id]) ? array_keys($data['teams'][$project->id]) : array();
|
||||
if(in_array('leftTasks', $appendFields)) $project->leftTasks = isset($data['leftTasks'][$project->id]) ? $data['leftTasks'][$project->id]->tasks : '—';
|
||||
|
||||
$stats[$projectID] = $project;
|
||||
}
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get project list data.
|
||||
*
|
||||
@@ -1418,15 +1472,10 @@ class programModel extends model
|
||||
|
||||
/* Init vars. */
|
||||
$projects = $this->getProjectList($programID, $browseType, $queryID, $orderBy, $pager, $programTitle, $involved, $queryAll);
|
||||
|
||||
if(empty($projects)) return array();
|
||||
|
||||
$projectKeys = array_keys($projects);
|
||||
$stats = array();
|
||||
$hours = array();
|
||||
$emptyHour = array('totalEstimate' => 0, 'totalConsumed' => 0, 'totalLeft' => 0, 'progress' => 0);
|
||||
$leftTasks = array();
|
||||
$teamMembers = array();
|
||||
|
||||
$executions = $this->dao->select('id')->from(TABLE_EXECUTION)
|
||||
->where('deleted')->eq(0)
|
||||
@@ -1438,37 +1487,11 @@ class programModel extends model
|
||||
$tasks = $this->dao->select('id, project, estimate, consumed, `left`, status, closedReason, execution')
|
||||
->from(TABLE_TASK)
|
||||
->where('project')->in($projectKeys)
|
||||
->andWhere('execution')->in(array_keys($executions))
|
||||
->andWhere('parent')->lt(1)
|
||||
->andWhere('deleted')->eq(0)
|
||||
->fetchGroup('project', 'id');
|
||||
|
||||
/* Compute totalEstimate, totalConsumed, totalLeft. */
|
||||
foreach($tasks as $projectID => $projectTasks)
|
||||
{
|
||||
$hour = (object)$emptyHour;
|
||||
foreach($projectTasks as $task)
|
||||
{
|
||||
if(isset($executions[$task->execution]))
|
||||
{
|
||||
$hour->totalConsumed += $task->consumed;
|
||||
$hour->totalEstimate += $task->estimate;
|
||||
|
||||
if(strpos('cancel,closed', $task->status) === false) $hour->totalLeft += $task->left;
|
||||
}
|
||||
}
|
||||
$hours[$projectID] = $hour;
|
||||
}
|
||||
|
||||
/* Compute totalReal and progress. */
|
||||
$progressList = $this->loadModel('project')->getWaterfallProgress(array_keys($hours));
|
||||
foreach($hours as $projectID => $hour)
|
||||
{
|
||||
$hour->totalEstimate = round($hour->totalEstimate, 1) ;
|
||||
$hour->totalConsumed = round($hour->totalConsumed, 1);
|
||||
$hour->totalLeft = round($hour->totalLeft, 1);
|
||||
$hour->totalReal = $hour->totalConsumed + $hour->totalLeft;
|
||||
$hour->progress = $projects[$projectID]->model == 'waterfall' ? $progressList[$projectID] : ($hour->totalReal ? round($hour->totalConsumed / $hour->totalReal, 2) * 100 : 0);
|
||||
}
|
||||
$hours = $this->computeProjectHours($tasks);
|
||||
|
||||
/* Get the number of left tasks. */
|
||||
if($this->cookie->projectType and $this->cookie->projectType == 'bycard')
|
||||
@@ -1489,30 +1512,10 @@ class programModel extends model
|
||||
->andWhere('t2.deleted')->eq(0)
|
||||
->fetchGroup('root', 'account');
|
||||
|
||||
/* Process projects. */
|
||||
foreach($projects as $key => $project)
|
||||
{
|
||||
if($project->end == '0000-00-00') $project->end = '';
|
||||
$stats = $this->appendStatToProjects($projects, 'hours,teamCount,teamMembers,leftTasks', array('hours' => $hours, 'teams' => $teamMembers, 'leftTasks' => $leftTasks));
|
||||
/* Convert predefined HTML entities to characters. */
|
||||
foreach($stats as $project) $project->name = htmlspecialchars_decode($project->name, ENT_QUOTES);
|
||||
|
||||
/* Judge whether the project is delayed. */
|
||||
if($project->status != 'done' and $project->status != 'closed' and $project->status != 'suspended')
|
||||
{
|
||||
$delay = helper::diffDate(helper::today(), $project->end);
|
||||
if($delay > 0) $project->delay = $delay;
|
||||
}
|
||||
|
||||
/* Process the hours. */
|
||||
$project->hours = isset($hours[$project->id]) ? $hours[$project->id] : (object)$emptyHour;
|
||||
|
||||
$project->teamCount = isset($teamMembers[$project->id]) ? count($teamMembers[$project->id]) : 0;
|
||||
$project->leftTasks = isset($leftTasks[$project->id]) ? $leftTasks[$project->id]->tasks : '—';
|
||||
$project->teamMembers = isset($teamMembers[$project->id]) ? array_keys($teamMembers[$project->id]) : array();
|
||||
|
||||
/* Convert predefined HTML entities to characters. */
|
||||
$project->name = htmlspecialchars_decode($project->name, ENT_QUOTES);
|
||||
|
||||
$stats[$key] = $project;
|
||||
}
|
||||
return $stats;
|
||||
}
|
||||
|
||||
@@ -1778,7 +1781,7 @@ class programModel extends model
|
||||
$actionsMap[] = $item;
|
||||
}
|
||||
|
||||
if($canSuspendProgram or ($canClose && $program->status != 'doing') or ($canActivateProgram and $program->status != 'closed'))
|
||||
if($canSuspendProgram or ($canCloseProgram and $program->status != 'doing') or ($canActivateProgram and $program->status != 'closed'))
|
||||
{
|
||||
$other = new stdclass();
|
||||
$other->name = 'other';
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
include dirname(__FILE__, 5) . "/test/lib/init.php";
|
||||
|
||||
zdTable('project')->gen(50);
|
||||
|
||||
$task = zdTable('task');
|
||||
$task->project->range('1-20');
|
||||
$task->gen(100);
|
||||
|
||||
/**
|
||||
|
||||
title=测试 programModel::create();
|
||||
cid=1
|
||||
pid=1
|
||||
|
||||
*/
|
||||
|
||||
global $tester;
|
||||
$tester->loadModel('program');
|
||||
|
||||
$projectID = 11;
|
||||
$hour = new stdclass();
|
||||
$hour->totalConsumed = 15;
|
||||
$hour->totalEstimate = 30;
|
||||
$hour->totalLeft = 10;
|
||||
$hour->progress = 60;
|
||||
$hours[$projectID] = $hour;
|
||||
|
||||
$projects = $tester->program->dao->select('*')->from(TABLE_PROJECT)->where('id')->eq($projectID)->fetchAll('id');
|
||||
$teams = $tester->program->dao->select('t1.root,t1.account')->from(TABLE_TEAM)->alias('t1')
|
||||
->leftJoin(TABLE_USER)->alias('t2')->on('t1.account=t2.account')
|
||||
->where('t1.root')->eq($projectID)
|
||||
->andWhere('t1.type')->eq('project')
|
||||
->andWhere('t2.deleted')->eq(0)
|
||||
->fetchGroup('root', 'account');
|
||||
$leftTasks = $tester->program->dao->select('t2.parent as project, count(*) as tasks')->from(TABLE_TASK)->alias('t1')
|
||||
->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id')
|
||||
->where('t1.project')->eq($projectID)
|
||||
->andWhere('t1.status')->notIn('cancel,closed')
|
||||
->groupBy('t2.parent')
|
||||
->fetchAll('project');
|
||||
|
||||
|
||||
r($tester->program->appendStatToProjects(array())) && p() && e('0'); //不传入任何数据
|
||||
r($tester->program->appendStatToProjects($projects)) && p('11:id') && e('11'); //只传入项目数据
|
||||
|
||||
$stats = $tester->program->appendStatToProjects($projects, 'hours');
|
||||
r(isset($stats[$projectID]->hours)) && p() && e('0'); //只传入项目数据和hours追加字段
|
||||
$stats = $tester->program->appendStatToProjects($projects, 'teamCount');
|
||||
r(isset($stats[$projectID]->teamCount)) && p() && e('0'); //只传入项目数据和teamCount追加字段
|
||||
$stats = $tester->program->appendStatToProjects($projects, 'teamMembers');
|
||||
r(isset($stats[$projectID]->teamMembers)) && p() && e('0'); //只传入项目数据和teamMembers追加字段
|
||||
$stats = $tester->program->appendStatToProjects($projects, 'leftTasks');
|
||||
r(isset($stats[$projectID]->leftTasks)) && p() && e('0'); //只传入项目数据和leftTasks追加字段
|
||||
|
||||
$stats = $tester->program->appendStatToProjects($projects, 'hours,teamCount,teamMembers,leftTasks', array('hours' => $hours, 'teams' => $teams, 'leftTasks' => $leftTasks));
|
||||
$stat = $stats[$projectID];
|
||||
r(isset($stat->hours)) && p() && e('1'); //正常传入数据,检查hours字段
|
||||
r(isset($stat->teamCount)) && p() && e('1'); //正常传入数据,检查teamCount字段
|
||||
r(isset($stat->teamMembers)) && p() && e('1'); //正常传入数据,检查teamMembers字段
|
||||
r(isset($stat->leftTasks)) && p() && e('1'); //正常传入数据,检查leftTasks字段
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
include dirname(__FILE__, 5) . "/test/lib/init.php";
|
||||
|
||||
/**
|
||||
|
||||
title=测试 programModel::create();
|
||||
cid=1
|
||||
pid=1
|
||||
|
||||
传入任务数组,返回计算后的总消耗 >> 13
|
||||
传入任务数组,返回计算后的总预计 >> 15
|
||||
传入任务数组,返回计算后的总剩余 >> 7
|
||||
传入任务数组,返回计算后的总工时 >> 20
|
||||
传入任务数组,返回计算后的进度 >> 65
|
||||
|
||||
*/
|
||||
|
||||
global $tester;
|
||||
$tester->loadModel('program');
|
||||
$task1 = new stdclass();
|
||||
$task1->consumed = 2;
|
||||
$task1->left = 2;
|
||||
$task1->estimate = 2;
|
||||
$task1->status = 'active';
|
||||
|
||||
$task2 = new stdclass();
|
||||
$task2->consumed = 4;
|
||||
$task2->left = 5;
|
||||
$task2->estimate = 6;
|
||||
$task2->status = 'active';
|
||||
|
||||
$task3 = new stdclass();
|
||||
$task3->consumed = 7;
|
||||
$task3->left = 7;
|
||||
$task3->estimate = 7;
|
||||
$task3->status = 'closed';
|
||||
|
||||
$tasks[] = $task1;
|
||||
$tasks[] = $task2;
|
||||
$tasks[] = $task3;
|
||||
|
||||
$projectTasks[1] = $tasks;
|
||||
|
||||
$hours = $tester->program->computeProgress($projectTasks);
|
||||
|
||||
r($hours) && p('1:totalConsumed') && e('13'); // 传入任务数组,返回计算后的总消耗
|
||||
r($hours) && p('1:totalEstimate') && e('15'); // 传入任务数组,返回计算后的总预计
|
||||
r($hours) && p('1:totalLeft') && e('7'); // 传入任务数组,返回计算后的总剩余
|
||||
r($hours) && p('1:totalReal') && e('20'); // 传入任务数组,返回计算后的总工时
|
||||
r($hours) && p('1:progress') && e('65'); // 传入任务数组,返回计算后的进度
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
include dirname(__FILE__, 5) . "/test/lib/init.php";
|
||||
|
||||
zdTable('project')->gen(50);
|
||||
|
||||
$task = zdTable('task');
|
||||
$task->project->range('1-20');
|
||||
$task->gen(100);
|
||||
|
||||
/**
|
||||
|
||||
title=测试 programModel::create();
|
||||
cid=1
|
||||
pid=1
|
||||
|
||||
*/
|
||||
|
||||
global $tester;
|
||||
$tester->loadModel('program');
|
||||
$tasks = $tester->program->dao->select('id, project, estimate, consumed, `left`, status, closedReason, execution')
|
||||
->from(TABLE_TASK)
|
||||
->where('project')->eq('11')
|
||||
->andWhere('parent')->lt(1)
|
||||
->andWhere('deleted')->eq(0)
|
||||
->fetchGroup('project', 'id');
|
||||
|
||||
$hours = $tester->program->computeProjectHours($tasks);
|
||||
|
||||
r($tester->program->computeProjectHours(array())) && p() && e('0');
|
||||
r($tester->program->computeProjectHours($tasks)) && p('11:totalConsumed,totalEstimate,totalLeft,progress') && e('15,30,10,60');
|
||||
@@ -150,6 +150,7 @@ class programplanModel extends model
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取甘特图页面数据。
|
||||
* Get gantt data.
|
||||
*
|
||||
* @param int $executionID
|
||||
@@ -160,27 +161,14 @@ class programplanModel extends model
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDataForGantt($executionID, $productID, $baselineID = 0, $selectCustom = '', $returnJson = true)
|
||||
public function getDataForGantt(int $executionID, int $productID, int $baselineID = 0, string $selectCustom = '', bool $returnJson = true): string
|
||||
{
|
||||
$this->loadModel('stage');
|
||||
$this->loadModel('execution');
|
||||
|
||||
$plans = $this->getStage($executionID, $productID, 'all', 'order');
|
||||
if($baselineID)
|
||||
{
|
||||
$baseline = $this->loadModel('cm')->getByID($baselineID);
|
||||
$oldData = json_decode($baseline->data);
|
||||
$oldPlans = $oldData->stage;
|
||||
foreach($oldPlans as $id => $oldPlan)
|
||||
{
|
||||
if(!isset($plans[$id])) continue;
|
||||
$plans[$id]->version = $oldPlan->version;
|
||||
$plans[$id]->name = $oldPlan->name;
|
||||
$plans[$id]->milestone = $oldPlan->milestone;
|
||||
$plans[$id]->begin = $oldPlan->begin;
|
||||
$plans[$id]->end = $oldPlan->end;
|
||||
}
|
||||
}
|
||||
|
||||
if($baselineID) $plans = $this->getBaselineData($baselineID, $plans);
|
||||
|
||||
$today = helper::today();
|
||||
$datas = array();
|
||||
@@ -380,6 +368,33 @@ class programplanModel extends model
|
||||
return $returnJson ? json_encode($datas) : $datas;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取基线数据。
|
||||
* Get baseline data.
|
||||
*
|
||||
* @param int $baselineID
|
||||
* @param array $plans
|
||||
* @access private
|
||||
* @return array
|
||||
*/
|
||||
private function getBaselineData(int $baselineID, array $plans): array
|
||||
{
|
||||
$baseline = $this->loadModel('cm')->getByID($baselineID);
|
||||
$oldData = json_decode($baseline->data);
|
||||
$oldPlans = $oldData->stage;
|
||||
foreach($oldPlans as $id => $oldPlan)
|
||||
{
|
||||
if(!isset($plans[$id])) continue;
|
||||
$plans[$id]->version = $oldPlan->version;
|
||||
$plans[$id]->name = $oldPlan->name;
|
||||
$plans[$id]->milestone = $oldPlan->milestone;
|
||||
$plans[$id]->begin = $oldPlan->begin;
|
||||
$plans[$id]->end = $oldPlan->end;
|
||||
}
|
||||
|
||||
return $plans;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get gantt data group by assigned.
|
||||
*
|
||||
@@ -617,11 +632,11 @@ class programplanModel extends model
|
||||
/**
|
||||
* Process plans.
|
||||
*
|
||||
* @param int $plans
|
||||
* @param array $plans
|
||||
* @access public
|
||||
* @return object
|
||||
*/
|
||||
public function processPlans($plans)
|
||||
private function processPlans(array $plans)
|
||||
{
|
||||
foreach($plans as $planID => $plan) $plans[$planID] = $this->processPlan($plan);
|
||||
return $plans;
|
||||
|
||||
+10
-8
@@ -238,12 +238,12 @@ class todoModel extends model
|
||||
/**
|
||||
* Get todo list of a user.
|
||||
*
|
||||
* @param string $type
|
||||
* @param string $account
|
||||
* @param string $status all|today|thisweek|lastweek|before, or a date.
|
||||
* @param int $limit
|
||||
* @param object $pager
|
||||
* @param string $orderBy
|
||||
* @param string $type
|
||||
* @param string $account
|
||||
* @param string|array $status all|today|thisweek|lastweek|before, or a date.
|
||||
* @param int $limit
|
||||
* @param ?object $pager
|
||||
* @param string $orderBy
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
@@ -362,11 +362,12 @@ class todoModel extends model
|
||||
if(!isset($validUsers[$todo->account])) continue;
|
||||
|
||||
$todo->config = json_decode($todo->config);
|
||||
|
||||
$begin = $todo->config->begin;
|
||||
$end = $todo->config->end;
|
||||
$beforeDays = (int)$todo->config->beforeDays;
|
||||
if(!empty($beforeDays) && $beforeDays > 0) $begin = date('Y-m-d', strtotime("$begin -{$beforeDays} days"));
|
||||
if($today < $begin or (!empty($end) && $today > $end)) continue;
|
||||
if($today < $begin || (!empty($end) && $today > $end)) continue;
|
||||
|
||||
$newTodo = $this->todoTao->buildCycleTodo($todo);
|
||||
if(isset($todo->assignedTo) && $todo->assignedTo) $newTodo->assignedDate = $now;
|
||||
@@ -560,7 +561,8 @@ class todoModel extends model
|
||||
* 根据待办ID更新待办数据。
|
||||
* Update todo data by id.
|
||||
*
|
||||
* @param array $todoIdList
|
||||
* @param int $todoID
|
||||
* @param object $todo
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
|
||||
+5
-8
@@ -279,7 +279,7 @@ class todoTao extends todoModel
|
||||
$todo->account = $this->app->user->account;
|
||||
|
||||
$todo->date = $formData->rawdata->date;
|
||||
if($formData->rawdata->switchDate == 'on' || $formData->rawdata->date == false) $todo->date = '2030-01-01';
|
||||
if($formData->rawdata->switchDate == 'on' || !$formData->rawdata->date) $todo->date = '2030-01-01';
|
||||
|
||||
$todo->type = $todos->types[$loop];
|
||||
$todo->pri = $todos->pris[$loop];
|
||||
@@ -368,8 +368,8 @@ class todoTao extends todoModel
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置周期待办数据。
|
||||
* Set cycle todo data.
|
||||
* 设置周期待办数据
|
||||
*
|
||||
* @param object $todoData
|
||||
* @access private
|
||||
@@ -397,12 +397,12 @@ class todoTao extends todoModel
|
||||
if($todoData->config['type'] == 'week')
|
||||
{
|
||||
unset($todoData->config['day'], $todoData->config['month']);
|
||||
$todoData->config['week'] = join(',', $todoData->config['week']);
|
||||
$todoData->config['week'] = implode(',', $todoData->config['week']);
|
||||
}
|
||||
if($todoData->config['type'] == 'month')
|
||||
{
|
||||
unset($todoData->config['day'], $todoData->config['week']);
|
||||
$todoData->config['month'] = join(',', $todoData->config['month']);
|
||||
$todoData->config['month'] = implode(',', $todoData->config['month']);
|
||||
}
|
||||
|
||||
if($todoData->config['beforeDays'] and !validater::checkInt($todoData->config['beforeDays']))
|
||||
@@ -476,10 +476,7 @@ class todoTao extends todoModel
|
||||
if($todo->type == 'opportunity') $todo->name = $this->dao->findByID($todo->objectID)->from(TABLE_OPPORTUNITY)->fetch('name');
|
||||
}
|
||||
|
||||
if($this->config->edition != 'open')
|
||||
{
|
||||
if($todo->type == 'feedback') $todo->name = $this->dao->findByID($todo->objectID)->from(TABLE_FEEDBACK)->fetch('title');
|
||||
}
|
||||
if($this->config->edition != 'open' && $todo->type == 'feedback') $todo->name = $this->dao->findByID($todo->objectID)->from(TABLE_FEEDBACK)->fetch('title');
|
||||
|
||||
return $todo;
|
||||
}
|
||||
|
||||
@@ -1,39 +1,23 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
include dirname(__FILE__, 5) . "/test/lib/init.php";
|
||||
include dirname(__FILE__, 5) . '/test/lib/init.php';
|
||||
include dirname(__FILE__, 2) . '/todo.class.php';
|
||||
su('admin');
|
||||
|
||||
function initData()
|
||||
{
|
||||
zdTable('todo')->config('createbycycle')->gen(3);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
title=测试 todoModel->createByCycle();
|
||||
cid=1
|
||||
pid=1
|
||||
|
||||
>> + days
|
||||
测试创建周期日待办 >> 11
|
||||
测试创建周期周待办 >> 9
|
||||
测试创建周期月待办 >> `%d`
|
||||
|
||||
*/
|
||||
|
||||
$days = 31;
|
||||
$begin = call_user_func_array('date', array('Y-m-d'));
|
||||
$end = call_user_func_array('date', array('Y-m-d', strtotime("+$days days")));
|
||||
$todo1 = new stdclass();
|
||||
$todo1->name = 'cycle生成的待办1';
|
||||
$todo1->config = '{"specify":{"month":"0","day":"1"},"day":"1","type":"day","beforeDays":11,"end":"' . $end . '","begin":"' . $begin . '"}';
|
||||
|
||||
$todo2 = new stdclass();
|
||||
$todo2->name = 'cycle生成的待办2';
|
||||
$todo2->config = '{"specify":{"month":"0","day":"1"},"week":"2,4,7","type":"week","beforeDays":31,"end":"' . $end . '","begin":"' . $begin . '"}';
|
||||
|
||||
$todo3 = new stdclass();
|
||||
$todo3->name = 'cycle生成的待办3';
|
||||
$todo3->config = '{"specify":{"month":"0","day":"1"},"month":"1,8,13,15,27,29,30","type":"month","beforeDays":301,"end":"' . $end . '","begin":"' . $begin . '"}';
|
||||
initData();
|
||||
|
||||
$todo = new todoTest();
|
||||
|
||||
r($todo->createByCycleTest($todo1)) && p() && e('11'); // 测试创建周期日待办
|
||||
r($todo->createByCycleTest($todo2)) && p() && e('9'); // 测试创建周期周待办
|
||||
r($todo->createByCycleTest($todo3)) && p() && e('`%d`'); // 测试创建周期月待办
|
||||
r($todo->createByCycleTest()) && p() && e('1');
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
title: Create cycle todos for todoModel::createCycle UTC.
|
||||
desc: Create cycle todos for todoModel::createCycle UTC.
|
||||
author: xushenjie
|
||||
version: 1.0.0
|
||||
|
||||
fields:
|
||||
- field: account
|
||||
range: 'admin'
|
||||
- field: name
|
||||
range: '`周期待办:提前1天,间隔一天生成`, `周期待办:每月的每天,提前1天生成`, `周期待办:每周的每天,提前1天生成`'
|
||||
- field: date
|
||||
range: 1-5
|
||||
prefix: '2023-05-0'
|
||||
- field: type
|
||||
range: 'cycle'
|
||||
- field: cycle
|
||||
range: 1
|
||||
- field: begin
|
||||
range: 0800-1100
|
||||
- field: end
|
||||
range: 1800-2000
|
||||
- field: assignedTo
|
||||
range: 'admin'
|
||||
- field: desc
|
||||
range: '周期待办描述'
|
||||
- field: status
|
||||
range: 'doing'
|
||||
- field: objectID
|
||||
range: 0
|
||||
- field: assignedBy
|
||||
range: 'admin'
|
||||
- field: pri
|
||||
range: 1-4
|
||||
- field: config
|
||||
range: '`{"specify":{"month":"0","day":"1"},"day":"1","type":"day","beforeDays":1,"end":"2030-05-31","begin":"2023-05-01"}`, `{"specify":{"month":"0","day":"1"},"month":"1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31","type":"month","beforeDays":1,"end":"2030-05-31","begin":"2023-05-01"}`, `{"specify":{"month":"0","day":"1"},"week":"1,2,3,4,5,6,0","type":"week","beforeDays":1,"end":"2030-05-31","begin":"2023-05-01"}`'
|
||||
@@ -243,36 +243,22 @@ class todoTest
|
||||
return $object ? 1 : 2;
|
||||
}
|
||||
|
||||
public function createByCycleTest($todo)
|
||||
/**
|
||||
* Create by cycle test.
|
||||
*
|
||||
* @access public
|
||||
* @return int
|
||||
*/
|
||||
public function createByCycleTest()
|
||||
{
|
||||
$todo->cycle = '1';
|
||||
$todo->type = 'cycle';
|
||||
$todo->pri = 3;
|
||||
$todo->desc = '';
|
||||
$todo->status = 'wait';
|
||||
$todo->begin = '0830';
|
||||
$todo->end = '0900';
|
||||
$todo->account = 'admin';
|
||||
$todo->objectID = '0';
|
||||
$todo->vision = 'rnd';
|
||||
$todo->assignedTo = 'admin';
|
||||
$todo->assignedBy = 'admin';
|
||||
$todo->assignedDate = date('Y-m-d', time());
|
||||
$todo->date = date('Y-m-d', time());
|
||||
|
||||
$todo->config = str_replace('2022-03-23', date('Y-m-d', time()), $todo->config);
|
||||
$todoList = $this->objectModel->getValidCycleList();
|
||||
$this->objectModel->createByCycle($todoList);
|
||||
|
||||
global $tester;
|
||||
$tester->dao->insert(TABLE_TODO)->data($todo)->autoCheck()->exec();
|
||||
$todoID = $tester->dao->lastInsertID();
|
||||
$todoIDList = array_keys($todoList);
|
||||
$count = $tester->dao->select('count(`id`) as count')->from(TABLE_TODO)->where('objectID')->in($todoIDList)->andWhere('deleted')->eq('0')->fetch('count');
|
||||
|
||||
$this->objectModel->createByCycle(array($todoID => $todo));
|
||||
|
||||
$objects = $tester->dao->select('id')->from(TABLE_TODO)->where('objectID')->eq($todoID)->andWhere('deleted')->eq('0')->fetchAll();
|
||||
|
||||
if(dao::isError()) return dao::getError();
|
||||
|
||||
return count($objects);
|
||||
return dao::isError() ? 0 :1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user