evmtools-node
Version:
このライブラリは、プライムブレインズ社で利用している「進捗管理ツール(Excel)」ファイルを読み込み、 プロジェクトの進捗状況や要員別の作業量を可視化するためのライブラリです。
259 lines • 12.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProjectService = void 0;
const tidy_1 = require("@tidyjs/tidy");
const common_1 = require("../common");
class ProjectService {
constructor() {
/**
* existingに対してincomingをマージする。おなじプロジェクト名でおなじ基準日のデータは上書きする。
* @param existing
* @param incoming
* @returns
*/
this.mergeProjectStatistics = (existing, incoming) => {
const map = new Map();
for (const stat of existing) {
const key = `${stat.projectName}_${stat.baseDate}`;
map.set(key, stat);
}
for (const stat of incoming) {
const key = `${stat.projectName}_${stat.baseDate}`;
map.set(key, stat); // 同じprojectNameでも日付が違えば別物として扱う
}
// return Array.from(map.values());
// 基準日で降順ソート(新しい順)
return Array.from(map.values()).sort((a, b) => new Date(b.baseDate).getTime() - new Date(a.baseDate).getTime());
};
/**
* 欠落している間のデータを補間して返す
* (たとえば、土日データを金曜日のデータで補間しています)
* @param projectStatisticsArray
* @returns
*/
this.fillMissingDates = (projectStatisticsArray) => {
const filledStats = [];
if (projectStatisticsArray.length === 0)
return filledStats;
const sorted = [...projectStatisticsArray].sort((a, b) => new Date(a.baseDate).getTime() - new Date(b.baseDate).getTime());
let prev = sorted[0];
filledStats.push(prev);
for (let i = 1; i < sorted.length; i++) {
const current = sorted[i];
const date = new Date(prev.baseDate);
const targetDate = new Date(current.baseDate);
date.setDate(date.getDate() + 1);
while (date < targetDate) {
const clone = {
...prev,
baseDate: (0, common_1.dateStr)(date),
};
filledStats.push(clone);
date.setDate(date.getDate() + 1);
}
filledStats.push(current);
prev = current;
}
const final = filledStats.sort((a, b) => new Date(b.baseDate).getTime() - new Date(a.baseDate).getTime());
return final;
};
}
calculateTaskDiffs(now, prev) {
const prevTasks = prev.toTaskRows();
// key:TaskRow#id, value: TaskRow のMap
const prevTasksMap = new Map(prevTasks.map((row) => [row.id, row]));
const nowTasks = now.toTaskRows();
const nowTasksMap = new Map(nowTasks.map((row) => [row.id, row]));
const diffs = [];
// 変更 or 追加 の処理を1ループにまとめる
// nowTaskはあるがprevTaskはない、時がある
for (const nowTask of nowTasks) {
// isLeaf なタスクのみ後続処理
if (!nowTask.isLeaf)
continue;
const prevTask = prevTasksMap.get(nowTask.id);
const isNew = !prevTask; // prevTaskがなかったらNew
const deltaProgressRate = delta(nowTask.progressRate, prevTask?.progressRate);
const deltaPV = delta(nowTask.pv, prevTask?.pv);
const deltaEV = delta(nowTask.ev, prevTask?.ev);
// const deltaSPI = delta(nowTask.spi, prevTask.spi)
const hasAnyChange = isNew ||
[deltaProgressRate, deltaPV, deltaEV].some((d) => d !== undefined && d !== 0);
// 個々の変更アリナシ
const hasProgressRateDiff = deltaProgressRate !== undefined && deltaProgressRate !== 0;
const hasPvDiff = deltaPV !== undefined && deltaPV !== 0;
const hasEvDiff = deltaEV !== undefined && deltaEV !== 0;
const fullName = now.getFullTaskName(nowTask);
const isOverdueAt = nowTask.isOverdueAt(now.baseDate);
const workload = nowTask.workload;
const prevBaseDate = prevTask ? prev.baseDate : undefined;
const currentBaseDate = now.baseDate;
const baseDate = currentBaseDate;
const daysOverdueAt = (0, common_1.formatRelativeDaysNumber)(baseDate, nowTask.endDate);
const daysStrOverdueAt = (0, common_1.formatRelativeDays)(baseDate, nowTask.endDate);
diffs.push({
id: nowTask.id,
name: nowTask.name,
fullName,
assignee: nowTask.assignee,
parentId: nowTask.parentId,
deltaProgressRate,
deltaPV,
deltaEV,
prevPV: prevTask?.pv,
prevEV: prevTask?.ev,
currentPV: nowTask.pv,
currentEV: nowTask.ev,
prevProgressRate: prevTask?.progressRate,
currentProgressRate: nowTask.progressRate,
hasDiff: hasAnyChange,
hasProgressRateDiff,
hasPvDiff,
hasEvDiff,
diffType: isNew ? 'added' : hasAnyChange ? 'modified' : 'none',
finished: nowTask.finished,
isOverdueAt,
workload,
prevBaseDate,
currentBaseDate,
baseDate,
daysOverdueAt,
daysStrOverdueAt,
currentTask: nowTask,
prevTask,
});
// console.log(nowTask.id)
// console.log(nowTask.plotMap)
}
// 削除されたタスク
for (const prevTask of prevTasks) {
// isLeaf かつ nowにないタスクのみ後続処理
if (!prevTask.isLeaf || nowTasksMap.has(prevTask.id))
continue;
const deltaProgressRate = delta(undefined, prevTask.progressRate);
const deltaPV = delta(undefined, prevTask.pv);
const deltaEV = delta(undefined, prevTask.ev);
// 個々の変更アリナシ
const hasProgressRateDiff = deltaProgressRate !== undefined && deltaProgressRate !== 0;
const hasPvDiff = deltaPV !== undefined && deltaPV !== 0;
const hasEvDiff = deltaEV !== undefined && deltaEV !== 0;
const fullName = prev.getFullTaskName(prevTask);
const isOverdueAt = prevTask.isOverdueAt(prev.baseDate);
const workload = prevTask.workload;
const prevBaseDate = prev.baseDate;
const currentBaseDate = undefined;
const baseDate = prevBaseDate;
const daysOverdueAt = (0, common_1.formatRelativeDaysNumber)(baseDate, prevTask.endDate);
const daysStrOverdueAt = (0, common_1.formatRelativeDays)(baseDate, prevTask.endDate);
diffs.push({
id: prevTask.id,
name: prevTask.name,
fullName,
assignee: prevTask.assignee,
parentId: prevTask.parentId,
deltaProgressRate,
deltaPV,
deltaEV,
prevPV: prevTask.pv,
prevEV: prevTask.ev,
currentPV: undefined,
currentEV: undefined,
prevProgressRate: prevTask.progressRate,
currentProgressRate: undefined,
hasDiff: true, // 削除も常に差分ありとみなす
hasProgressRateDiff,
hasPvDiff,
hasEvDiff,
diffType: 'removed',
finished: prevTask.finished,
isOverdueAt,
workload,
prevBaseDate,
currentBaseDate,
baseDate,
daysOverdueAt,
daysStrOverdueAt,
currentTask: undefined,
prevTask,
});
}
return diffs;
}
// private _calcProgressRafe(group: TaskDiff[]) {
// const pv = sumDelta(group.map((g) => g.deltaPV))
// const ev = sumDelta(group.map((g) => g.deltaEV))
// return calcRate(ev, pv)
// }
calculateProjectDiffs(taskDiffs) {
const result = (0, tidy_1.tidy)(taskDiffs.filter((taskDiff) => taskDiff.hasDiff),
// taskDiffs,
(0, tidy_1.summarize)({
// deltaProgressRate: (group) => this._calcProgressRate(group),
deltaPV: (group) => sumDelta(group.map((g) => g.deltaPV)),
deltaEV: (group) => sumDelta(group.map((g) => g.deltaEV)),
// deltaSPI: (group) => sumDelta(group.map((g) => g.deltaSPI)), // これはおかしい。
prevPV: (group) => sumDelta(group.filter((g) => g.hasPvDiff).map((g) => g.prevPV)),
prevEV: (group) => sumDelta(group.filter((g) => g.hasEvDiff).map((g) => g.prevEV)),
currentPV: (group) => sumDelta(group.filter((g) => g.hasPvDiff).map((g) => g.currentPV)),
currentEV: (group) => sumDelta(group.filter((g) => g.hasEvDiff).map((g) => g.currentEV)),
modifiedCount: (group) => group.filter((g) => g.diffType === 'modified').length,
addedCount: (group) => group.filter((g) => g.diffType === 'added').length,
removedCount: (group) => group.filter((g) => g.diffType === 'removed').length,
hasDiff: (group) => group.some((g) => ['modified', 'added', 'removed'].includes(g.diffType)),
finished: (group) => group.every((g) => g.finished),
// prevBaseDate:(group) => group.map((g) => g.prevBaseDate)?.[0],
// currentBaseDate:(group) => group.map((g) => g.currentBaseDate)?.[0],
}));
return result;
}
calculateAssigneeDiffs(taskDiffs) {
const result = (0, tidy_1.tidy)(taskDiffs.filter((taskDiff) => taskDiff.hasDiff),
// taskDiffs,
(0, tidy_1.groupBy)('assignee', [
(0, tidy_1.summarize)({
// deltaProgressRate: (group) => this._calcProgressRate(group),
deltaPV: (group) => sumDelta(group.map((g) => g.deltaPV)),
deltaEV: (group) => sumDelta(group.map((g) => g.deltaEV)),
// deltaSPI: (group) => sumDelta(group.map((g) => g.deltaSPI)), // これはおかしい。
prevPV: (group) => sumDelta(group.filter((g) => g.hasPvDiff).map((g) => g.prevPV)),
prevEV: (group) => sumDelta(group.filter((g) => g.hasEvDiff).map((g) => g.prevEV)),
currentPV: (group) => sumDelta(group.filter((g) => g.hasPvDiff).map((g) => g.currentPV)),
currentEV: (group) => sumDelta(group.filter((g) => g.hasEvDiff).map((g) => g.currentEV)),
modifiedCount: (group) => group.filter((g) => g.diffType === 'modified').length,
addedCount: (group) => group.filter((g) => g.diffType === 'added').length,
removedCount: (group) => group.filter((g) => g.diffType === 'removed').length,
hasDiff: (group) => group.some((g) => ['modified', 'added', 'removed'].includes(g.diffType)),
finished: (group) => group.every((g) => g.finished),
// prevBaseDate:(group) => group.map((g) => g.prevBaseDate)?.[0],
// currentBaseDate:(group) => group.map((g) => g.currentBaseDate)?.[0],
}),
]));
return result;
}
}
exports.ProjectService = ProjectService;
/**
* bのみがundefinedのばあいはa
* aのみがundefinedの場合は-b
* 両方undefinedの場合はundefined
* あとは a-b
* @param a
* @param b
* @returns
*/
function delta(a, b) {
const aIsNum = typeof a === 'number';
const bIsNum = typeof b === 'number';
if (aIsNum && bIsNum) {
const diff = a - b;
// return diff !== 0 ? diff : undefined
return diff;
}
if (aIsNum)
return a;
if (bIsNum)
return -b;
return undefined;
}
const sumDelta = (numbers) => (0, common_1.sum)(numbers.filter((v) => v !== undefined), 3);
//# sourceMappingURL=ProjectService.js.map