diff --git a/module/task/js/batchcreate.ui.js b/module/task/js/batchcreate.ui.js index 92b17bf40d..bb139ef6d2 100644 --- a/module/task/js/batchcreate.ui.js +++ b/module/task/js/batchcreate.ui.js @@ -156,37 +156,46 @@ $(document).off('change', '#formSettingBtn input[value=story]').on('change', '#f function checkBatchEstStartedAndDeadline(event) { + if(taskDateLimit != 'limit') return; + const $currentRow = $(event.target).closest('tr'); const field = $(event.target).closest('.form-batch-control').data('name'); const estStarted = $currentRow.find('[name^=estStarted]').val(); const deadline = $currentRow.find('[name^=deadline]').val(); + const level = $currentRow.attr('data-level'); - if(field == 'estStarted' && estStarted.length > 0 && parentEstStarted.length > 0 && estStarted < parentEstStarted) + let $nextRow = $currentRow.next(); + while($nextRow.length) { - const $estStartedTd = $currentRow.find('td[data-name=estStarted]'); - if($estStartedTd.find('.date-tip').length == 0 || $estStartedTd.find('.date-tip .form-tip').length > 0) - { - $estStartedTd.find('.date-tip').remove(); + let nextLevel = $nextRow.attr('data-level'); + if(nextLevel <= level) break; - let $datetip = $('
'); - $datetip.append('
' + overParentEstStartedLang + '' + ignoreLang + '
'); - $datetip.off('click', '.ignore-date').on('click', '.ignore-date', function(e){ignoreTip(e)}); - $estStartedTd.append($datetip); - } + if(field == 'estStarted') $nextRow.find('td[data-name=estStarted]').find('[id^=estStarted]').zui('datepicker').render({disabled: estStarted == ''}); + if(field == 'deadline') $nextRow.find('td[data-name=deadline]').find('[id^=deadline]').zui('datepicker').render({disabled: deadline == ''}); + + $nextRow = $nextRow.next(); } + if($currentRow.find('td[data-name=name]').find('input[name^=name]').val() == '') return; + + const $estStartedTd = $currentRow.find('td[data-name=estStarted]'); + $estStartedTd.find('.date-tip').remove(); + if(field == 'estStarted' && estStarted.length > 0 && parentEstStarted.length > 0 && estStarted < parentEstStarted) + { + let $datetip = $('
'); + $datetip.append('
' + overParentEstStartedLang + '
'); + $datetip.off('click', '.ignore-date').on('click', '.ignore-date', function(e){ignoreTip(e)}); + $estStartedTd.append($datetip); + } + + const $deadlineTd = $currentRow.find('td[data-name=deadline]'); + $deadlineTd.find('.date-tip').remove(); if(field == 'deadline' && deadline.length > 0 && parentDeadline.length > 0 && deadline > parentDeadline) { - const $deadlineTd = $currentRow.find('td[data-name=deadline]'); - if($deadlineTd.find('.date-tip').length == 0 || $deadlineTd.find('.date-tip .form-tip').length > 0) - { - $deadlineTd.find('.date-tip').remove(); - - let $datetip = $('
'); - $datetip.append('
' + overParentDeadlineLang + '' + ignoreLang + '
'); - $datetip.off('click', '.ignore-date').on('click', '.ignore-date', function(e){ignoreTip(e)}); - $deadlineTd.append($datetip); - } + let $datetip = $('
'); + $datetip.append('
' + overParentDeadlineLang + '
'); + $datetip.off('click', '.ignore-date').on('click', '.ignore-date', function(e){ignoreTip(e)}); + $deadlineTd.append($datetip); } } @@ -281,6 +290,27 @@ window.handleRenderRow = function($row, index) const $preAssignedTo = $prevRow.find('input[name^=assignedTo]').zui('picker'); if($preAssignedTo != undefined) $assignedTo.render({items: $preAssignedTo.options.items}); }) + + if(taskDateLimit == 'limit') + { + let disabledStarted = false; + let disabledDeadline = false; + if(parentID) + { + disabledStarted = parentEstStarted == ''; + disabledDeadline = parentDeadline == ''; + } + else if(parentID == 0 && level > 0) + { + const $prevLevelRow = $row.prev('tr[data-level="' + (level - 1) + '"]'); + const $prevLevelStarted = $prevLevelRow.find('td[data-name=estStarted]').find('[id^=estStarted]'); + const $prevLevelDeadline = $prevLevelRow.find('td[data-name=deadline]').find('[id^=deadline]'); + disabledStarted = $prevLevelStarted.val() == '' || $prevLevelStarted.prop('disabled'); + disabledDeadline = $prevLevelDeadline.val() == '' || $prevLevelDeadline.prop('disabled'); + } + $row.find('td[data-name=estStarted]').find('[id^=estStarted]').on('inited', function(e, info) { info[0].render({disabled: disabledStarted}); }) + $row.find('td[data-name=deadline]').find('[id^=deadline]').on('inited', function(e, info) { info[0].render({disabled: disabledDeadline}); }) + } }; $(function() diff --git a/module/task/js/batchedit.ui.js b/module/task/js/batchedit.ui.js index 4193418ff6..8125bed055 100644 --- a/module/task/js/batchedit.ui.js +++ b/module/task/js/batchedit.ui.js @@ -8,6 +8,8 @@ window.renderRowData = function($row, index, row) members[teamAccount] = users[teamAccount]; }); + $row.attr('data-parent', row.parent); + let taskMembers = []; if(row.mode != '' && teams[row.id] != undefined) { @@ -26,15 +28,25 @@ window.renderRowData = function($row, index, row) const taskUsers = []; let disabled = false; $row.find('.form-batch-input[data-name="assignedTo"]').empty(); - if(teams[row.id] != undefined && ((row.mode == 'linear' && row.status != 'done') || taskMembers[currentUser] == undefined)) - { - disabled = true; - } + if(teams[row.id] != undefined && ((row.mode == 'linear' && row.status != 'done') || taskMembers[currentUser] == undefined)) disabled = true; if(row.status == 'closed') disabled = true; if(row.assignedTo && taskMembers[row.assignedTo] == undefined) taskMembers[row.assignedTo] = users[row.assignedTo]; for(let account in taskMembers) taskUsers.push({value: account, text: taskMembers[account]}); + if(parentTasks[row.parent] != undefined && taskDateLimit == 'limit') + { + const parentTask = parentTasks[row.parent]; + $row.find('[id^="estStarted"]').on('inited', function(e, info) + { + if(parentTask.estStarted == '') info[0].render({disabled: true}); + }); + $row.find('[id^="deadline"]').on('inited', function(e, info) + { + if(parentTask.deadline == '') info[0].render({disabled: true}); + }); + } + $row.find('[data-name="assignedTo"]').find('.picker-box').on('inited', function(e, info) { const $assignedTo = info[0]; @@ -46,13 +58,7 @@ window.renderRowData = function($row, index, row) $assignedTo.render({items: taskUsers, disabled: disabled, toolbar: pickerToolbar}); }); - if(row.status == 'wait') - { - $row.find('[data-name="status"]').find('.picker-box').on('inited', function(e, info) - { - info[0].render({items: noPauseStatusList}); - }); - } + if(row.status == 'wait') $row.find('[data-name="status"]').find('.picker-box').on('inited', function(e, info) { info[0].render({items: noPauseStatusList}); }); if(teams[row.id] != undefined || row.isParent > 0) { @@ -173,51 +179,69 @@ window.statusChange = function(event) function checkBatchEstStartedAndDeadline(event) { - if(parentTasks.length == 0) return true; + if(taskDateLimit != 'limit') return; const $currentRow = $(event.target).closest('tr'); const taskID = $currentRow.find('[name^=id]').val(); const parentID = tasks[taskID].parent; - if(typeof parentTasks[parentID] == 'undefined' || !parentTasks[parentID]) return true; - const parentTask = parentTasks[parentID]; const field = $(event.target).closest('.form-batch-control').data('name'); const estStarted = $currentRow.find('[name^=estStarted]').val(); const deadline = $currentRow.find('[name^=deadline]').val(); + const parentTask = parentTasks[parentID] ? parentTasks[parentID] : {estStarted: '', deadline: ''}; if(field == 'estStarted') { - let parentEstStarted = typeof tasks[parentID] == 'undefined' || $(event.target).closest('tbody').find('[name="estStarted[' + parentID + ']"]').length == 0 ? parentTask.estStarted : $(event.target).closest('tbody').find('[name="estStarted[' + parentID + ']"]').val(); - if(estStarted.length > 0 && parentEstStarted.length > 0 && estStarted < parentEstStarted) - { - const $estStartedTd = $currentRow.find('td[data-name=estStarted]'); - if($estStartedTd.find('.date-tip').length == 0 || $estStartedTd.find('.date-tip .form-tip').length > 0) - { - $estStartedTd.find('.date-tip').remove(); + const $estStartedTd = $currentRow.find('td[data-name=estStarted]'); + $estStartedTd.find('.date-tip').remove(); - let $datetip = $('
'); - $datetip.append('
' + overParentEstStartedLang.replace('%s', parentEstStarted) + '' + ignoreLang + '
'); - $datetip.off('click', '.ignore-date').on('click', '.ignore-date', function(e){ignoreTip(e)}); - $estStartedTd.append($datetip); + const $childrenEstStarted = $(event.target).closest('tbody').find('tr[data-parent="' + taskID + '"]').find('[name^=estStarted]'); + $childrenEstStarted.each(function(){$(this).zui('datePicker').render({disabled: estStarted.length == 0});}); + if(estStarted.length > 0) + { + let $datetip = $('
'); + let parentEstStarted = typeof tasks[parentID] == 'undefined' || $(event.target).closest('tbody').find('[name="estStarted[' + parentID + ']"]').length == 0 ? parentTask.estStarted : $(event.target).closest('tbody').find('[name="estStarted[' + parentID + ']"]').val(); + if(parentEstStarted.length > 0 && estStarted < parentEstStarted) $datetip.append('
' + overParentEstStartedLang.replace('%s', parentEstStarted) + '
'); + + let childEstStarted = childrenDateLimit[taskID] ? childrenDateLimit[taskID].estStarted : ''; + $childrenEstStarted.each(function() + { + if(childEstStarted.length == 0 || ($(this).val().length > 0 && $(this).val() < childEstStarted)) childEstStarted = $(this).val(); + }); + if(childEstStarted.length > 0 && estStarted > childEstStarted) + { + $datetip.append('
' + overChildEstStartedLang.replace('%s', childEstStarted) + '' + ignoreLang + '
'); + $datetip.off('click', '.ignore-child').on('click', '.ignore-child', function(e){ignoreTip(e)}); } + $estStartedTd.append($datetip); } } if(field == 'deadline') { - let parentDeadline = typeof tasks[parentID] == 'undefined' || $(event.target).closest('tbody').find('[name="deadline[' + parentID + ']"]').length == 0 ? parentTask.deadline : $(event.target).closest('tbody').find('[name="deadline[' + parentID + ']"]').val(); - if(deadline.length > 0 && parentDeadline.length > 0 && deadline > parentDeadline) - { - const $deadlineTd = $currentRow.find('td[data-name=deadline]'); - if($deadlineTd.find('.date-tip').length == 0 || $deadlineTd.find('.date-tip .form-tip').length > 0) - { - $deadlineTd.find('.date-tip').remove(); + const $deadlineTd = $currentRow.find('td[data-name=deadline]'); + $deadlineTd.find('.date-tip').remove(); - let $datetip = $('
'); - $datetip.append('
' + overParentDeadlineLang.replace('%s', parentDeadline) + '' + ignoreLang + '
'); - $datetip.off('click', '.ignore-date').on('click', '.ignore-date', function(e){ignoreTip(e)}); - $deadlineTd.append($datetip); + const $childrenDeadline = $(event.target).closest('tbody').find('tr[data-parent="' + taskID + '"]').find('[name^=deadline]'); + $childrenDeadline.each(function(){$(this).zui('datePicker').render({disabled: deadline.length == 0});}); + if(deadline.length > 0) + { + let $datetip = $('
'); + + let parentDeadline = typeof tasks[parentID] == 'undefined' || $(event.target).closest('tbody').find('[name="deadline[' + parentID + ']"]').length == 0 ? parentTask.deadline : $(event.target).closest('tbody').find('[name="deadline[' + parentID + ']"]').val(); + if(parentDeadline.length > 0 && deadline > parentDeadline) $datetip.append('
' + overParentDeadlineLang.replace('%s', parentDeadline) + '
'); + + let childDeadline = childrenDateLimit[taskID] ? childrenDateLimit[taskID].deadline : ''; + $childrenDeadline.each(function() + { + if(childDeadline.length == 0 || ($(this).val().length > 0 && $(this).val() > childDeadline)) childDeadline = $(this).val(); + }); + if(childDeadline.length > 0 && deadline < childDeadline) + { + $datetip.append('
' + overChildDeadlineLang.replace('%s', childDeadline) + '' + ignoreLang + '
'); + $datetip.off('click', '.ignore-child').on('click', '.ignore-child', function(e){ignoreTip(e)}); } + $deadlineTd.append($datetip); } } } diff --git a/module/task/js/create.ui.js b/module/task/js/create.ui.js index d4e59bb2b4..57a0a5ea99 100644 --- a/module/task/js/create.ui.js +++ b/module/task/js/create.ui.js @@ -644,8 +644,17 @@ overParentEstStartedLang = ''; overParentDeadlineLang = ''; window.getParentEstStartedAndDeadline = function() { - const parent = $('[name=parent]').val(); - if(!parent) return; + const $parent = $('[name=parent]'); + const parent = $parent.val(); + if(!parent) + { + if(taskDateLimit != 'limit') return; + + const $form = $parent.closest('form'); + $form.find('[name=estStarted]').zui('datePicker').render({disabled: false}); + $form.find('[name=deadline]').zui('datePicker').render({disabled: false}); + return; + } const link = $.createLink('task', 'ajaxGetTaskEstStartedAndDeadline', 'taskID=' + parent); $.getJSON(link, function(data) @@ -662,6 +671,8 @@ window.getParentEstStartedAndDeadline = function() window.checkEstStartedAndDeadline = function(event) { + if(taskDateLimit != 'limit') return; + const parent = $('[name=parent]').val(); if(!parent) return; @@ -672,31 +683,28 @@ window.checkEstStartedAndDeadline = function(event) const $deadline = $form.find('[name=deadline]'); const deadline = $deadline.val(); + const $estStartedDiv = $estStarted.closest('.form-group-wrapper'); + if(field == 'estStarted') $estStartedDiv.find('.date-tip').remove(); if(field == 'estStarted' && estStarted.length > 0 && parentEstStarted.length > 0 && estStarted < parentEstStarted) { - const $estStartedDiv = $estStarted.closest('.form-group'); - if($estStartedDiv.find('.date-tip').length == 0 || $estStartedDiv.find('.date-tip .form-tip').length > 0) - { - $estStartedDiv.find('.date-tip').remove(); - - let $datetip = $('
'); - $datetip.append('
' + overParentEstStartedLang + '' + ignoreLang + '
'); - $datetip.off('click', '.ignore-date').on('click', '.ignore-date', function(e){ignoreTip(e)}); - $estStartedDiv.append($datetip); - } + let $datetip = $('
'); + $datetip.append('
' + overParentEstStartedLang + '
'); + $estStartedDiv.append($datetip); } + const $deadlineDiv = $deadline.closest('.form-group-wrapper'); + if(field == 'deadline') $deadlineDiv.find('.date-tip').remove(); if(field == 'deadline' && deadline.length > 0 && parentDeadline.length > 0 && deadline > parentDeadline) { - const $deadlineDiv = $deadline.closest('.form-group'); - if($deadlineDiv.find('.date-tip').length == 0 || $deadlineDiv.find('.date-tip .form-tip').length > 0) - { - $deadlineDiv.find('.date-tip').remove(); - - let $datetip = $('
'); - $datetip.append('
' + overParentDeadlineLang + '' + ignoreLang + '
'); - $datetip.off('click', '.ignore-date').on('click', '.ignore-date', function(e){ignoreTip(e)}); - $deadlineDiv.append($datetip); - } + let $datetip = $('
'); + $datetip.append('
' + overParentDeadlineLang + '
'); + $deadlineDiv.append($datetip); } + + let $estStartedPicker = $estStarted.zui('datePicker'); + let $deadlinePicker = $deadline.zui('datePicker'); + $estStartedPicker.render({disabled: parentEstStarted == ''}); + $deadlinePicker.render({disabled: parentDeadline == ''}); + if(parentEstStarted == '') $estStartedPicker.$.setValue(''); + if(parentDeadline == '') $deadlinePicker.$.setValue(''); } diff --git a/module/task/js/edit.ui.js b/module/task/js/edit.ui.js index cbc33d8a55..86435cd4c4 100644 --- a/module/task/js/edit.ui.js +++ b/module/task/js/edit.ui.js @@ -413,8 +413,17 @@ window.setStoryModule = function() getParentEstStartedAndDeadline = function() { - const parent = $('[name=parent]').val(); - if(!parent) return; + const $parent = $('[name=parent]'); + const parent = $parent.val(); + if(!parent) + { + if(taskDateLimit != 'limit') return; + + const $form = $parent.closest('form'); + $form.find('[name=estStarted]').zui('datePicker').render({disabled: false}); + $form.find('[name=deadline]').zui('datePicker').render({disabled: false}); + return; + } const link = $.createLink('task', 'ajaxGetTaskEstStartedAndDeadline', 'taskID=' + parent); $.getJSON(link, function(data) @@ -431,6 +440,11 @@ getParentEstStartedAndDeadline = function() function checkEstStartedAndDeadline(event) { + if(taskDateLimit != 'limit') return; + + const parent = $('[name=parent]').val(); + if(!parent) return; + const $form = $(event.target).closest('form'); const field = $(event.target).attr('name') const $estStarted = $form.find('[name=estStarted]'); @@ -438,31 +452,40 @@ function checkEstStartedAndDeadline(event) const $deadline = $form.find('[name=deadline]'); const deadline = $deadline.val(); - if(field == 'estStarted' && estStarted.length > 0 && parentEstStarted.length > 0 && estStarted < parentEstStarted) + const $estStartedDiv = $estStarted.closest('.form-group'); + if(field == 'estStarted' && estStarted.length > 0) { - const $estStartedDiv = $estStarted.closest('.form-group'); - if($estStartedDiv.find('.date-tip').length == 0 || $estStartedDiv.find('.date-tip .form-tip').length > 0) - { - $estStartedDiv.find('.date-tip').remove(); + $estStartedDiv.find('.date-tip').remove(); + let $datetip = $('
'); + if(parentEstStarted.length > 0 && estStarted < parentEstStarted) $datetip.append('
' + overParentEstStartedLang + '
'); - let $datetip = $('
'); - $datetip.append('
' + overParentEstStartedLang + '' + ignoreLang + '
'); - $datetip.off('click', '.ignore-date').on('click', '.ignore-date', function(e){ignoreTip(e)}); - $estStartedDiv.append($datetip); + if(childDateLimit['estStarted'].length > 0 && estStarted > childDateLimit['estStarted']) + { + $datetip.append('
' + overChildEstStartedLang + '' + ignoreLang + '
'); + $datetip.off('click', '.ignore-child').on('click', '.ignore-child', function (e) { ignoreTip(e) }); } + $estStartedDiv.append($datetip); } - if(field == 'deadline' && deadline.length > 0 && parentDeadline.length > 0 && deadline > parentDeadline) + const $deadlineDiv = $deadline.closest('.form-group'); + if(field == 'deadline' && deadline.length > 0) { - const $deadlineDiv = $deadline.closest('.form-group'); - if($deadlineDiv.find('.date-tip').length == 0 || $deadlineDiv.find('.date-tip .form-tip').length > 0) - { - $deadlineDiv.find('.date-tip').remove(); + $deadlineDiv.find('.date-tip').remove(); + let $datetip = $('
'); + if(parentDeadline.length > 0 && deadline > parentDeadline) $datetip.append('
' + overParentDeadlineLang + '
'); - let $datetip = $('
'); - $datetip.append('
' + overParentDeadlineLang + '' + ignoreLang + '
'); - $datetip.off('click', '.ignore-date').on('click', '.ignore-date', function(e){ignoreTip(e)}); - $deadlineDiv.append($datetip); + if(childDateLimit['deadline'].length > 0 && deadline < childDateLimit['deadline']) + { + $datetip.append('
' + overChildDeadlineLang + '' + ignoreLang + '
'); + $datetip.off('click', '.ignore-child').on('click', '.ignore-child', function (e) { ignoreTip(e) }); } + $deadlineDiv.append($datetip); } + + let $estStartedPicker = $estStarted.zui('datePicker'); + let $deadlinePicker = $deadline.zui('datePicker'); + $estStartedPicker.render({disabled: parentEstStarted == ''}); + $deadlinePicker.render({disabled: parentDeadline == ''}); + if(parentEstStarted == '') $estStartedPicker.$.setValue(''); + if(parentDeadline == '') $deadlinePicker.$.setValue(''); } diff --git a/module/task/lang/de.php b/module/task/lang/de.php index ba80d1888f..8feeb4a82b 100644 --- a/module/task/lang/de.php +++ b/module/task/lang/de.php @@ -437,6 +437,8 @@ $lang->task->overEsEndDate = 'The %s schedule end time has exceeded, please mo $lang->task->overParentEsStarted = 'StartDate is less than the parent task\'s startDate: %s'; $lang->task->overParentDeadline = 'Deadline is greater than the parent task\'s deadline: %s'; +$lang->task->overChildEstStarted = "Existed child task's startDate is less than the task's startDate: %s"; +$lang->task->overChildDeadline = "Existed child task's deadline is greater than the task's deadline: %s"; $lang->task->disabledHint = new stdclass(); $lang->task->disabledHint->assignedConfirmStoryChange = 'Changes can only be confirmed by the assignee.'; diff --git a/module/task/lang/en.php b/module/task/lang/en.php index 3eaf16fa8c..a9d6796b81 100755 --- a/module/task/lang/en.php +++ b/module/task/lang/en.php @@ -437,6 +437,8 @@ $lang->task->overEsEndDate = 'The %s schedule end time has exceeded, please mo $lang->task->overParentEsStarted = 'StartDate is less than the parent task\'s startDate: %s'; $lang->task->overParentDeadline = 'Deadline is greater than the parent task\'s deadline: %s'; +$lang->task->overChildEstStarted = "Existed child task's startDate is less than the task's startDate: %s"; +$lang->task->overChildDeadline = "Existed child task's deadline is greater than the task's deadline: %s"; $lang->task->disabledHint = new stdclass(); $lang->task->disabledHint->assignedConfirmStoryChange = 'Changes can only be confirmed by the assignee.'; diff --git a/module/task/lang/fr.php b/module/task/lang/fr.php index 2a33173f1b..a85ea8bcb5 100644 --- a/module/task/lang/fr.php +++ b/module/task/lang/fr.php @@ -437,6 +437,8 @@ $lang->task->overEsEndDate = 'The %s schedule end time has exceeded, please mo $lang->task->overParentEsStarted = 'StartDate is less than the parent task\'s startDate: %s'; $lang->task->overParentDeadline = 'Deadline is greater than the parent task\'s deadline: %s'; +$lang->task->overChildEstStarted = "Existed child task's startDate is less than the task's startDate: %s"; +$lang->task->overChildDeadline = "Existed child task's deadline is greater than the task's deadline: %s"; $lang->task->disabledHint = new stdclass(); $lang->task->disabledHint->assignedConfirmStoryChange = 'Changes can only be confirmed by the assignee.'; diff --git a/module/task/lang/zh-cn.php b/module/task/lang/zh-cn.php index d09ef454f5..164a921461 100755 --- a/module/task/lang/zh-cn.php +++ b/module/task/lang/zh-cn.php @@ -437,6 +437,8 @@ $lang->task->overEsEndDate = '已超出%s计划结束时间,请先修改%s $lang->task->overParentEsStarted = '任务的预计开始日期小于了父任务的预计开始日期:%s'; $lang->task->overParentDeadline = '任务的截止日期大于了父任务的截止日期:%s'; +$lang->task->overChildEstStarted = '存在子任务的预计开始日期小于了该任务的预计开始日期:%s'; +$lang->task->overChildDeadline = '存在子任务的截止日期超出了该任务的截止日期:%s'; $lang->task->disabledHint = new stdclass(); $lang->task->disabledHint->assignedConfirmStoryChange = '只有指派人才能确认变更'; diff --git a/module/task/model.php b/module/task/model.php index cab61faf1e..24ea5bd5ce 100755 --- a/module/task/model.php +++ b/module/task/model.php @@ -749,26 +749,29 @@ class taskModel extends model * @param int $executionID * @param string $estStarted * @param string $deadline - * @param string $prefix + * @param int|null $rowID * @access public * @return false|void */ - public function checkEstStartedAndDeadline(int $executionID, string $estStarted, string $deadline, string $prefix = '') + public function checkEstStartedAndDeadline(int $executionID, string $estStarted, string $deadline, int|null $rowID = null) { + $beginIndex = $rowID === null ? 'estStarted' : "estStarted[$rowID]"; + $endIndex = $rowID === null ? 'deadline' : "deadline[$rowID]"; + $execution = $this->loadModel('execution')->getByID($executionID); if(empty($execution) || empty($this->config->limitTaskDate)) return false; if(empty($execution->multiple)) $this->lang->execution->common = $this->lang->project->common; if(!empty($estStarted) && !helper::isZeroDate($estStarted)) { - if($estStarted < $execution->begin) dao::$errors['estStarted'] = $prefix . sprintf($this->lang->task->error->beginLtExecution, $this->lang->execution->common, $execution->begin); - if($estStarted > $execution->end) dao::$errors['estStarted'] = $prefix . sprintf($this->lang->task->error->beginGtExecution, $this->lang->execution->common, $execution->end); + if($estStarted < $execution->begin) dao::$errors[$beginIndex] = sprintf($this->lang->task->error->beginLtExecution, $this->lang->execution->common, $execution->begin); + if($estStarted > $execution->end) dao::$errors[$beginIndex] = sprintf($this->lang->task->error->beginGtExecution, $this->lang->execution->common, $execution->end); } if(!empty($deadline) && !helper::isZeroDate($deadline)) { - if($deadline > $execution->end) dao::$errors['deadline'] = $prefix . sprintf($this->lang->task->error->endGtExecution, $this->lang->execution->common, $execution->end); - if($deadline < $execution->begin) dao::$errors['deadline'] = $prefix . sprintf($this->lang->task->error->endLtExecution, $this->lang->execution->common, $execution->begin); + if($deadline > $execution->end) dao::$errors[$endIndex] = sprintf($this->lang->task->error->endGtExecution, $this->lang->execution->common, $execution->end); + if($deadline < $execution->begin) dao::$errors[$endIndex] = sprintf($this->lang->task->error->endLtExecution, $this->lang->execution->common, $execution->begin); } } @@ -821,6 +824,11 @@ class taskModel extends model $tasks = $this->dao->select('estStarted, realStarted, deadline')->from(TABLE_TASK)->where('parent')->eq($taskID)->andWhere('status')->ne('cancel')->andWhere('deleted')->eq(0)->fetchAll(); if(empty($tasks)) return !dao::isError(); + /* Initialize task data and update it. */ + $parent = $this->fetchById($taskID); + $taskDateLimit = $this->dao->select('taskDateLimit')->from(TABLE_PROJECT)->where('id')->eq($parent->project)->fetch('taskDateLimit'); + if($taskDateLimit == 'limit') return !dao::isError(); + /* Compute the earliest estStarted, the earliest realStarted and the latest deadline. */ $earliestEstStarted = ''; $earliestRealStarted = ''; @@ -832,9 +840,6 @@ class taskModel extends model if(!helper::isZeroDate($task->deadline) && (empty($latestDeadline) || $latestDeadline < $task->deadline)) $latestDeadline = $task->deadline; } - /* Initialize task data and update it. */ - $parent = $this->fetchById($taskID); - $newTask = array(); if(!empty($earliestEstStarted) && !helper::isZeroDate($parent->estStarted) && $parent->estStarted > $earliestEstStarted) $newTask['estStarted'] = $earliestEstStarted; if(!empty($earliestRealStarted) && !helper::isZeroDate($parent->realStarted) && $parent->realStarted > $earliestRealStarted) $newTask['realStarted'] = $earliestRealStarted; @@ -3251,6 +3256,21 @@ class taskModel extends model if($postData->type == 'task') { + $project = $this->dao->select('id,taskDateLimit')->from(TABLE_PROJECT)->where('id')->eq($oldObject->project)->fetch(); + if($project->taskDateLimit == 'limit') + { + $parentTasks = $this->dao->select('id,estStarted,deadline')->from(TABLE_TASK)->where('id')->in($oldObject->path)->andWhere('id')->ne($oldObject->id)->fetchAll('id'); + foreach(array_reverse(array_filter(explode(',', $oldObject->path))) as $taskID) + { + if(!isset($parentTasks[$taskID])) continue; + + $parentTask = $parentTasks[$taskID]; + if(!helper::isZeroDate($parentTask->estStarted) && $parentTask->estStarted > $postData->startDate) dao::$errors[] = sprintf($this->lang->task->overParentEsStarted, $parentTask->estStarted); + if(!helper::isZeroDate($parentTask->deadline) && $parentTask->deadline < $postData->endDate) dao::$errors[] = sprintf($this->lang->task->overParentDeadline, $parentTask->deadline); + } + if(dao::isError()) return false; + } + $oldObject->estStarted = $postData->startDate; $oldObject->deadline = $postData->endDate; unset($oldObject->openedDate); @@ -3728,7 +3748,7 @@ class taskModel extends model */ public function getChildTasksByList(array $taskIdList): array|false { - $childTasks = $this->dao->select('id,parent')->from(TABLE_TASK)->where('parent')->in($taskIdList)->andWhere('deleted')->eq('0')->fetchGroup('parent', 'id'); + $childTasks = $this->dao->select('id,parent,path,estStarted,deadline')->from(TABLE_TASK)->where('parent')->in($taskIdList)->andWhere('deleted')->eq('0')->fetchGroup('parent', 'id'); $nonStoryChildTasks = $this->dao->select('id,parent')->from(TABLE_TASK)->where('parent')->in($taskIdList)->andWhere('story')->eq('0')->andWhere('deleted')->eq('0')->fetchGroup('parent', 'id'); return array($childTasks, $nonStoryChildTasks); } diff --git a/module/task/ui/batchcreate.html.php b/module/task/ui/batchcreate.html.php index 9a22456b53..9227d8db9f 100644 --- a/module/task/ui/batchcreate.html.php +++ b/module/task/ui/batchcreate.html.php @@ -24,6 +24,7 @@ jsVar('overParentEstStartedLang', isset($parentTask) ? sprintf($lang->task->over jsVar('overParentDeadlineLang', isset($parentTask) ? sprintf($lang->task->overParentDeadline, $parentTask->deadline) : ''); jsVar('taskHasConsumed', $taskConsumed > 0); jsVar('langAddChildTask', $lang->task->addChildTask); +jsVar('taskDateLimit', $project->taskDateLimit); /* zin: Set variables to define picker options for form. */ $storyItem = ''; diff --git a/module/task/ui/batchedit.html.php b/module/task/ui/batchedit.html.php index 86b24479ba..8a159656c3 100644 --- a/module/task/ui/batchedit.html.php +++ b/module/task/ui/batchedit.html.php @@ -27,6 +27,7 @@ jsVar('moduleGroup', $moduleGroup); jsVar('executionID', $executionID); jsVar('childTasks', $childTasks); jsVar('nonStoryChildTasks', $nonStoryChildTasks); +jsVar('childrenDateLimit', $childrenDateLimit); jsVar('tasks', $tasks); jsVar('noPauseStatusList', $noPauseStatusList); jsVar('stories', $stories); @@ -37,7 +38,10 @@ jsVar('noSprintPairs', $noSprintPairs); jsVar('ignoreLang', $lang->project->ignore); jsVar('overParentEstStartedLang', $lang->task->overParentEsStarted); jsVar('overParentDeadlineLang', $lang->task->overParentDeadline); +jsVar('overChildEstStartedLang', $lang->task->overChildEstStarted); +jsVar('overChildDeadlineLang', $lang->task->overChildDeadline); jsVar('manageTeamMemberText', $lang->execution->manageTeamMember); +jsVar('taskDateLimit', empty($project) ? '' : $project->taskDateLimit); /* ====== Define the page structure with zin widgets ====== */ formBatchPanel diff --git a/module/task/ui/create.html.php b/module/task/ui/create.html.php index abdb3a52e7..0ef6939158 100644 --- a/module/task/ui/create.html.php +++ b/module/task/ui/create.html.php @@ -29,6 +29,7 @@ jsVar('showFields', $showFields); jsVar('canViewStory', common::hasPriv('execution', 'storyView')); jsVar('ignoreLang', $lang->project->ignore); jsVar('assignedToOptions', $assignedToOptions); +jsVar('taskDateLimit', $project->taskDateLimit); if(!empty($task->team)) { diff --git a/module/task/ui/edit.html.php b/module/task/ui/edit.html.php index 9cdf5f6a75..7769704b66 100644 --- a/module/task/ui/edit.html.php +++ b/module/task/ui/edit.html.php @@ -34,9 +34,13 @@ jsVar('leftNotEmpty', sprintf($lang->error->gt, $lang->task->left, '0')); jsVar('requiredFields', $config->task->edit->requiredFields); jsVar('+parentEstStarted', !empty($parentTask) ? $parentTask->estStarted : ''); jsVar('+parentDeadline', !empty($parentTask) ? $parentTask->deadline : ''); +jsVar('childDateLimit', $childDateLimit); jsVar('ignoreLang', $lang->project->ignore); jsVar('+overParentEstStartedLang', !empty($parentTask) ? sprintf($lang->task->overParentEsStarted, $parentTask->estStarted) : ''); jsVar('+overParentDeadlineLang', !empty($parentTask) ? sprintf($lang->task->overParentDeadline, $parentTask->deadline) : ''); +jsVar('+overChildEstStartedLang', sprintf($lang->task->overChildEstStarted, $childDateLimit['estStarted'])); +jsVar('+overChildDeadlineLang', sprintf($lang->task->overChildDeadline, $childDateLimit['deadline'])); +jsVar('taskDateLimit', empty($project) ? '' : $project->taskDateLimit); $confirmSyncTip = ''; if(!empty($syncChildren) && !empty($task->children)) $confirmSyncTip = sprintf($lang->task->syncStoryToChildrenTip, 'ID' . implode(', ID', $syncChildren)); @@ -463,6 +467,7 @@ detailBody datePicker ( set::name('estStarted'), + set::disabled(!empty($parentTask) && helper::isZeroDate($parentTask->estStarted) ? true : false), on::change('checkEstStartedAndDeadline'), helper::isZeroDate($task->estStarted) ? null : set::value($task->estStarted) ) @@ -477,6 +482,7 @@ detailBody datePicker ( set::name('deadline'), + set::disabled(!empty($parentTask) && helper::isZeroDate($parentTask->deadline) ? true : false), on::change('checkEstStartedAndDeadline'), helper::isZeroDate($task->deadline) ? null : set::value($task->deadline) ) diff --git a/module/task/zen.php b/module/task/zen.php index 36c66d88a9..ec75c3915c 100644 --- a/module/task/zen.php +++ b/module/task/zen.php @@ -70,6 +70,7 @@ class taskZen extends task $this->view->showFields = $this->config->task->custom->createFields; $this->view->gobackLink = (isset($output['from']) && $output['from'] == 'global') ? $this->createLink('execution', 'task', "executionID={$executionID}") : ''; $this->view->execution = $execution; + $this->view->project = $this->loadModel('project')->fetchById($execution->project); $this->view->storyID = $storyID; $this->view->blockID = helper::isAjaxRequest('modal') ? $this->loadModel('block')->getSpecifiedBlockID('my', 'assigntome', 'assigntome') : 0; $this->view->hideStory = $this->task->isNoStoryExecution($execution); @@ -195,6 +196,7 @@ class taskZen extends task $this->view->title = $execution->name . $this->lang->hyphen . $this->lang->task->batchEdit; $this->view->execution = $execution; + $this->view->project = $this->loadModel('project')->fetchById($execution->project); $this->view->modules = $this->tree->getTaskOptionMenu($executionID, 0, !empty($this->config->task->allModule) ? 'allModule' : ''); } else @@ -247,6 +249,18 @@ class taskZen extends task } list($childTasks, $nonStoryChildTasks) = $this->task->getChildTasksByList(array_keys($tasks)); + $childrenDateLimit = array(); + foreach($childTasks as $parent => $children) + { + $childDateLimit = array('estStarted' => '', 'deadline' => ''); + foreach($children as $child) + { + if(!helper::isZeroDate($child->estStarted) && (empty($childDateLimit['estStarted']) || $childDateLimit['estStarted'] > $child->estStarted)) $childDateLimit['estStarted'] = $child->estStarted; + if(!helper::isZeroDate($child->deadline) && (empty($childDateLimit['deadline']) || $childDateLimit['deadline'] < $child->deadline)) $childDateLimit['deadline'] = $child->deadline; + } + $childrenDateLimit[$parent] = $childDateLimit; + } + $storyPairs = $this->story->getExecutionStoryPairs($executionID, 0, 'all', '', 'full', 'active', 'story', false);; $storyList = $this->story->getByList(array_keys($storyPairs)); $stories = array(); @@ -268,6 +282,7 @@ class taskZen extends task $this->view->moduleGroup = $moduleGroup; $this->view->childTasks = $childTasks; $this->view->nonStoryChildTasks = $nonStoryChildTasks; + $this->view->childrenDateLimit = $childrenDateLimit; $this->view->stories = $stories; $this->view->parentTasks = $this->task->getByIdList($parentTaskIdList); $this->view->noSprintPairs = $this->loadModel('project')->getProjectExecutionPairs(); @@ -301,7 +316,7 @@ class taskZen extends task $executions = !empty($task->project) ? $this->execution->getByProject($task->project, 'all', 0, true) : array(); /* Get task members. */ - $taskMembers = array(); + $taskMembers = $this->view->members; if(!empty($task->team)) { foreach($task->members as $teamAccount) @@ -310,10 +325,6 @@ class taskZen extends task $taskMembers[$teamAccount] = $this->view->members[$teamAccount]; } } - else - { - $taskMembers = $this->view->members; - } /* Get execution stories. */ $moduleID = $task->module; @@ -324,29 +335,34 @@ class taskZen extends task } $stories = $this->story->getExecutionStoryPairs($this->view->execution->id, 0, 'all', $moduleID, 'full', 'active', 'story', false); - $syncChildren = array(); + $syncChildren = array(); + $childDateLimit = array('estStarted' => '', 'deadline' => ''); if(!empty($task->children)) { foreach($task->children as $child) { if(empty($child->story)) $syncChildren[] = $child->id; + if(!helper::isZeroDate($child->estStarted) && (empty($childDateLimit['estStarted']) || $childDateLimit['estStarted'] > $child->estStarted)) $childDateLimit['estStarted'] = $child->estStarted; + if(!helper::isZeroDate($child->deadline) && (empty($childDateLimit['deadline']) || $childDateLimit['deadline'] < $child->deadline)) $childDateLimit['deadline'] = $child->deadline; } } if($this->view->execution->multiple) $manageLink = common::hasPriv('execution', 'manageMembers') ? $this->createLink('execution', 'manageMembers', "execution={$this->view->execution->id}") : ''; if(!$this->view->execution->multiple) $manageLink = common::hasPriv('project', 'manageMembers') ? $this->createLink('project', 'manageMembers', "projectID={$this->view->execution->project}") : ''; - $this->view->title = $this->lang->task->edit . 'TASK' . $this->lang->hyphen . $this->view->task->name; - $this->view->stories = $this->story->addGradeLabel($stories); - $this->view->tasks = $tasks; - $this->view->taskMembers = $taskMembers; - $this->view->users = $this->loadModel('user')->getPairs('nodeleted|noclosed', "{$task->openedBy},{$task->canceledBy},{$task->closedBy}"); - $this->view->showAllModule = isset($this->config->execution->task->allModule) ? $this->config->execution->task->allModule : ''; - $this->view->modules = $this->tree->getTaskOptionMenu($task->execution, 0, $this->view->showAllModule ? 'allModule' : ''); - $this->view->executions = $executions; - $this->view->syncChildren = $syncChildren; - $this->view->parentTask = !empty($task->parent) ? $this->task->getById($task->parent) : null; - $this->view->manageLink = $manageLink; + $this->view->title = $this->lang->task->edit . 'TASK' . $this->lang->hyphen . $this->view->task->name; + $this->view->stories = $this->story->addGradeLabel($stories); + $this->view->tasks = $tasks; + $this->view->taskMembers = $taskMembers; + $this->view->users = $this->loadModel('user')->getPairs('nodeleted|noclosed', "{$task->openedBy},{$task->canceledBy},{$task->closedBy}"); + $this->view->showAllModule = isset($this->config->execution->task->allModule) ? $this->config->execution->task->allModule : ''; + $this->view->modules = $this->tree->getTaskOptionMenu($task->execution, 0, $this->view->showAllModule ? 'allModule' : ''); + $this->view->executions = $executions; + $this->view->syncChildren = $syncChildren; + $this->view->childDateLimit = $childDateLimit; + $this->view->parentTask = !empty($task->parent) ? $this->task->getById($task->parent) : null; + $this->view->manageLink = $manageLink; + $this->view->project = $task->project ? $this->loadModel('project')->fetchById($task->project) : null; $this->display(); } @@ -437,6 +453,7 @@ class taskZen extends task $this->view->title = $this->lang->task->batchCreate; $this->view->execution = $execution; + $this->view->project = $this->loadModel('project')->fetchById($execution->project); $this->view->modules = $modules; $this->view->parent = $taskID; $this->view->storyID = $storyID; @@ -558,12 +575,13 @@ class taskZen extends task ->stripTags($this->config->task->editor->edit['id'], $this->config->allowedTags) ->get(); + $project = $this->loadModel('project')->fetchById($oldTask->project); + $parents = $this->getParentEstStartedAndDeadline(array($task->parent)); + $this->checkLegallyDate($task, $project->taskDateLimit == 'limit', isset($parents[$task->parent]) ? $parents[$task->parent] : null); + $team = $this->post->team ? array_filter($this->post->team) : array(); - if($task->mode && empty($team)) - { - dao::$errors['assignedTo'] = $this->lang->task->teamNotEmpty; - return false; - } + if($task->mode && empty($team)) dao::$errors['assignedTo'] = $this->lang->task->teamNotEmpty; + if(dao::isError()) return false; return $this->loadModel('file')->processImgURL($task, $this->config->task->editor->edit['id'], (string)$this->post->uid); } @@ -1006,6 +1024,31 @@ class taskZen extends task $this->setMenu($this->view->execution->id); } + /** + * 检查任务的开始时间和截止时间是否合法。 + * Check if the start and end time of the task is legal. + * + * @param object $task + * @param bool $isDateLimit + * @param object $parent + * @param int|null $rowID + * @access public + * @return void + */ + public function checkLegallyDate(object $task, bool $isDateLimit, object|null $parent, int|null $rowID = null): void + { + $beginIndex = $rowID === null ? 'estStarted' : "estStarted[$rowID]"; + $endIndex = $rowID === null ? 'deadline' : "deadline[$rowID]"; + + $beginIsZeroDate = helper::isZeroDate($task->estStarted); + $endIsZeroDate = helper::isZeroDate($task->deadline); + if(!$beginIsZeroDate and !$endIsZeroDate and $task->deadline < $task->estStarted) dao::$errors[$endIndex] = $this->lang->task->error->deadlineSmall; + + if(!$isDateLimit || empty($parent)) return; + if(!$beginIsZeroDate && !helper::isZeroDate($parent->estStarted) && $task->estStarted < $parent->estStarted) dao::$errors[$beginIndex] = sprintf($this->lang->task->overParentEsStarted, $parent->estStarted); + if(!$endIsZeroDate && !helper::isZeroDate($parent->deadline) && $task->deadline > $parent->deadline) dao::$errors[$endIndex] = sprintf($this->lang->task->overParentDeadline, $parent->deadline); + } + /** * 检查传入的创建数据是否符合要求。 * Check if the input post meets the requirements. @@ -1018,17 +1061,9 @@ class taskZen extends task protected function checkCreateTask(object $task, array $team): bool { /* Check if the estimate is positive. */ - if($task->estimate < 0) - { - dao::$errors['estimate'] = $this->lang->task->error->recordMinus; - return false; - } - - if($this->post->multiple && empty($team)) - { - dao::$errors['assignedTo'] = $this->lang->task->teamNotEmpty; - return false; - } + if($task->estimate < 0) dao::$errors['estimate'] = $this->lang->task->error->recordMinus; + if($this->post->multiple && empty($team)) dao::$errors['assignedTo'] = $this->lang->task->teamNotEmpty; + if(dao::isError()) return false; /* If the task start and end date must be between the execution start and end date, check if the task start and end date accord with the conditions. */ if(!empty($this->config->limitTaskDate)) @@ -1037,12 +1072,9 @@ class taskZen extends task if(dao::isError()) return false; } - /* Check start and end date. */ - if(!helper::isZeroDate($task->deadline) && $task->estStarted > $task->deadline) - { - dao::$errors['deadline'] = $this->lang->task->error->deadlineSmall; - return false; - } + $project = $this->dao->findById($task->project)->from(TABLE_PROJECT)->fetch(); + $parents = $this->getParentEstStartedAndDeadline(array($task->parent)); + $this->checkLegallyDate($task, $project->taskDateLimit == 'limit', isset($parents[$task->parent]) ? $parents[$task->parent] : null); return !dao::isError(); } @@ -1060,26 +1092,26 @@ class taskZen extends task { /* Set required fields. */ $requiredFields = $this->config->task->create->requiredFields; - $execution = $this->loadModel('execution')->getById($executionID); + $execution = $this->loadModel('execution')->fetchById($executionID); if($this->task->isNoStoryExecution($execution)) $requiredFields = str_replace(',story,', ',', ',' . $requiredFields . ','); $requiredFields = array_filter(explode(',', $requiredFields)); + $levels = array(); + $project = $this->loadModel('project')->fetchById($execution->project); + $parentIdList = array_filter(array_column($tasks, 'parent', 'parent')); + $parents = $this->getParentEstStartedAndDeadline($parentIdList); foreach($tasks as $rowIndex => $task) { - if(mb_strlen($task->name) > 255) - { - dao::$errors["name[$rowIndex]"] = sprintf($this->lang->task->error->length, 255); - } + $levels[$task->level] = $rowIndex; + + if(mb_strlen($task->name) > 255) dao::$errors["name[$rowIndex]"] = sprintf($this->lang->task->error->length, 255); if(!empty($this->post->estimate[$rowIndex]) and !preg_match("/^[0-9]+(.[0-9]+)?$/", (string)$this->post->estimate[$rowIndex])) { dao::$errors["estimate[$rowIndex]"] = $this->lang->task->error->estimateNumber; } /* If the task start and end date must be between the execution start and end date, check if the task start and end date accord with the conditions. */ - if(!empty($this->config->limitTaskDate)) - { - $this->task->checkEstStartedAndDeadline($executionID, (string)$task->estStarted, (string)$task->deadline); - } + if(!empty($this->config->limitTaskDate)) $this->task->checkEstStartedAndDeadline($executionID, (string)$task->estStarted, (string)$task->deadline); /* Check start and end date. */ if(!helper::isZeroDate($task->deadline) && $task->deadline < $task->estStarted) @@ -1087,24 +1119,21 @@ class taskZen extends task dao::$errors["deadline[$rowIndex]"] = $this->lang->task->error->deadlineSmall; } + $parentTask = isset($parents[$task->parent]) ? $parents[$task->parent] : null; + if($task->level > 0 && isset($levels[$task->level - 1])) $parentTask = zget($tasks, $levels[$task->level - 1], null); + $this->checkLegallyDate($task, $project->taskDateLimit == 'limit', $parentTask, $rowIndex); + /* Check if the estimate is positive. */ - if($task->estimate < 0) - { - dao::$errors["estimate[$rowIndex]"] = $this->lang->task->error->recordMinus; - } + if($task->estimate < 0) dao::$errors["estimate[$rowIndex]"] = $this->lang->task->error->recordMinus; /* Check if the required fields are empty. */ foreach($requiredFields as $field) { - if(empty($task->$field)) - { - dao::$errors[$field . "[$rowIndex]"] = sprintf($this->lang->error->notempty, $this->lang->task->$field); - } + if(empty($task->$field)) dao::$errors[$field . "[$rowIndex]"] = sprintf($this->lang->error->notempty, $this->lang->task->$field); } } - if(dao::isError()) return false; - return true; + return !dao::isError(); } /** @@ -1118,6 +1147,10 @@ class taskZen extends task */ protected function checkBatchEditTask(array $tasks, array $oldTasks): bool { + $oldTask = reset($oldTasks); + $project = $this->loadModel('project')->fetchById($oldTask->project); + $parentIdList = array_filter(array_column($tasks, 'parent', 'parent')); + $parents = $this->getParentEstStartedAndDeadline($parentIdList); foreach($tasks as $taskID => $task) { $oldTask = $oldTasks[$taskID]; @@ -1131,11 +1164,12 @@ class taskZen extends task if($task->consumed < 0 ) dao::$errors["consumed[{$taskID}]"] = (array)sprintf($this->lang->task->error->recordMinus, $this->lang->task->consumedThisTime); if($task->left < 0) dao::$errors["left[$taskID]"] = (array)sprintf($this->lang->task->error->recordMinus, $this->lang->task->leftAB); - if(!empty($this->config->limitTaskDate)) $this->task->checkEstStartedAndDeadline($oldTask->execution, (string)$task->estStarted, (string)$task->deadline, "task:{$taskID} "); + if(!empty($this->config->limitTaskDate)) $this->task->checkEstStartedAndDeadline($oldTask->execution, (string)$task->estStarted, (string)$task->deadline, $taskID); if($task->status == 'cancel') continue; if($task->status == 'done' && !$task->consumed) dao::$errors["consumed[{$taskID}]"] = (array)sprintf($this->lang->error->notempty, $this->lang->task->consumedThisTime); - if(!empty($task->deadline) && $task->estStarted > $task->deadline) dao::$errors["deadline[{$taskID}]"] = (array)$this->lang->task->error->deadlineSmall; + + $this->checkLegallyDate($task, $project->taskDateLimit == 'limit', isset($parents[$task->parent]) ? $parents[$task->parent] : null, $taskID); } return !dao::isError(); } @@ -1485,11 +1519,11 @@ class taskZen extends task unset($task->team); } - if($task->isParent) + if($task->isParent && strpos($task->name, "[{$this->lang->task->parentAB}]") === false) { $task->name = '[' . $this->lang->task->parentAB . '] ' . $task->name; } - elseif($task->parent > 0) + elseif($task->parent > 0 && strpos($task->name, "[{$this->lang->task->childrenAB}]") === false) { $task->name = '[' . $this->lang->task->childrenAB . '] ' . $task->name; } @@ -2047,4 +2081,39 @@ class taskZen extends task return $options; } + + /** + * 获取父任务的开始时间和截止时间。 + * Get the start and end time of the parent task. + * + * @param array $parentIdList + * @access protected + * @return array + */ + protected function getParentEstStartedAndDeadline(array $parentIdList): array + { + $pathPairs = $this->dao->select('id,path')->from(TABLE_TASK)->where('id')->in($parentIdList)->fetchPairs(); + if(empty($pathPairs)) return array(); + + $allParentIdList = array_filter(array_unique(explode(',', implode(',', $pathPairs)))); + $allParents = $this->dao->select('id,estStarted,deadline')->from(TABLE_TASK)->where('id')->in($allParentIdList)->fetchAll('id'); + $parents = array(); + foreach($pathPairs as $parentID => $path) + { + $parent = new stdClass(); + $parent->estStarted = null; + $parent->deadline = null; + foreach(array_reverse(array_filter(explode(',', $path))) as $taskID) + { + if(!isset($allParents[$taskID])) continue; + + $task = $allParents[$taskID]; + if(empty($parent->estStarted) && !helper::isZeroDate($task->estStarted)) $parent->estStarted = $task->estStarted; + if(empty($parent->deadline) && !helper::isZeroDate($task->deadline)) $parent->deadline = $task->deadline; + if(!empty($parent->estStarted) && !empty($parent->deadline)) break; + } + $parents[$parentID] = $parent; + } + return $parents; + } }