From 1ea0bb47607b3da3e2823ce4c6231919f667af32 Mon Sep 17 00:00:00 2001 From: liumengyi Date: Thu, 13 Jan 2022 10:35:04 +0800 Subject: [PATCH 1/3] * Finish task #47334.Add card view. --- module/execution/css/kanban.css | 54 ++- module/execution/js/kanban.js | 461 ++++++++++++++++++-------- module/execution/view/kanban.html.php | 13 +- module/kanban/model.php | 79 ++++- 4 files changed, 420 insertions(+), 187 deletions(-) diff --git a/module/execution/css/kanban.css b/module/execution/css/kanban.css index ec349e5541..dc4cba5734 100644 --- a/module/execution/css/kanban.css +++ b/module/execution/css/kanban.css @@ -58,38 +58,8 @@ .region .kanban-lane-items {overflow: auto; padding-bottom: 10px;} .region .kanban-item {position: relative} -.region .kanban-item > .kanban-card > .title:hover {color: #2272eb} -.region .kanban-item > .kanban-card > .info {margin-top: 5px; position: relative;} -.region .kanban-item > .kanban-card > .info > .pri {height: 14px; border-width: 1px; font-size: 12px; line-height: 12px; min-width: 14px; padding: 0 1px;} -.region .kanban-item > .kanban-card > .info > .estimate{margin-left: 4px; color: #999; background-color: rgb(255, 255, 255); padding: 3px 0px} -.region .kanban-item > .kanban-card > .info > .time {margin-left: 4px; font-size: 12px; background: rgb(242, 242, 242)} -.region .kanban-item > .kanban-card > .info > .user {position: absolute; right: 0; top: -2px} -.region .kanban-item > .kanban-card > .info > .user > .avatar {display: inline-block; width: 24px; height: 24px; line-height: 24px; margin-right: 0px; margin-left: 2px} -.region .kanban-item > .kanban-card > .info > .user > span {display: inline-block; color: transparent; width: 2px; height: 2px; background-color: #8990a2; position: sticky; border-radius: 50%; top: 3px; margin: 0 7px;} -.region .kanban-item > .kanban-card > .info > .user > span:before, -.region .kanban-item > .kanban-card > .info > .user > span:after {content: ''; display: block; position: absolute; width: 2px; height: 2px; background-color: #8990a2; top: 0; border-radius: 50%} -.region .kanban-item > .kanban-card > .info > .user > span:before {left: -4px;} -.region .kanban-item > .kanban-card > .info > .user > span:after {right: -4px;} -.region .kanban-item > .kanban-card > .actions > a {display: block; float: left; width: 20px; height: 20px; line-height: 20px; text-align: center; border-radius: 4px; opacity: .7;} -.region .kanban-item > .kanban-card > .actions > a:hover {background-color: rgba(0,0,0,.075); opacity: 1;} -.region .kanban-item:hover > .actions {opacity: 1;} -.region .kanban-item .has-color .title, -.region .kanban-item .has-color .actions .icon-more-v, -.region .kanban-item .has-color .info > .label-pri, -.region .kanban-item .has-color .info > .estimate, -.region .kanban-item .has-color .info > .user .lable-teamSumCount, -.region .kanban-item .has-color .info > .label-light {color: #FFFFFF;} -.region .kanban-item .has-color .info > .label-pri {border-color: #FFFFFF;} -.region .kanban-item .item-more {position: absolute; right: 6px; top: 0px;} .kanban-card {height: auto !important; padding: 8px 14px !important} -.kanban-card > .title {word-break: break-all;} -.kanban-card > .info {position: relative; margin-top: 5px} -.kanban-card > .info > .info + .info {margin-left: 8px} -.kanban-card:hover > .actions {opacity: 1;} -.kanban-card > .actions {position: absolute; top: 4px; right: 4px; opacity: 0;} -.kanban-card > .actions > a {display: block; float: left; width: 20px; height: 20px; line-height: 20px; text-align: center; border-radius: 4px; opacity: .7;} -.kanban-card > .actions > a:hover {background-color: rgba(0,0,0,.075); opacity: 1;} .cardcolor {width:40px; height: 14px; float: left; margin-left: 5px; margin-right: 5px; margin-top: 2px; border-radius: 2px;} #cardcolormenu {padding:2px 2px; width: 100px;} @@ -109,3 +79,27 @@ #kanbanScaleControl > .input-group-btn:last-child > .btn {border-radius: 0 16px 16px 0; border-left-color: transparent;} #kanbanScaleControl > .input-group-btn > .btn:hover {border-color: #b8bfce;} #kanbanActionMenu {top: 24px; right: 28px;} + +.kanban + .kanban {margin-top: 15px} +.kanban-card > .title {display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis} +.kanban-card > .infos {position: relative; margin-top: 5px} +.kanban-card > .infos > .info + .info {margin-left: 8px} +.kanban-card > .infos > .info-id, +.kanban-card > .infos > .info-deadline, +.kanban-card > .infos > .info-estimate {font-size: 12px; position: relative; top: 2px} +.kanban-card > .infos > .label-pri {min-width: 16px; line-height: 14px; height: 16px; padding: 0} +.kanban-card > .infos > .label-severity {transform: scale(.75)} +.kanban-card > .infos > .avatar {position: absolute; right: 0; top: 0} +.kanban-card > .actions {position: absolute; top: 4px; right: 4px; opacity: 0;} +.kanban-card:hover > .actions {opacity: 1;} +.kanban-card > .actions > a {display: block; float: left; width: 20px; height: 20px; line-height: 20px; text-align: center; border-radius: 4px; opacity: .7;} +.kanban-card > .actions > a:hover {background-color: rgba(0,0,0,.075); opacity: 1;} + +#kanban .kanban-card[data-scale-size="4"] {padding: 8px;} +#kanban .kanban-card[data-scale-size="3"] {padding: 3px 4px;} +#kanban .kanban-card[data-scale-size="3"] > .title {white-space: normal; max-height: 100%;} +#kanban .kanban-card[data-scale-size="2"] {padding: 3px 4px; overflow: hidden;} +#kanban .kanban-card[data-scale-size="2"] > .title {display: inline; line-height: 18px; white-space: normal; max-height: 100%; word-break: break-all;} +#kanban .kanban-card[data-scale-size="2"] > .infos {display: inline-block; height: 18px; vertical-align: top; margin: 0;} +#kanban .kanban-card[data-scale-size="2"] > .infos > .info-pri {transform: scale(.8); vertical-align: top;} +#kanban .kanban-card[data-scale-size="2"] > .infos > .avatar {display: inline-block; transform: scale(.8); position: relative; top: -2px; vertical-align: top; margin-right: -1px;} diff --git a/module/execution/js/kanban.js b/module/execution/js/kanban.js index 9094d0c3cf..839fed12c9 100644 --- a/module/execution/js/kanban.js +++ b/module/execution/js/kanban.js @@ -11,7 +11,7 @@ function changeKanbanScaleSize(newScaleSize) $('#kanbanScaleControl .btn[data-type="+"]').attr('disabled', newScaleSize >= 4 ? 'disabled' : null); $('#kanbanScaleControl .btn[data-type="-"]').attr('disabled', newScaleSize <= 1 ? 'disabled' : null); - $('#kanbans').children('.kanban').each(function() + $('#kanban').children('.region').children("div[id^='kanban']").each(function() { var kanban = $(this).data('zui.kanban'); if(!kanban) return; @@ -21,6 +21,12 @@ function changeKanbanScaleSize(newScaleSize) return newScaleSize; } +/** Get card height */ +function getCardHeight() +{ + return [59, 59, 62, 62, 47][window.kanbanScaleSize]; +} + $('#type').change(function() { var type = $('#type').val(); @@ -70,7 +76,6 @@ function createColumnMenu(options) var items = []; if(privs.includes('setColumn')) items.push({label: kanbanLang.editColumn, icon: 'edit', url: createLink('kanban', 'setColumn', 'columnID=' + column.id, '', 'true'), className: 'iframe', attrs: {'data-toggle': 'modal'}}); if(privs.includes('setWIP')) items.push({label: kanbanLang.setWIP, icon: 'alert', url: createLink('kanban', 'setWIP', 'columnID=' + column.id), className: 'iframe', attrs: {'data-toggle': 'modal', 'data-width' : '500px'}}); - if(privs.includes('deleteColumn') && column.$kanbanData.columns.length > 1) items.push({label: kanbanLang.deleteColumn, icon: 'trash', url: createLink('kanban', 'deleteColumn', 'columnID=' + column.id), attrs: {'target': 'hiddenwin'}}); var bounds = options.$trigger[0].getBoundingClientRect(); items.$options = {x: bounds.right, y: bounds.top}; @@ -126,35 +131,327 @@ function handleFinishDrop() $('.kanban').find('.can-drop-here').removeClass('can-drop-here'); } -/** +/* Define drag and drop rules */ +if(!window.kanbanDropRules) +{ + window.kanbanDropRules = + { + story: + { + backlog: ['ready'], + ready: ['backlog'], + }, + bug: + { + 'unconfirmed': ['confirmed', 'fixing', 'fixed'], + 'confirmed': ['fixing', 'fixed'], + 'fixing': ['fixed'], + 'fixed': ['testing', 'tested', 'fixing'], + 'testing': ['tested', 'closed', 'fixing'], + 'tested': ['closed', 'fixing'], + 'closed': ['fixing'], + }, + task: + { + 'wait': ['developing', 'developed', 'canceled', 'closed'], + 'developing': ['developed', 'pause'], + 'developed': ['canceled', 'closed'], + 'pause': ['developing'], + 'canceled': ['developing'], + 'closed': ['developing'], + } + } +} + +/* * Find drop columns * @param {JQuery} $element Drag element * @param {JQuery} $root Dnd root element */ function findDropColumns($element, $root) { - var $task = $element; - var task = $task.data('task'); - //var status = task.status; - var $col = $task.closest('.kanban-col'); - var col = $col.data(); - var lane = $col.closest('.kanban-lane').data('lane'); - var allStatusCanChange = statusChangeMap[status]; + var $col = $element.closest('.kanban-col'); + var col = $col.data(); + var kanbanID = $root.data('id'); + var kanbanRules = window.kanbanDropRules ? window.kanbanDropRules[kanbanID] : null; - hideKanbanAction(); + if(!kanbanRules) return $root.find('.kanban-lane-col:not([data-type="' + col.type + '"])'); - return $root.find('.kanban-lane-col:not([data-type="EMPTY"],[data-type=""])').filter(function() + var colRules = kanbanRules[col.type]; + var lane = $col.closest('.kanban-lane').data('lane'); + return $root.find('.kanban-lane-col').filter(function() { + if(!colRules) return false; + if(colRules === true) return true; + var $newCol = $(this); var newCol = $newCol.data(); + if(newCol.id === col.id) return false; + var $newLane = $newCol.closest('.kanban-lane'); var newLane = $newLane.data('lane'); - - $newCol.addClass('can-drop-here'); - return true; + var canDropHere = colRules.indexOf(newCol.type) > -1 && newLane.id === lane.id; + if(canDropHere) $newCol.addClass('can-drop-here'); + return canDropHere; }); } +/** + * Render user avatar + * @param {String|{account: string, avatar: string}} user User account or user object + * @returns {string} + */ +function renderUserAvatar(user, objectType, objectID, size) +{ + var avatarSizeClass = 'avatar-' + (size || 'sm'); + var $noPrivAndNoAssigned = $('
'); + if(objectType == 'task') + { + if(!priv.canAssignTask && !user) return $noPrivAndNoAssigned; + var link = createLink('task', 'assignto', 'executionID=' + executionID + '&id=' + objectID, '', true); + } + if(objectType == 'story') + { + if(!priv.canAssignStory && !user) return $noPrivAndNoAssigned; + var link = createLink('story', 'assignto', 'id=' + objectID, '', true); + } + if(objectType == 'bug') + { + if(!priv.canAssignBug && !user) return $noPrivAndNoAssigned; + var link = createLink('bug', 'assignto', 'id=' + objectID, '', true); + } + + if(!user) return $(''); + + if(typeof user === 'string') user = {account: user}; + if(!user.avatar && window.userList && window.userList[user.account]) user = {avatar: userList[user.account].avatar, account: user.account}; + + var $noPrivAvatar = $('
').avatar({user: user}); + if(objectType == 'task' && !priv.canAssignTask) return $noPrivAvatar; + if(objectType == 'story' && !priv.canAssignStory) return $noPrivAvatar; + if(objectType == 'bug' && !priv.canAssignBug) return $noPrivAvatar; + + return $('').avatar({user: user}).attr('data-toggle', 'modal').attr('data-width', '80%'); +} + +/** + * Render deadline + * @param {String|Date} deadline Deadline + * @returns {JQuery} + */ +function renderDeadline(deadline) +{ + if(deadline == '0000-00-00') return; + + var date = $.zui.createDate(deadline); + var now = new Date(); + now.setHours(0); + now.setMinutes(0); + now.setSeconds(0); + now.setMilliseconds(0); + var isEarlyThanToday = date.getTime() < now.getTime(); + var deadlineDate = $.zui.formatDate(date, 'MM-dd'); + + return $('').text(deadlineLang + ' ' + deadlineDate).addClass(isEarlyThanToday ? 'text-red' : 'text-muted'); +} + +/** + * Render story item + * @param {Object} item Story item object + * @param {JQuery} $item Kanban item element + * @param {Object} col Column object + * @returns {JQuery} $item Kanban item element + */ +function renderStoryItem(item, $item, col) +{ + var scaleSize = window.kanbanScaleSize; + if(+$item.attr('data-scale-size') !== scaleSize) $item.empty().attr('data-scale-size', scaleSize); + + if(scaleSize <= 3) + { + var $title = $item.find('.title'); + if(!$title.length) + { + $title = $('' + (scaleSize <= 1 ? ' ' : '') + '') + .attr('href', $.createLink('story', 'view', 'storyID=' + item.id, '', true)).attr('data-toggle', 'modal').attr('data-width', '80%'); + $title.appendTo($item); + } + $title.attr('title', item.title).find('.text').text(item.title); + } + + if(scaleSize <= 2) + { + var idHtml = scaleSize <= 1 ? ('#' + item.id + '') : ''; + var priHtml = '' + item.pri + ''; + var hoursHtml = (item.estimate && scaleSize <= 1) ? ('' + item.estimate + 'h') : ''; + var avatarHtml = renderUserAvatar(item.assignedTo, 'story', item.id); + var $infos = $item.find('.infos'); + if(!$infos.length) $infos = $('
'); + $infos.html([idHtml, priHtml, hoursHtml].join('')); + + $infos[scaleSize <= 1 ? 'append' : 'prepend'](avatarHtml); + if(scaleSize <= 1) $infos.appendTo($item); + else if(scaleSize === 2) $infos.prependTo($item); + else $infos.prependTo($item.find('.title')); + } + else if(scaleSize === 4) + { + $item.html(renderUserAvatar(item.assignedTo, 'story', item.id, 'md')); + } + + if(scaleSize <= 1) + { + var $actions = $item.find('.actions'); + if(!$actions.length && item.menus && item.menus.length) + { + $actions = $([ + '
', + '', + '', + '', + '
' + ].join('')).appendTo($item); + } + } + + return $item.attr('data-type', 'story').addClass('kanban-item-story'); +} + +/** + * Render bug item + * @param {Object} item Bug item object + * @param {JQuery} $item Kanban item element + * @param {Object} col Column object + * @returns {JQuery} $item Kanban item element + */ +function renderBugItem(item, $item, col) +{ + var scaleSize = window.kanbanScaleSize; + if(+$item.attr('data-scale-size') !== scaleSize) $item.empty().attr('data-scale-size', scaleSize); + + if(scaleSize <= 3) + { + var $title = $item.find('.title'); + if(!$title.length) + { + $title = $('' + (scaleSize <= 1 ? ' ' : '') + '') + .attr('href', $.createLink('bug', 'view', 'bugID=' + item.id, '', true)).attr('data-toggle', 'modal').attr('data-width', '80%'); + $title.appendTo($item); + } + $title.attr('title', item.title).find('.text').text(item.title); + } + + if(scaleSize <= 2) + { + var idHtml = scaleSize <= 1 ? ('#' + item.id + '') : ''; + var severityHtml = scaleSize <= 1 ? ('') : ''; + var priHtml = '' + item.pri + ''; + var avatarHtml = renderUserAvatar(item.assignedTo, 'bug', item.id); + + var $infos = $item.find('.infos'); + if(!$infos.length) $infos = $('
'); + $infos.html([idHtml, severityHtml, priHtml].join('')); + if(item.deadline && scaleSize <= 1) $infos.append(renderDeadline(item.deadline)); + $infos[scaleSize <= 1 ? 'append' : 'prepend'](avatarHtml); + + if(scaleSize <= 1) $infos.appendTo($item); + else if(scaleSize === 2) $infos.prependTo($item); + else $infos.prependTo($item.find('.title')); + } + else if(scaleSize === 4) + { + $item.html(renderUserAvatar(item.assignedTo, 'bug', item.id, 'md')); + } + + if(scaleSize <= 1) + { + var $actions = $item.find('.actions'); + if(!$actions.length && item.menus && item.menus.length) + { + $actions = $([ + '
', + '', + '', + '', + '
' + ].join('')).appendTo($item); + } + } + + return $item.attr('data-type', 'bug').addClass('kanban-item-bug'); +} + +/** + * Render task item + * @param {Object} item Task item object + * @param {JQuery} $item Kanban item element + * @param {Object} col Column object + * @returns {JQuery} $item Kanban item element + */ +function renderTaskItem(item, $item, col) +{ + var scaleSize = window.kanbanScaleSize; + if(+$item.attr('data-scale-size') !== scaleSize) $item.empty().attr('data-scale-size', scaleSize); + + if(scaleSize <= 3) + { + var $title = $item.find('.title'); + if(!$title.length) + { + $title = $('' + (scaleSize <= 1 ? ' ' : '') + '') + .attr('href', $.createLink('task', 'view', 'taskID=' + item.id, '', true)).attr('data-toggle', 'modal').attr('data-width', '80%'); + $title.appendTo($item); + } + $title.attr('title', item.name).find('.text').text(item.name); + } + + if(scaleSize <= 2) + { + var idHtml = scaleSize <= 1 ? ('#' + item.id + '') : ''; + var priHtml = '' + item.pri + ''; + var hoursHtml = (item.estimate && scaleSize <= 1) ? ('' + item.estimate + 'h') : ''; + var avatarHtml = renderUserAvatar(item.assignedTo, 'task', item.id); + + var $infos = $item.find('.infos'); + if(!$infos.length) $infos = $('
'); + $infos.html([idHtml, priHtml, hoursHtml].join('')); + if(item.deadline && scaleSize <= 1) $infos.append(renderDeadline(item.deadline)); + $infos[scaleSize <= 1 ? 'append' : 'prepend'](avatarHtml); + + if(scaleSize <= 1) $infos.appendTo($item); + else if(scaleSize === 2) $infos.prependTo($item); + else $infos.prependTo($item.find('.title')); + } + else if(scaleSize === 4) + { + $item.html(renderUserAvatar(item.assignedTo, 'task', item.id, 'md')); + } + + if(scaleSize <= 1) + { + var $actions = $item.find('.actions'); + if(!$actions.length && item.menus && item.menus.length) + { + $actions = $([ + '
', + '', + '', + '', + '
' + ].join('')).appendTo($item); + } + } + + $item.attr('data-type', 'task').addClass('kanban-item-task'); + + return $item; +} + +/* Add column renderer */ +addColumnRenderer('story', renderStoryItem); +addColumnRenderer('bug', renderBugItem); +addColumnRenderer('task', renderTaskItem); + /** * Render items count of a column. */ @@ -196,7 +493,7 @@ function renderHeaderCol($column, column, $header, kanbanData) /* Render group header. */ var privs = kanbanData.actions; var columnPrivs = kanbanData.columns[0].actions; - var $actions = $('
'); + var $actions = $column.children('.actions'); if(privs.includes('sortGroup')) { @@ -213,14 +510,10 @@ function renderHeaderCol($column, column, $header, kanbanData) } } - var regionID = $column.closest('.kanban').data('id'); - var groupID = $column.closest('.kanban-board').data('id'); - var laneID = column.$kanbanData.lanes[0].id ? column.$kanbanData.lanes[0].id : 0; - var columnID = $column.closest('.kanban-col').data('id'); - var printMoreBtn = (columnPrivs.includes('setColumn') || columnPrivs.includes('setWIP') || columnPrivs.includes('deleteColumn')); + var printMoreBtn = (columnPrivs.includes('setColumn') || columnPrivs.includes('setWIP')); /* Render more menu. */ - if((column.type == 'backlog' && hasStoryButton) || (column.type == 'wait' && hasTaskButton) || (column.type == 'unconfirmed' && hasBugButton)) + if(((column.type == 'backlog' && hasStoryButton) || (column.type == 'wait' && hasTaskButton) || (column.type == 'unconfirmed' && hasBugButton)) && $actions.children('.text-primary').length == 0) { $actions.append([ '', @@ -228,11 +521,10 @@ function renderHeaderCol($column, column, $header, kanbanData) '' ].join('')); } - if(printMoreBtn) + if(printMoreBtn && $actions.children('.btn').length == 0) { $actions.append(' '); } - $actions.appendTo($column); } /** @@ -266,28 +558,6 @@ function renderLaneName($lane, lane, $kanban, columns, kanban) } } -/** - * Adjust add button postion in column - */ -function adjustAddBtnPosition($kanban) -{ - if(!$kanban) - { - $('.kanban').children('.kanban-board').each(function() - { - adjustAddBtnPosition($(this)); - }); - return; - } - - $kanban.find('.kanban-lane-col:not([data-type="EMPTY"])').each(function() - { - var $col = $(this); - var items = $col.children('.kanban-lane-items')[0]; - $col.toggleClass('has-scrollbar', items.scrollHeight > items.clientHeight); - }); -} - /** * Handle kanban action */ @@ -316,94 +586,6 @@ function processMinusBtn() } } -/** - * The function for rendering kanban item - */ -function renderKanbanItem(item, $item) -{ - var $title = $item.children('.title'); - var privs = item.actions; - var printMoreBtn = (privs.includes('editCard') || privs.includes('archiveCard') || privs.includes('copyCard') || privs.includes('deleteCard') || privs.includes('moveCard') || privs.includes('setCardColor')); - if(!$title.length) - { - if(privs.includes('viewCard')) $title = $('').appendTo($item).attr('href', createLink('kanban', 'viewCard', 'cardID=' + item.id, '', true)); - if(!privs.includes('viewCard')) $title = $('

').appendTo($item); - } - - $title.text(item.name).attr('title', item.name); - - if(printMoreBtn) - { - $( - [ - '
', - '', - '', - '', - '
' - ].join('')).appendTo($item); - } - - var $info = $item.children('.info'); - if(!$info.length) $info = $( - [ - '
', - '', - '', - '', - '
', - '
' - ].join('')).appendTo($item); - - $item.data('card', item); - - $info.children('.estimate').text(item.estimate + kanbancardLang.lblHour); - if(item.estimate == 0) $info.children('.estimate').hide(); - - $info.children('.pri') - .attr('class', 'pri label-pri label-pri-' + item.pri) - .text(item.pri); - - $item.css('background-color', item.color); - $item.toggleClass('has-color', item.color != '#fff' && item.color != ''); - $item.find('.info > .label-light').css('background-color', item.color); - - var $time = $info.children('.time'); - if(item.end == '0000-00-00' && item.begin == '0000-00-00') - { - $time.hide(); - } - else - { - var today = new Date(); - var begin = $.zui.createDate(item.begin); - var end = $.zui.createDate(item.end); - var needRemind = (begin.toLocaleDateString() == today.toLocaleDateString() || end.toLocaleDateString() == today.toLocaleDateString()); - if(item.end == '0000-00-00' && item.begin != '0000-00-00') - { - $time.text($.zui.formatDate(begin, 'MM/dd') + ' ' + kanbancardLang.beginAB).attr('title', $.zui.formatDate(begin, 'yyyy/MM/dd') + ' ' +kanbancardLang.beginAB).show(); - } - else if(item.begin == '0000-00-00' && item.end != '0000-00-00') - { - $time.text($.zui.formatDate(end, 'MM/dd') + ' ' + kanbancardLang.deadlineAB).attr('title', $.zui.formatDate(end, 'yyyy/MM/dd') + ' ' + kanbancardLang.deadlineAB).show(); - } - else if(item.begin != '0000-00-00' && item.end != '0000-00-00') - { - $time.text($.zui.formatDate(begin, 'MM/dd') + ' ' + kanbancardLang.to + ' ' + $.zui.formatDate(end, 'MM/dd')).attr('title', $.zui.formatDate(begin, 'yyyy/MM/dd') + kanbancardLang.to + $.zui.formatDate(end, 'yyyy/MM/dd')).show(); - } - - if(!$item.hasClass('has-color') && needRemind) $time.css('background-color', 'rgba(210, 50, 61, 0.3)'); - if($item.hasClass('has-color') && needRemind) $time.css('background-color', 'rgba(255, 255, 255, 0.3)'); - if(!needRemind) $time.css('background-color', 'rgba(0, 0, 0, 0.15)'); - } - - /* Display avatars of assignedTo. */ - var assignedTo = item.assignedTo.split(','); - var $user = $info.children('.user'); - var title = []; - for(i = 0; i < assignedTo.length; i++) title.push(users[assignedTo[i]]); - $user.html(renderUsersAvatar(assignedTo, item.id)).attr('title', title); -} /* Define menu creators */ window.menuCreators = { @@ -429,9 +611,9 @@ function initKanban($kanban) maxColWidth: 300, createColumnText: kanbanLang.createColumn, addItemText: '', - itemRender: renderKanbanItem, + cardHeight: getCardHeight(), + cardsPerRow: window.kanbanScaleSize, onAction: handleKanbanAction, - onRenderKanban: adjustAddBtnPosition, onRenderLaneName: renderLaneName, onRenderHeaderCol: renderHeaderCol, onRenderCount: renderCount, @@ -511,13 +693,6 @@ $(function() $.zui.ContextMenu.show(items, items.$options || {event: event}); }); - /* Adjust the add button position on window resize */ - $(window).on('resize', function(a) - { - adjustAddBtnPosition(); - }); - - /* Hide contextmenu when page scroll */ $(window).on('scroll', function() { diff --git a/module/execution/view/kanban.html.php b/module/execution/view/kanban.html.php index 24200cdb58..fd4462ec3d 100644 --- a/module/execution/view/kanban.html.php +++ b/module/execution/view/kanban.html.php @@ -62,6 +62,17 @@ js::set('priv', 'canBatchCreateStory' => $canBatchCreateStory, 'canLinkStory' => $canLinkStory, 'canLinkStoryByPlane' => $canLinkStoryByPlane, + 'canAssignTask' => common::hasPriv('task', 'assignto'), + 'canAssignStory' => common::hasPriv('story', 'assignto'), + 'canFinishTask' => common::hasPriv('task', 'finish'), + 'canPauseTask' => common::hasPriv('task', 'pause'), + 'canCancelTask' => common::hasPriv('task', 'cancel'), + 'canCloseTask' => common::hasPriv('task', 'close'), + 'canActivateTask' => common::hasPriv('task', 'activate'), + 'canStartTask' => common::hasPriv('task', 'start'), + 'canAssignBug' => common::hasPriv('bug', 'assignto'), + 'canConfirmBug' => common::hasPriv('bug', 'confirmBug'), + 'canActivateBug' => common::hasPriv('bug', 'activate') ) ); js::set('hasStoryButton', $hasStoryButton); @@ -119,8 +130,6 @@ js::set('hasTaskButton', $hasTaskButton); $actions .= ""; } - $actions .= "
"; - echo $actions; ?>
diff --git a/module/kanban/model.php b/module/kanban/model.php index b2de333aa5..44ca064ee7 100644 --- a/module/kanban/model.php +++ b/module/kanban/model.php @@ -654,7 +654,12 @@ class kanbanModel extends model $lanes = zget($laneGroup, $group->id, array()); if(!$lanes) continue; - foreach($lanes as $lane) $lane->items = array(); + foreach($lanes as $lane) + { + $this->refreshCards($lane); + $lane->items = isset($cardGroup[$lane->id]) ? $cardGroup[$lane->id] : array(); + $lane->defaultCardType = $lane->type; + } $group->columns = zget($columnGroup, $group->id, array()); $group->lanes = $lanes; @@ -918,12 +923,60 @@ class kanbanModel extends model */ public function getCardGroupByExecution($executionID, $browseType = 'all', $orderBy = 'id_asc') { - $cards = array(); - if($browseType == 'all' or $browseType == 'story') $cards['story'] = $this->loadModel('story')->getExecutionStories($executionID, 0, 0, $orderBy); - if($browseType == 'all' or $browseType == 'bug') $cards['bug'] = $this->loadModel('bug')->getExecutionBugs($executionID, 0, 0, '', 0, $orderBy); - if($browseType == 'all' or $browseType == 'task') $cards['task'] = $this->loadModel('execution')->getKanbanTasks($executionID, $orderBy); + $cards = $this->dao->select('t1.*, t2.type as columnType') + ->from(TABLE_KANBANCELL)->alias('t1') + ->leftJoin(TABLE_KANBANCOLUMN)->alias('t2')->on('t1.column=t2.id') + ->where('t1.kanban')->eq($executionID) + ->beginIF($browseType != 'all')->andWhere('t1.type')->eq($browseType)->fi() + ->orderby($orderBy) + ->fetchgroup('lane', 'column'); - return $cards; + /* Get group objects. */ + if($browseType == 'all' or $browseType == 'story') $objectGroup['story'] = $this->loadModel('story')->getExecutionStories($executionID); + if($browseType == 'all' or $browseType == 'bug') $objectGroup['bug'] = $this->loadModel('bug')->getExecutionBugs($executionID); + if($browseType == 'all' or $browseType == 'task') $objectGroup['task'] = $this->loadModel('execution')->getKanbanTasks($executionID, "id"); + + $cardGroup = array(); + + foreach($cards as $laneID => $cells) + { + foreach($cells as $columnID => $cell) + { + $cardIdList = array_filter(explode(',', $cell->cards)); + $cardOrder = 1; + foreach($cardIdList as $cardID) + { + $cardData = array(); + $objects = zget($objectGroup, $cell->type, array()); + $object = zget($objects, $cardID, array()); + + if(empty($object)) continue; + + $cardData['id'] = $object->id; + $cardData['order'] = $cardOrder++; + $cardData['pri'] = $object->pri ? $object->pri : ''; + $cardData['estimate'] = $cell->type == 'bug' ? '' : $object->estimate; + $cardData['assignedTo'] = $object->assignedTo; + $cardData['deadline'] = $cell->type == 'story' ? '' : $object->deadline; + $cardData['severity'] = $cell->type == 'bug' ? $object->severity : ''; + $cardData['acl'] = 'open'; + $cardData['lane'] = $laneID; + $cardData['column'] = $cell->column; + + if($cell->type == 'task') + { + $cardData['name'] = $object->name; + } + else + { + $cardData['title'] = $object->title; + } + $cardGroup[$laneID][$cell->columnType][] = $cardData; + } + } + } + + return $cardGroup; } /** @@ -1616,10 +1669,10 @@ class kanbanModel extends model /** * Add kanban cell for new lane. - * - * @param int $kanbanID - * @param int $laneID - * @param int $colID + * + * @param int $kanbanID + * @param int $laneID + * @param int $colID * @param string $type story|task|bug|card * @access public * @return void @@ -1725,7 +1778,7 @@ class kanbanModel extends model $this->dao->insert(TABLE_KANBANLANE)->data($lane)->autoCheck()->exec(); if(dao::isError()) return false; - $this->createRDColumn($regionID, $groupID, $this->dao->lastInsertId(), $type); + $this->createRDColumn($regionID, $groupID, $this->dao->lastInsertId(), $type, $executionID); } } @@ -1740,7 +1793,7 @@ class kanbanModel extends model * @access public * @return bool */ - public function createRDColumn($regionID, $groupID, $laneID, $laneType) + public function createRDColumn($regionID, $groupID, $laneID, $laneType, $executionID) { $devColumnID = $testColumnID = $resolvingColumnID = 0; if($laneType == 'story') $columnList = $this->lang->kanban->storyColumn; @@ -1768,6 +1821,8 @@ class kanbanModel extends model if($type == 'develop') $devColumnID = $this->dao->lastInsertId(); if($type == 'test') $testColumnID = $this->dao->lastInsertId(); if($type == 'resolving') $resolvingColumnID = $this->dao->lastInsertId(); + + $this->addKanbanCell($executionID, $laneID, $this->dao->lastInsertId(), $laneType); } } From fa461923423824c18584b6e5c33ee0b23537ae62 Mon Sep 17 00:00:00 2001 From: liumengyi Date: Thu, 13 Jan 2022 10:39:59 +0800 Subject: [PATCH 2/3] * Modify execution kanban view. --- module/execution/js/kanban.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/execution/js/kanban.js b/module/execution/js/kanban.js index 839fed12c9..cae23472c8 100644 --- a/module/execution/js/kanban.js +++ b/module/execution/js/kanban.js @@ -221,7 +221,7 @@ function renderUserAvatar(user, objectType, objectID, size) var link = createLink('bug', 'assignto', 'id=' + objectID, '', true); } - if(!user) return $(''); + if(!user) return $(''); if(typeof user === 'string') user = {account: user}; if(!user.avatar && window.userList && window.userList[user.account]) user = {avatar: userList[user.account].avatar, account: user.account}; From 0403bb58d16a8c97b267cccdfc85b15110ea3562 Mon Sep 17 00:00:00 2001 From: liumengyi Date: Thu, 13 Jan 2022 11:03:21 +0800 Subject: [PATCH 3/3] * Modify the opening method of link story. --- module/execution/control.php | 2 +- module/execution/js/kanban.js | 2 +- module/execution/lang/en.php | 199 ++++++++++++++++---------------- module/execution/lang/zh-cn.php | 199 ++++++++++++++++---------------- module/execution/model.php | 6 + 5 files changed, 208 insertions(+), 200 deletions(-) diff --git a/module/execution/control.php b/module/execution/control.php index 8da0e8c60a..e4a890573c 100644 --- a/module/execution/control.php +++ b/module/execution/control.php @@ -2638,7 +2638,7 @@ class execution extends control $this->loadModel('product'); /* Get projects, executions and products. */ - $object = $this->project->getByID($objectID, $this->app->tab == 'project' ? 'project' : 'sprint,stage'); + $object = $this->project->getByID($objectID, $this->app->tab == 'project' ? 'project' : 'sprint,stage,kanban'); $products = $this->product->getProducts($objectID); $browseLink = $this->createLink($this->app->tab == 'project' ? 'projectstory' : 'execution', 'story', "objectID=$objectID"); diff --git a/module/execution/js/kanban.js b/module/execution/js/kanban.js index cae23472c8..5e6800e695 100644 --- a/module/execution/js/kanban.js +++ b/module/execution/js/kanban.js @@ -96,7 +96,7 @@ function createColumnCreateMenu(options) { if(priv.canCreateStory) items.push({label: storyLang.create, url: $.createLink('story', 'create', 'productID=' + productID)}); if(priv.canBatchCreateStory) items.push({label: executionLang.batchCreateStroy, url: $.createLink('story', 'batchcreate', 'productID=' + productID + '&branch=0&moduleID=0&storyID=0&executionID=' + executionID)}); - if(priv.canLinkStory) items.push({label: executionLang.linkStory, url: $.createLink('execution', 'linkStory', 'executionID=' + executionID)}); + if(priv.canLinkStory) items.push({label: executionLang.linkStory, url: $.createLink('execution', 'linkStory', 'executionID=' + executionID + 'objectType=kanban'), className: 'iframe', attrs: {'data-toggle': 'modal', 'data-width': '80%'}}); if(priv.canLinkStoryByPlane) items.push({label: executionLang.linkStoryByPlan, url: '#linkStoryByPlan', 'attrs' : {'data-toggle': 'modal'}}); } else if(col.type == 'unconfirmed') diff --git a/module/execution/lang/en.php b/module/execution/lang/en.php index 40578c1152..5abad60bc4 100644 --- a/module/execution/lang/en.php +++ b/module/execution/lang/en.php @@ -10,105 +10,106 @@ * @link http://www.zentao.net */ /* Fields. */ -$lang->execution->allExecutions = 'All ' . $lang->execution->common . 's'; -$lang->execution->allExecutionAB = 'Execution List'; -$lang->execution->id = $lang->executionCommon . ' ID'; -$lang->execution->type = $lang->executionCommon . 'Type'; -$lang->execution->name = $lang->executionCommon . 'Name'; -$lang->execution->code = $lang->executionCommon . 'Code'; -$lang->execution->projectName = 'Project'; -$lang->execution->execName = 'Execution Name'; -$lang->execution->execCode = 'Execution Code'; -$lang->execution->execType = 'Execution Type'; -$lang->execution->stage = 'Stage'; -$lang->execution->pri = 'Priority'; -$lang->execution->openedBy = 'OpenedBy'; -$lang->execution->openedDate = 'OpenedDate'; -$lang->execution->closedBy = 'ClosedBy'; -$lang->execution->closedDate = 'ClosedDate'; -$lang->execution->canceledBy = 'CanceledBy'; -$lang->execution->canceledDate = 'CanceledDate'; -$lang->execution->begin = 'Planned Begin'; -$lang->execution->end = 'Planned End'; -$lang->execution->dateRange = 'Duration'; -$lang->execution->realBeganAB = 'Actual Begin'; -$lang->execution->realEndAB = 'Actual End'; -$lang->execution->realBegan = 'Actual Begin'; -$lang->execution->realEnd = 'Actual End'; -$lang->execution->to = 'To'; -$lang->execution->days = ' Days'; -$lang->execution->day = ' Days'; -$lang->execution->workHour = ' Hours'; -$lang->execution->workHourUnit = 'H'; -$lang->execution->totalHours = ' Hours'; -$lang->execution->totalDays = ' Days'; -$lang->execution->status = $lang->executionCommon . 'Status'; -$lang->execution->execStatus = 'Status'; -$lang->execution->subStatus = 'Sub Status'; -$lang->execution->desc = $lang->executionCommon . 'Description'; -$lang->execution->execDesc = 'Description'; -$lang->execution->owner = 'Owner'; -$lang->execution->PO = "{$lang->executionCommon} Owner"; -$lang->execution->PM = "{$lang->executionCommon} Manager"; -$lang->execution->execPM = "Execution Manager"; -$lang->execution->QD = 'Test Manager'; -$lang->execution->RD = 'Release Manager'; -$lang->execution->release = 'Release'; -$lang->execution->acl = 'Access Control'; -$lang->execution->teamname = 'Team Name'; -$lang->execution->updateOrder = 'Rank'; -$lang->execution->order = "Rank {$lang->executionCommon}"; -$lang->execution->orderAB = "Rank"; -$lang->execution->products = "Link {$lang->productCommon}"; -$lang->execution->whitelist = 'Whitelist'; -$lang->execution->addWhitelist = 'Add Whitelist'; -$lang->execution->unbindWhitelist = 'Remove Whitelist'; -$lang->execution->totalEstimate = 'Estimates'; -$lang->execution->totalConsumed = 'Cost'; -$lang->execution->totalLeft = 'Left'; -$lang->execution->progress = ' Progress'; -$lang->execution->hours = 'Estimates: %s, Cost: %s, Left: %s.'; -$lang->execution->viewBug = 'Bugs'; -$lang->execution->noProduct = "No {$lang->productCommon} yet."; -$lang->execution->createStory = "Create Story"; -$lang->execution->storyTitle = "Story Name"; -$lang->execution->all = "All {$lang->executionCommon}s"; -$lang->execution->undone = 'Unfinished '; -$lang->execution->unclosed = 'Unclosed'; -$lang->execution->typeDesc = "OPS {$lang->executionCommon} has no {$lang->SRCommon}, Bug, Build, or Test features."; -$lang->execution->mine = 'Mine: '; -$lang->execution->involved = 'Mine'; -$lang->execution->other = 'Others'; -$lang->execution->deleted = 'Deleted'; -$lang->execution->delayed = 'Delayed'; -$lang->execution->product = $lang->execution->products; -$lang->execution->readjustTime = "Adjust {$lang->executionCommon} Begin and End"; -$lang->execution->readjustTask = 'Adjust Task Begin and End'; -$lang->execution->effort = 'Effort'; -$lang->execution->storyEstimate = 'Story Estimate'; -$lang->execution->newEstimate = 'New Estimate'; -$lang->execution->reestimate = 'Reestimate'; -$lang->execution->selectRound = 'Select Round'; -$lang->execution->average = 'Average'; -$lang->execution->relatedMember = 'Team'; -$lang->execution->watermark = 'Exported by ZenTao'; -$lang->execution->burnXUnit = '(Date)'; -$lang->execution->burnYUnit = '(Hours)'; -$lang->execution->waitTasks = 'Waiting Tasks'; -$lang->execution->viewByUser = 'By User'; -$lang->execution->oneProduct = "Only one stage can be linked {$lang->productCommon}"; -$lang->execution->noLinkProduct = "Stage not linked {$lang->productCommon}"; -$lang->execution->recent = 'Recent visits: '; -$lang->execution->copyNoExecution = 'There are no ' . $lang->executionCommon . 'available to copy.'; -$lang->execution->noTeam = 'No team members at the moment'; -$lang->execution->or = ' or '; -$lang->execution->selectProject = 'Please select project'; -$lang->execution->unfoldClosed = 'Unfold Closed'; -$lang->execution->editName = 'Edit Name'; -$lang->execution->setWIP = 'WIP Settings'; -$lang->execution->sortColumn = 'Kanban Card Sorting'; -$lang->execution->batchCreateStroy = "Batch create {$lang->SRCommon}"; -$lang->execution->batchCreateTask = 'Batch create task'; +$lang->execution->allExecutions = 'All ' . $lang->execution->common . 's'; +$lang->execution->allExecutionAB = 'Execution List'; +$lang->execution->id = $lang->executionCommon . ' ID'; +$lang->execution->type = $lang->executionCommon . 'Type'; +$lang->execution->name = $lang->executionCommon . 'Name'; +$lang->execution->code = $lang->executionCommon . 'Code'; +$lang->execution->projectName = 'Project'; +$lang->execution->execName = 'Execution Name'; +$lang->execution->execCode = 'Execution Code'; +$lang->execution->execType = 'Execution Type'; +$lang->execution->stage = 'Stage'; +$lang->execution->pri = 'Priority'; +$lang->execution->openedBy = 'OpenedBy'; +$lang->execution->openedDate = 'OpenedDate'; +$lang->execution->closedBy = 'ClosedBy'; +$lang->execution->closedDate = 'ClosedDate'; +$lang->execution->canceledBy = 'CanceledBy'; +$lang->execution->canceledDate = 'CanceledDate'; +$lang->execution->begin = 'Planned Begin'; +$lang->execution->end = 'Planned End'; +$lang->execution->dateRange = 'Duration'; +$lang->execution->realBeganAB = 'Actual Begin'; +$lang->execution->realEndAB = 'Actual End'; +$lang->execution->realBegan = 'Actual Begin'; +$lang->execution->realEnd = 'Actual End'; +$lang->execution->to = 'To'; +$lang->execution->days = ' Days'; +$lang->execution->day = ' Days'; +$lang->execution->workHour = ' Hours'; +$lang->execution->workHourUnit = 'H'; +$lang->execution->totalHours = ' Hours'; +$lang->execution->totalDays = ' Days'; +$lang->execution->status = $lang->executionCommon . 'Status'; +$lang->execution->execStatus = 'Status'; +$lang->execution->subStatus = 'Sub Status'; +$lang->execution->desc = $lang->executionCommon . 'Description'; +$lang->execution->execDesc = 'Description'; +$lang->execution->owner = 'Owner'; +$lang->execution->PO = "{$lang->executionCommon} Owner"; +$lang->execution->PM = "{$lang->executionCommon} Manager"; +$lang->execution->execPM = "Execution Manager"; +$lang->execution->QD = 'Test Manager'; +$lang->execution->RD = 'Release Manager'; +$lang->execution->release = 'Release'; +$lang->execution->acl = 'Access Control'; +$lang->execution->teamname = 'Team Name'; +$lang->execution->updateOrder = 'Rank'; +$lang->execution->order = "Rank {$lang->executionCommon}"; +$lang->execution->orderAB = "Rank"; +$lang->execution->products = "Link {$lang->productCommon}"; +$lang->execution->whitelist = 'Whitelist'; +$lang->execution->addWhitelist = 'Add Whitelist'; +$lang->execution->unbindWhitelist = 'Remove Whitelist'; +$lang->execution->totalEstimate = 'Estimates'; +$lang->execution->totalConsumed = 'Cost'; +$lang->execution->totalLeft = 'Left'; +$lang->execution->progress = ' Progress'; +$lang->execution->hours = 'Estimates: %s, Cost: %s, Left: %s.'; +$lang->execution->viewBug = 'Bugs'; +$lang->execution->noProduct = "No {$lang->productCommon} yet."; +$lang->execution->createStory = "Create Story"; +$lang->execution->storyTitle = "Story Name"; +$lang->execution->all = "All {$lang->executionCommon}s"; +$lang->execution->undone = 'Unfinished '; +$lang->execution->unclosed = 'Unclosed'; +$lang->execution->typeDesc = "OPS {$lang->executionCommon} has no {$lang->SRCommon}, Bug, Build, or Test features."; +$lang->execution->mine = 'Mine: '; +$lang->execution->involved = 'Mine'; +$lang->execution->other = 'Others'; +$lang->execution->deleted = 'Deleted'; +$lang->execution->delayed = 'Delayed'; +$lang->execution->product = $lang->execution->products; +$lang->execution->readjustTime = "Adjust {$lang->executionCommon} Begin and End"; +$lang->execution->readjustTask = 'Adjust Task Begin and End'; +$lang->execution->effort = 'Effort'; +$lang->execution->storyEstimate = 'Story Estimate'; +$lang->execution->newEstimate = 'New Estimate'; +$lang->execution->reestimate = 'Reestimate'; +$lang->execution->selectRound = 'Select Round'; +$lang->execution->average = 'Average'; +$lang->execution->relatedMember = 'Team'; +$lang->execution->watermark = 'Exported by ZenTao'; +$lang->execution->burnXUnit = '(Date)'; +$lang->execution->burnYUnit = '(Hours)'; +$lang->execution->waitTasks = 'Waiting Tasks'; +$lang->execution->viewByUser = 'By User'; +$lang->execution->oneProduct = "Only one stage can be linked {$lang->productCommon}"; +$lang->execution->noLinkProduct = "Stage not linked {$lang->productCommon}"; +$lang->execution->recent = 'Recent visits: '; +$lang->execution->copyNoExecution = 'There are no ' . $lang->executionCommon . 'available to copy.'; +$lang->execution->noTeam = 'No team members at the moment'; +$lang->execution->or = ' or '; +$lang->execution->selectProject = 'Please select project'; +$lang->execution->unfoldClosed = 'Unfold Closed'; +$lang->execution->editName = 'Edit Name'; +$lang->execution->setWIP = 'WIP Settings'; +$lang->execution->sortColumn = 'Kanban Card Sorting'; +$lang->execution->batchCreateStroy = "Batch create {$lang->SRCommon}"; +$lang->execution->batchCreateTask = 'Batch create task'; +$lang->execution->kanbanNoLinkProduct = "Kanban not linked {$lang->productCommon}"; /* Fields of zt_team. */ $lang->execution->root = 'Root'; diff --git a/module/execution/lang/zh-cn.php b/module/execution/lang/zh-cn.php index f33c35aeac..f4bd6fc8a3 100644 --- a/module/execution/lang/zh-cn.php +++ b/module/execution/lang/zh-cn.php @@ -10,105 +10,106 @@ * @link http://www.zentao.net */ /* 字段列表。*/ -$lang->execution->allExecutions = '所有' . $lang->execution->common; -$lang->execution->allExecutionAB = "{$lang->execution->common}列表"; -$lang->execution->id = $lang->executionCommon . '编号'; -$lang->execution->type = $lang->executionCommon . '类型'; -$lang->execution->name = $lang->executionCommon . '名称'; -$lang->execution->code = $lang->executionCommon . '代号'; -$lang->execution->projectName = '所属项目'; -$lang->execution->execName = "{$lang->execution->common}名称"; -$lang->execution->execCode = "{$lang->execution->common}代号"; -$lang->execution->execType = "{$lang->execution->common}类型"; -$lang->execution->stage = '阶段'; -$lang->execution->pri = '优先级'; -$lang->execution->openedBy = '由谁创建'; -$lang->execution->openedDate = '创建日期'; -$lang->execution->closedBy = '由谁关闭'; -$lang->execution->closedDate = '关闭日期'; -$lang->execution->canceledBy = '由谁取消'; -$lang->execution->canceledDate = '取消日期'; -$lang->execution->begin = '计划开始'; -$lang->execution->end = '计划完成'; -$lang->execution->dateRange = '起始日期'; -$lang->execution->realBeganAB = '实际开始'; -$lang->execution->realEndAB = '实际完成'; -$lang->execution->realBegan = '实际开始日期'; -$lang->execution->realEnd = '实际完成日期'; -$lang->execution->to = '至'; -$lang->execution->days = '可用工作日'; -$lang->execution->day = '天'; -$lang->execution->workHour = '工时'; -$lang->execution->workHourUnit = 'h'; -$lang->execution->totalHours = '可用工时'; -$lang->execution->totalDays = '可用工日'; -$lang->execution->status = $lang->executionCommon . '状态'; -$lang->execution->execStatus = "{$lang->execution->common}状态"; -$lang->execution->subStatus = '子状态'; -$lang->execution->desc = $lang->executionCommon . '描述'; -$lang->execution->execDesc = "{$lang->execution->common}描述"; -$lang->execution->owner = '负责人'; -$lang->execution->PO = $lang->productCommon . '负责人'; -$lang->execution->PM = $lang->executionCommon . '负责人'; -$lang->execution->execPM = "{$lang->execution->common}负责人"; -$lang->execution->QD = '测试负责人'; -$lang->execution->RD = '发布负责人'; -$lang->execution->release = '发布'; -$lang->execution->acl = '访问控制'; -$lang->execution->teamname = '团队名称'; -$lang->execution->updateOrder = '排序'; -$lang->execution->order = $lang->executionCommon . '排序'; -$lang->execution->orderAB = '排序'; -$lang->execution->products = '相关' . $lang->productCommon; -$lang->execution->whitelist = '白名单'; -$lang->execution->addWhitelist = '添加白名单'; -$lang->execution->unbindWhitelist = '删除白名单'; -$lang->execution->totalEstimate = '预计'; -$lang->execution->totalConsumed = '消耗'; -$lang->execution->totalLeft = '剩余'; -$lang->execution->progress = '进度'; -$lang->execution->hours = '预计 %s 消耗 %s 剩余 %s'; -$lang->execution->viewBug = '查看bug'; -$lang->execution->noProduct = "无{$lang->executionCommon}"; -$lang->execution->createStory = "提{$lang->SRCommon}"; -$lang->execution->storyTitle = "{$lang->SRCommon}名称"; -$lang->execution->all = '所有'; -$lang->execution->undone = '未完成'; -$lang->execution->unclosed = '未关闭'; -$lang->execution->typeDesc = "运维{$lang->executionCommon}没有{$lang->SRCommon}、bug、版本、测试功能。"; -$lang->execution->mine = '我负责:'; -$lang->execution->involved = '我参与'; -$lang->execution->other = '其他'; -$lang->execution->deleted = '已删除'; -$lang->execution->delayed = '已延期'; -$lang->execution->product = $lang->execution->products; -$lang->execution->readjustTime = "调整{$lang->executionCommon}起止时间"; -$lang->execution->readjustTask = '顺延任务的起止时间'; -$lang->execution->effort = '日志'; -$lang->execution->storyEstimate = '需求估算'; -$lang->execution->newEstimate = '新一轮估算'; -$lang->execution->reestimate = '重新估算'; -$lang->execution->selectRound = '选择轮次'; -$lang->execution->average = '平均值'; -$lang->execution->relatedMember = '相关成员'; -$lang->execution->watermark = '由禅道导出'; -$lang->execution->burnXUnit = '(日期)'; -$lang->execution->burnYUnit = '(工时)'; -$lang->execution->waitTasks = '待处理'; -$lang->execution->viewByUser = '按用户查看'; -$lang->execution->oneProduct = "阶段只能关联一个{$lang->productCommon}"; -$lang->execution->noLinkProduct = "阶段没有关联{$lang->productCommon}"; -$lang->execution->recent = '近期访问:'; -$lang->execution->copyNoExecution = '没有可用的' . $lang->executionCommon . '来复制'; -$lang->execution->noTeam = '暂时没有团队成员'; -$lang->execution->or = '或'; -$lang->execution->selectProject = '请选择项目'; -$lang->execution->unfoldClosed = '展开已结束'; -$lang->execution->editName = '编辑名称'; -$lang->execution->setWIP = '在制品数量设置(WIP)'; -$lang->execution->sortColumn = '看板列卡片排序'; -$lang->execution->batchCreateStroy = "批量新建{$lang->SRCommon}"; -$lang->execution->batchCreateTask = '批量建任务'; +$lang->execution->allExecutions = '所有' . $lang->execution->common; +$lang->execution->allExecutionAB = "{$lang->execution->common}列表"; +$lang->execution->id = $lang->executionCommon . '编号'; +$lang->execution->type = $lang->executionCommon . '类型'; +$lang->execution->name = $lang->executionCommon . '名称'; +$lang->execution->code = $lang->executionCommon . '代号'; +$lang->execution->projectName = '所属项目'; +$lang->execution->execName = "{$lang->execution->common}名称"; +$lang->execution->execCode = "{$lang->execution->common}代号"; +$lang->execution->execType = "{$lang->execution->common}类型"; +$lang->execution->stage = '阶段'; +$lang->execution->pri = '优先级'; +$lang->execution->openedBy = '由谁创建'; +$lang->execution->openedDate = '创建日期'; +$lang->execution->closedBy = '由谁关闭'; +$lang->execution->closedDate = '关闭日期'; +$lang->execution->canceledBy = '由谁取消'; +$lang->execution->canceledDate = '取消日期'; +$lang->execution->begin = '计划开始'; +$lang->execution->end = '计划完成'; +$lang->execution->dateRange = '起始日期'; +$lang->execution->realBeganAB = '实际开始'; +$lang->execution->realEndAB = '实际完成'; +$lang->execution->realBegan = '实际开始日期'; +$lang->execution->realEnd = '实际完成日期'; +$lang->execution->to = '至'; +$lang->execution->days = '可用工作日'; +$lang->execution->day = '天'; +$lang->execution->workHour = '工时'; +$lang->execution->workHourUnit = 'h'; +$lang->execution->totalHours = '可用工时'; +$lang->execution->totalDays = '可用工日'; +$lang->execution->status = $lang->executionCommon . '状态'; +$lang->execution->execStatus = "{$lang->execution->common}状态"; +$lang->execution->subStatus = '子状态'; +$lang->execution->desc = $lang->executionCommon . '描述'; +$lang->execution->execDesc = "{$lang->execution->common}描述"; +$lang->execution->owner = '负责人'; +$lang->execution->PO = $lang->productCommon . '负责人'; +$lang->execution->PM = $lang->executionCommon . '负责人'; +$lang->execution->execPM = "{$lang->execution->common}负责人"; +$lang->execution->QD = '测试负责人'; +$lang->execution->RD = '发布负责人'; +$lang->execution->release = '发布'; +$lang->execution->acl = '访问控制'; +$lang->execution->teamname = '团队名称'; +$lang->execution->updateOrder = '排序'; +$lang->execution->order = $lang->executionCommon . '排序'; +$lang->execution->orderAB = '排序'; +$lang->execution->products = '相关' . $lang->productCommon; +$lang->execution->whitelist = '白名单'; +$lang->execution->addWhitelist = '添加白名单'; +$lang->execution->unbindWhitelist = '删除白名单'; +$lang->execution->totalEstimate = '预计'; +$lang->execution->totalConsumed = '消耗'; +$lang->execution->totalLeft = '剩余'; +$lang->execution->progress = '进度'; +$lang->execution->hours = '预计 %s 消耗 %s 剩余 %s'; +$lang->execution->viewBug = '查看bug'; +$lang->execution->noProduct = "无{$lang->executionCommon}"; +$lang->execution->createStory = "提{$lang->SRCommon}"; +$lang->execution->storyTitle = "{$lang->SRCommon}名称"; +$lang->execution->all = '所有'; +$lang->execution->undone = '未完成'; +$lang->execution->unclosed = '未关闭'; +$lang->execution->typeDesc = "运维{$lang->executionCommon}没有{$lang->SRCommon}、bug、版本、测试功能。"; +$lang->execution->mine = '我负责:'; +$lang->execution->involved = '我参与'; +$lang->execution->other = '其他'; +$lang->execution->deleted = '已删除'; +$lang->execution->delayed = '已延期'; +$lang->execution->product = $lang->execution->products; +$lang->execution->readjustTime = "调整{$lang->executionCommon}起止时间"; +$lang->execution->readjustTask = '顺延任务的起止时间'; +$lang->execution->effort = '日志'; +$lang->execution->storyEstimate = '需求估算'; +$lang->execution->newEstimate = '新一轮估算'; +$lang->execution->reestimate = '重新估算'; +$lang->execution->selectRound = '选择轮次'; +$lang->execution->average = '平均值'; +$lang->execution->relatedMember = '相关成员'; +$lang->execution->watermark = '由禅道导出'; +$lang->execution->burnXUnit = '(日期)'; +$lang->execution->burnYUnit = '(工时)'; +$lang->execution->waitTasks = '待处理'; +$lang->execution->viewByUser = '按用户查看'; +$lang->execution->oneProduct = "阶段只能关联一个{$lang->productCommon}"; +$lang->execution->noLinkProduct = "阶段没有关联{$lang->productCommon}"; +$lang->execution->recent = '近期访问:'; +$lang->execution->copyNoExecution = '没有可用的' . $lang->executionCommon . '来复制'; +$lang->execution->noTeam = '暂时没有团队成员'; +$lang->execution->or = '或'; +$lang->execution->selectProject = '请选择项目'; +$lang->execution->unfoldClosed = '展开已结束'; +$lang->execution->editName = '编辑名称'; +$lang->execution->setWIP = '在制品数量设置(WIP)'; +$lang->execution->sortColumn = '看板列卡片排序'; +$lang->execution->batchCreateStroy = "批量新建{$lang->SRCommon}"; +$lang->execution->batchCreateTask = '批量建任务'; +$lang->execution->kanbanNoLinkProduct = "看板没有关联{$lang->productCommon}"; /* Fields of zt_team. */ $lang->execution->root = '源ID'; diff --git a/module/execution/model.php b/module/execution/model.php index fd0ac79003..1c73027ff7 100644 --- a/module/execution/model.php +++ b/module/execution/model.php @@ -312,6 +312,12 @@ class executionModel extends model return false; } + if($type == 'kanban' and empty($this->post->products[0])) + { + dao::$errors['message'][] = $this->lang->execution->kanbanNoLinkProduct; + return false; + } + $this->config->execution->create->requiredFields .= ',project'; }