mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 02:51:54 +03:00
UBER-1052: Fix remainings (#3844)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
0b3b5725ca
commit
3c0ff4c049
@ -34,6 +34,7 @@ const object: AttachedData<Issue> = {
|
||||
subIssues: 0,
|
||||
parents: [],
|
||||
reportedTime: 0,
|
||||
remainingTime: 0,
|
||||
estimation: 0,
|
||||
reports: 0,
|
||||
childInfo: []
|
||||
@ -96,6 +97,7 @@ async function genIssue (client: TxOperations, statuses: Ref<IssueStatus>[]): Pr
|
||||
dueDate: object.dueDate,
|
||||
parents: [],
|
||||
reportedTime: 0,
|
||||
remainingTime: 0,
|
||||
estimation: object.estimation,
|
||||
reports: 0,
|
||||
relations: [],
|
||||
|
@ -39,8 +39,10 @@ import {
|
||||
TProjectIssueTargetOptions,
|
||||
TRelatedIssueTarget,
|
||||
TTimeSpendReport,
|
||||
TTypeEstimation,
|
||||
TTypeIssuePriority,
|
||||
TTypeMilestoneStatus,
|
||||
TTypeRemainingTime,
|
||||
TTypeReportedTime
|
||||
} from './types'
|
||||
import { defineViewlets } from './viewlets'
|
||||
@ -430,7 +432,9 @@ export function createModel (builder: Builder): void {
|
||||
TTimeSpendReport,
|
||||
TTypeReportedTime,
|
||||
TProjectIssueTargetOptions,
|
||||
TRelatedIssueTarget
|
||||
TRelatedIssueTarget,
|
||||
TTypeEstimation,
|
||||
TTypeRemainingTime
|
||||
)
|
||||
|
||||
defineViewlets(builder)
|
||||
|
@ -171,6 +171,22 @@ async function fixEstimation (client: MigrationClient): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function fixRemainingTime (client: MigrationClient): Promise<void> {
|
||||
while (true) {
|
||||
const issues = await client.find<Issue>(DOMAIN_TASK, { remainingTime: { $exists: false } }, { limit: 1000 })
|
||||
for (const issue of issues) {
|
||||
await client.update(
|
||||
DOMAIN_TASK,
|
||||
{ _id: issue._id },
|
||||
{ remainingTime: Math.max(0, issue.estimation - issue.reportedTime) }
|
||||
)
|
||||
}
|
||||
if (issues.length === 0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function moveIssues (client: MigrationClient): Promise<void> {
|
||||
const docs = await client.find(DOMAIN_TRACKER, { _class: tracker.class.Issue })
|
||||
if (docs.length > 0) {
|
||||
@ -218,6 +234,10 @@ export const trackerOperation: MigrateOperation = {
|
||||
{
|
||||
state: 'estimationDayToHour',
|
||||
func: fixEstimation
|
||||
},
|
||||
{
|
||||
state: 'fixRemainingTime',
|
||||
func: fixRemainingTime
|
||||
}
|
||||
])
|
||||
},
|
||||
|
@ -43,7 +43,6 @@ export default mergeIds(trackerId, tracker, {
|
||||
Unarchive: '' as IntlString,
|
||||
UnarchiveConfirm: '' as IntlString,
|
||||
AllProjects: '' as IntlString,
|
||||
RemainingTime: '' as IntlString,
|
||||
MapRelatedIssues: '' as IntlString
|
||||
},
|
||||
activity: {
|
||||
|
@ -138,7 +138,21 @@ export function definePresenters (builder: Builder): void {
|
||||
classPresenter(
|
||||
builder,
|
||||
tracker.class.TypeReportedTime,
|
||||
view.component.NumberPresenter,
|
||||
tracker.component.TimePresenter,
|
||||
tracker.component.ReportedTimeEditor
|
||||
)
|
||||
|
||||
classPresenter(
|
||||
builder,
|
||||
tracker.class.TypeEstimation,
|
||||
tracker.component.TimePresenter,
|
||||
tracker.component.EstimationValueEditor
|
||||
)
|
||||
|
||||
classPresenter(
|
||||
builder,
|
||||
tracker.class.TypeRemainingTime,
|
||||
tracker.component.TimePresenter,
|
||||
tracker.component.EstimationValueEditor
|
||||
)
|
||||
}
|
||||
|
@ -143,17 +143,31 @@ export class TRelatedIssueTarget extends TDoc implements RelatedIssueTarget {
|
||||
|
||||
rule!: RelatedClassRule | RelatedSpaceRule
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
||||
export function TypeReportedTime (): Type<number> {
|
||||
return { _class: tracker.class.TypeReportedTime, label: core.string.Number }
|
||||
return { _class: tracker.class.TypeReportedTime, label: tracker.string.ReportedTime }
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function TypeRemainingTime (): Type<number> {
|
||||
return { _class: tracker.class.TypeRemainingTime, label: tracker.string.RemainingTime }
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function TypeEstimation (): Type<number> {
|
||||
return { _class: tracker.class.TypeEstimation, label: tracker.string.Estimation }
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@Model(tracker.class.Issue, task.class.Task)
|
||||
@UX(tracker.string.Issue, tracker.icon.Issue, 'TSK', 'title')
|
||||
export class TIssue extends TTask implements Issue {
|
||||
@ -229,18 +243,15 @@ export class TIssue extends TTask implements Issue {
|
||||
@Index(IndexKind.Indexed)
|
||||
milestone!: Ref<Milestone> | null
|
||||
|
||||
@Prop(TypeNumber(), tracker.string.Estimation)
|
||||
@Prop(TypeEstimation(), tracker.string.Estimation)
|
||||
estimation!: number
|
||||
|
||||
@Prop(TypeReportedTime(), tracker.string.ReportedTime)
|
||||
@ReadOnly()
|
||||
reportedTime!: number
|
||||
|
||||
// A fully virtual property with calculated content.
|
||||
// TODO: Add proper support for this kind of fields
|
||||
@Prop(TypeNumber(), tracker.string.RemainingTime)
|
||||
@Prop(TypeRemainingTime(), tracker.string.RemainingTime)
|
||||
@ReadOnly()
|
||||
@Hidden()
|
||||
remainingTime!: number
|
||||
|
||||
@Prop(Collection(tracker.class.TimeSpendReport), tracker.string.TimeSpendReports)
|
||||
@ -283,7 +294,7 @@ export class TIssueTemplate extends TDoc implements IssueTemplate {
|
||||
@Prop(TypeRef(tracker.class.Milestone), tracker.string.Milestone)
|
||||
milestone!: Ref<Milestone> | null
|
||||
|
||||
@Prop(TypeNumber(), tracker.string.Estimation)
|
||||
@Prop(TypeEstimation(), tracker.string.Estimation)
|
||||
estimation!: number
|
||||
|
||||
@Prop(ArrOf(TypeRef(tracker.class.IssueTemplate)), tracker.string.IssueTemplate)
|
||||
@ -386,3 +397,11 @@ export class TMilestone extends TDoc implements Milestone {
|
||||
@UX(core.string.Number)
|
||||
@Model(tracker.class.TypeReportedTime, core.class.Type)
|
||||
export class TTypeReportedTime extends TType {}
|
||||
|
||||
@UX(core.string.Number)
|
||||
@Model(tracker.class.TypeEstimation, core.class.Type)
|
||||
export class TTypeEstimation extends TType {}
|
||||
|
||||
@UX(core.string.Number)
|
||||
@Model(tracker.class.TypeRemainingTime, core.class.Type)
|
||||
export class TTypeRemainingTime extends TType {}
|
||||
|
@ -24,14 +24,28 @@ import tracker from './plugin'
|
||||
import tags from '@hcengineering/tags'
|
||||
|
||||
export const issuesOptions = (kanban: boolean): ViewOptionsModel => ({
|
||||
groupBy: ['status', 'assignee', 'priority', 'component', 'milestone', 'createdBy', 'modifiedBy'],
|
||||
groupBy: [
|
||||
'status',
|
||||
'assignee',
|
||||
'priority',
|
||||
'component',
|
||||
'milestone',
|
||||
'createdBy',
|
||||
'modifiedBy',
|
||||
'estimation',
|
||||
'remainingTime',
|
||||
'reportedTime'
|
||||
],
|
||||
orderBy: [
|
||||
['status', SortingOrder.Ascending],
|
||||
['priority', SortingOrder.Descending],
|
||||
['modifiedOn', SortingOrder.Descending],
|
||||
['createdOn', SortingOrder.Descending],
|
||||
['dueDate', SortingOrder.Ascending],
|
||||
['rank', SortingOrder.Ascending]
|
||||
['rank', SortingOrder.Ascending],
|
||||
['estimation', SortingOrder.Descending],
|
||||
['remainingTime', SortingOrder.Descending],
|
||||
['reportedTime', SortingOrder.Descending]
|
||||
],
|
||||
other: [
|
||||
{
|
||||
@ -202,6 +216,7 @@ export function defineViewlets (builder: Builder): void {
|
||||
'component',
|
||||
'milestone',
|
||||
'estimation',
|
||||
'remainingTime',
|
||||
'status',
|
||||
'dueDate',
|
||||
'attachedTo',
|
||||
@ -246,6 +261,7 @@ export function defineViewlets (builder: Builder): void {
|
||||
'dueDate',
|
||||
'milestone',
|
||||
'estimation',
|
||||
'remainingTime',
|
||||
'createdBy',
|
||||
'modifiedBy'
|
||||
]
|
||||
@ -287,6 +303,7 @@ export function defineViewlets (builder: Builder): void {
|
||||
'dueDate',
|
||||
'milestone',
|
||||
'estimation',
|
||||
'remainingTime',
|
||||
'createdBy',
|
||||
'modifiedBy'
|
||||
]
|
||||
@ -328,6 +345,7 @@ export function defineViewlets (builder: Builder): void {
|
||||
'dueDate',
|
||||
'component',
|
||||
'estimation',
|
||||
'remainingTime',
|
||||
'createdBy',
|
||||
'modifiedBy'
|
||||
]
|
||||
@ -355,7 +373,17 @@ export function defineViewlets (builder: Builder): void {
|
||||
},
|
||||
configOptions: {
|
||||
strict: true,
|
||||
hiddenKeys: ['milestone', 'estimation', 'component', 'title', 'description', 'createdBy', 'modifiedBy']
|
||||
hiddenKeys: [
|
||||
'milestone',
|
||||
'estimation',
|
||||
'remainingTime',
|
||||
'reportedTime',
|
||||
'component',
|
||||
'title',
|
||||
'description',
|
||||
'createdBy',
|
||||
'modifiedBy'
|
||||
]
|
||||
},
|
||||
config: [
|
||||
// { key: '', presenter: tracker.component.PriorityEditor, props: { kind: 'list', size: 'small' } },
|
||||
|
@ -140,6 +140,7 @@
|
||||
dueDate: null,
|
||||
parents: [],
|
||||
reportedTime: 0,
|
||||
remainingTime: 0,
|
||||
estimation: template.estimation,
|
||||
reports: 0,
|
||||
relations: [{ _id: id, _class: recruit.class.Vacancy }],
|
||||
|
@ -388,6 +388,7 @@
|
||||
? [{ parentId: parentIssue._id, parentTitle: parentIssue.title }, ...parentIssue.parents]
|
||||
: [],
|
||||
reportedTime: 0,
|
||||
remainingTime: 0,
|
||||
estimation: object.estimation,
|
||||
reports: 0,
|
||||
relations: relatedTo !== undefined ? [{ _id: relatedTo._id, _class: relatedTo._class }] : [],
|
||||
|
@ -92,6 +92,7 @@
|
||||
dueDate: null,
|
||||
parents,
|
||||
reportedTime: 0,
|
||||
remainingTime: 0,
|
||||
estimation: subIssue.estimation,
|
||||
reports: 0,
|
||||
relations: [],
|
||||
|
@ -25,6 +25,7 @@
|
||||
import SubIssuesEstimations from './SubIssuesEstimations.svelte'
|
||||
import TimeSpendReportPopup from './TimeSpendReportPopup.svelte'
|
||||
import TimeSpendReports from './TimeSpendReports.svelte'
|
||||
import TimePresenter from './TimePresenter.svelte'
|
||||
|
||||
export let format: 'text' | 'password' | 'number'
|
||||
export let kind: EditStyle = 'search-style'
|
||||
@ -104,6 +105,10 @@
|
||||
>
|
||||
<EstimationStatsPresenter value={object} estimation={_value} />
|
||||
</div>
|
||||
<Label label={tracker.string.RemainingTime} />
|
||||
<div class="ml-2 mr-4">
|
||||
<TimePresenter value={object.remainingTime} />
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
|
@ -0,0 +1,79 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import type { ButtonSize } from '@hcengineering/ui'
|
||||
import { EditBox, Label, showPopup, eventToHTMLElement, Button } from '@hcengineering/ui'
|
||||
import { EditBoxPopup } from '@hcengineering/view-resources'
|
||||
import TimePresenter from './TimePresenter.svelte'
|
||||
|
||||
// export let label: IntlString
|
||||
export let placeholder: IntlString
|
||||
export let value: number | undefined
|
||||
export let autoFocus: boolean = false
|
||||
// export let maxWidth: string = '10rem'
|
||||
export let onChange: (value: number | undefined) => void
|
||||
export let kind: 'no-border' | 'link' | 'button' = 'no-border'
|
||||
export let readonly = false
|
||||
export let size: ButtonSize = 'small'
|
||||
export let justify: 'left' | 'center' = 'center'
|
||||
export let width: string | undefined = 'fit-content'
|
||||
|
||||
let shown: boolean = false
|
||||
|
||||
function _onchange (ev: Event) {
|
||||
const value = (ev.target as HTMLInputElement).valueAsNumber
|
||||
if (Number.isFinite(value)) {
|
||||
onChange(value)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if kind === 'button' || kind === 'link'}
|
||||
<Button
|
||||
kind={kind === 'button' ? 'regular' : kind}
|
||||
{size}
|
||||
{justify}
|
||||
{width}
|
||||
on:click={(ev) => {
|
||||
if (!shown && !readonly) {
|
||||
showPopup(EditBoxPopup, { value, format: 'number' }, eventToHTMLElement(ev), (res) => {
|
||||
if (Number.isFinite(res)) {
|
||||
value = res
|
||||
onChange(value)
|
||||
}
|
||||
shown = false
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="content">
|
||||
{#if value != null}
|
||||
<span class="caption-color overflow-label pointer-events-none"><TimePresenter {value} /></span>
|
||||
{:else}
|
||||
<span class="content-dark-color pointer-events-none"><Label label={placeholder} /></span>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Button>
|
||||
{:else if readonly}
|
||||
{#if value != null}
|
||||
<span class="caption-color overflow-label"><TimePresenter {value} /></span>
|
||||
{:else}
|
||||
<span class="content-dark-color"><Label label={placeholder} /></span>
|
||||
{/if}
|
||||
{:else}
|
||||
<EditBox {placeholder} bind:value format={'number'} {autoFocus} on:change={_onchange} />
|
||||
{/if}
|
@ -62,9 +62,13 @@
|
||||
|
||||
{#if kind === 'link'}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div id="ReportedTimeEditor" class="link-container {size} flex-between" on:click={showReports}>
|
||||
<div
|
||||
id="ReportedTimeEditor"
|
||||
class="link-container antiButton link {size} flex-grow flex-between"
|
||||
on:click={showReports}
|
||||
>
|
||||
{#if value !== undefined}
|
||||
<span class="overflow-label">
|
||||
<span class="flex-row-center">
|
||||
<TimePresenter {value} />
|
||||
{#if childTime !== 0}
|
||||
/ <TimePresenter value={childTime} />
|
||||
@ -78,7 +82,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{:else if value !== undefined}
|
||||
<span class="overflow-label">
|
||||
<span class="flex-row-center">
|
||||
<TimePresenter {value} />
|
||||
{#if childTime !== 0}
|
||||
/ <TimePresenter value={childTime} />
|
||||
@ -90,24 +94,7 @@
|
||||
|
||||
<style lang="scss">
|
||||
.link-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 0.875rem;
|
||||
width: 100%;
|
||||
color: var(--theme-caption-color);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
|
||||
&.small {
|
||||
height: 1.5rem;
|
||||
}
|
||||
&.medium {
|
||||
height: 2rem;
|
||||
}
|
||||
&.large {
|
||||
height: 2.25rem;
|
||||
}
|
||||
padding: 0px 0.75rem;
|
||||
.add-action {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
@ -46,7 +46,7 @@
|
||||
<Label label={tracker.string.ReportedTime} />:
|
||||
<span class="caption-color"><TimePresenter value={reportedTime} /></span>.
|
||||
<Label label={tracker.string.TimeSpendReports} />:
|
||||
<span class="caption-color"><TimePresenter value={floorFractionDigits(total / 8, 3)} /></span>
|
||||
<span class="caption-color"><TimePresenter value={floorFractionDigits(total, 3)} /></span>
|
||||
</span>
|
||||
</svelte:fragment>
|
||||
<TimeSpendReportsList {reports} />
|
||||
|
@ -142,6 +142,9 @@ import ProjectSpacePresenter from './components/projects/ProjectSpacePresenter.s
|
||||
|
||||
import { get } from 'svelte/store'
|
||||
|
||||
import TimePresenter from './components/issues/timereport/TimePresenter.svelte'
|
||||
import EstimationValueEditor from './components/issues/timereport/EstimationValueEditor.svelte'
|
||||
|
||||
export { default as SubIssueList } from './components/issues/edit/SubIssueList.svelte'
|
||||
export { default as IssueStatusIcon } from './components/issues/IssueStatusIcon.svelte'
|
||||
export { default as StatusPresenter } from './components/issues/StatusPresenter.svelte'
|
||||
@ -479,7 +482,9 @@ export default async (): Promise<Resources> => ({
|
||||
ProjectFilterValuePresenter,
|
||||
ComponentFilterValuePresenter,
|
||||
EditRelatedTargets,
|
||||
EditRelatedTargetsPopup
|
||||
EditRelatedTargetsPopup,
|
||||
TimePresenter,
|
||||
EstimationValueEditor
|
||||
},
|
||||
completion: {
|
||||
IssueQuery: async (client: Client, query: string, filter?: { in?: RelatedDocument[], nin?: RelatedDocument[] }) =>
|
||||
|
@ -261,6 +261,7 @@ export default mergeIds(trackerId, tracker, {
|
||||
|
||||
Estimation: '' as IntlString,
|
||||
ReportedTime: '' as IntlString,
|
||||
RemainingTime: '' as IntlString,
|
||||
TimeSpendReport: '' as IntlString,
|
||||
TimeSpendReportAdd: '' as IntlString,
|
||||
TimeSpendReports: '' as IntlString,
|
||||
@ -355,6 +356,8 @@ export default mergeIds(trackerId, tracker, {
|
||||
MilestoneDatePresenter: '' as AnyComponent,
|
||||
EditMilestone: '' as AnyComponent,
|
||||
ReportedTimeEditor: '' as AnyComponent,
|
||||
TimePresenter: '' as AnyComponent,
|
||||
EstimationValueEditor: '' as AnyComponent,
|
||||
TimeSpendReport: '' as AnyComponent,
|
||||
EstimationEditor: '' as AnyComponent,
|
||||
TemplateEstimationEditor: '' as AnyComponent,
|
||||
|
@ -208,6 +208,9 @@ export interface Issue extends Task {
|
||||
// Estimation in man hours
|
||||
estimation: number
|
||||
|
||||
// Remaining time in man hours
|
||||
remainingTime: number
|
||||
|
||||
// ReportedTime time, auto updated using trigger.
|
||||
reportedTime: number
|
||||
// Collection of reportedTime entries, for proper time estimations per person.
|
||||
@ -393,6 +396,8 @@ export default plugin(trackerId, {
|
||||
TypeMilestoneStatus: '' as Ref<Class<Type<MilestoneStatus>>>,
|
||||
TimeSpendReport: '' as Ref<Class<TimeSpendReport>>,
|
||||
TypeReportedTime: '' as Ref<Class<Type<number>>>,
|
||||
TypeEstimation: '' as Ref<Class<Type<number>>>,
|
||||
TypeRemainingTime: '' as Ref<Class<Type<number>>>,
|
||||
RelatedIssueTarget: '' as Ref<Class<RelatedIssueTarget>>
|
||||
},
|
||||
ids: {
|
||||
|
@ -77,7 +77,7 @@
|
||||
}
|
||||
mergedModel.groupBy = Array.from(new Set([...mergedModel.groupBy, ...customAttributes]))
|
||||
mergedModel.groupBy = mergedModel.groupBy.filter((it, idx, arr) => arr.indexOf(it) === idx)
|
||||
mergedModel.orderBy = mergedModel.orderBy.filter((it, idx, arr) => arr.indexOf(it) === idx)
|
||||
mergedModel.orderBy = mergedModel.orderBy.filter((it, idx, arr) => arr.findIndex((q) => it[0] === q[0]) === idx)
|
||||
mergedModel.other = mergedModel.other.filter((it, idx, arr) => arr.findIndex((q) => q.key === it.key) === idx)
|
||||
|
||||
showPopup(
|
||||
|
@ -270,6 +270,7 @@ async function doTimeReportUpdate (cud: TxCUD<TimeSpendReport>, tx: Tx, control:
|
||||
]
|
||||
const [currentIssue] = await control.findAll(tracker.class.Issue, { _id: parentTx.objectId }, { limit: 1 })
|
||||
currentIssue.reportedTime += ccud.attributes.value
|
||||
currentIssue.remainingTime = Math.max(0, currentIssue.estimation - currentIssue.reportedTime)
|
||||
updateIssueParentEstimations(currentIssue, res, control, currentIssue.parents, currentIssue.parents)
|
||||
return res
|
||||
}
|
||||
@ -294,6 +295,7 @@ async function doTimeReportUpdate (cud: TxCUD<TimeSpendReport>, tx: Tx, control:
|
||||
)
|
||||
currentIssue.reportedTime -= doc.value
|
||||
currentIssue.reportedTime += upd.operations.value
|
||||
currentIssue.remainingTime = Math.max(0, currentIssue.estimation - currentIssue.reportedTime)
|
||||
}
|
||||
|
||||
updateIssueParentEstimations(currentIssue, res, control, currentIssue.parents, currentIssue.parents)
|
||||
@ -317,6 +319,7 @@ async function doTimeReportUpdate (cud: TxCUD<TimeSpendReport>, tx: Tx, control:
|
||||
]
|
||||
const [currentIssue] = await control.findAll(tracker.class.Issue, { _id: parentTx.objectId }, { limit: 1 })
|
||||
currentIssue.reportedTime -= doc.value
|
||||
currentIssue.remainingTime = Math.max(0, currentIssue.estimation - currentIssue.reportedTime)
|
||||
updateIssueParentEstimations(currentIssue, res, control, currentIssue.parents, currentIssue.parents)
|
||||
return res
|
||||
}
|
||||
@ -377,12 +380,23 @@ async function doIssueUpdate (
|
||||
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(updateTx.operations, 'estimation') ||
|
||||
Object.prototype.hasOwnProperty.call(updateTx.operations, 'reportedTime')
|
||||
Object.prototype.hasOwnProperty.call(updateTx.operations, 'reportedTime') ||
|
||||
(Object.prototype.hasOwnProperty.call(updateTx.operations, '$inc') &&
|
||||
Object.prototype.hasOwnProperty.call(updateTx.operations.$inc, 'reportedTime')) ||
|
||||
(Object.prototype.hasOwnProperty.call(updateTx.operations, '$dec') &&
|
||||
Object.prototype.hasOwnProperty.call(updateTx.operations.$inc, 'reportedTime'))
|
||||
) {
|
||||
const issue = await getCurrentIssue()
|
||||
|
||||
issue.estimation = updateTx.operations.estimation ?? issue.estimation
|
||||
issue.reportedTime = updateTx.operations.reportedTime ?? issue.reportedTime
|
||||
issue.remainingTime = Math.max(0, issue.estimation - issue.reportedTime)
|
||||
|
||||
res.push(
|
||||
control.txFactory.createTxUpdateDoc(tracker.class.Issue, issue.space, issue._id, {
|
||||
remainingTime: issue.remainingTime
|
||||
})
|
||||
)
|
||||
|
||||
updateIssueParentEstimations(issue, res, control, issue.parents, issue.parents)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user