evmtools-node
Version:
このライブラリは、プライムブレインズ社で利用している「進捗管理ツール(Excel)」ファイルを読み込み、 プロジェクトの進捗状況や要員別の作業量を可視化するためのライブラリです。
282 lines • 10 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TaskRow = void 0;
const excel_csv_read_write_1 = require("excel-csv-read-write");
const logger_1 = require("../logger");
const common_1 = require("../common");
/**
* タスクを表す基本エンティティ(リーフまたは中間ノード)
*/
class TaskRow {
constructor(
/**
* 表示順や構造上の識別に使われる行番号("#"に対応)
*/
sharp,
/**
* タスクの一意なID
*/
id,
/**
* タスクの階層レベル(1=ルート、2=子など)
*/
level,
/**
* タスクの名称
*/
name,
/**
* 担当者名(任意)
*/
assignee,
/**
* 予定工数(日単位など、任意)
*/
workload,
/**
* 予定の開始日(任意)
*/
startDate,
/**
* 予定の終了日(任意)
*/
endDate,
// /**
// * 現時点での進捗率(任意、%値)
// */
// public readonly progress?: number
// --- 実績系 ---
/**
* 実際の開始日(任意)
*/
actualStartDate,
/**
* 実際の終了日(任意)
*/
actualEndDate,
/**
* 実績ベースの進捗率(任意)
*/
progressRate,
/**
* 予定された稼働日数(任意)
*/
scheduledWorkDays,
/**
* Planned Value:計画された作業価値(任意)
*/
pv,
/**
* Earned Value:実施された作業価値(任意)
*/
ev,
/**
* Schedule Performance Index:スケジュール効率指標(任意)
*/
spi,
/**
* 現在の進捗率に相当する予定日(任意)
*/
expectedProgressDate,
/**
* 遅延日数(任意、マイナスで前倒し)
*/
delayDays,
/**
* 備考(任意)
*/
remarks,
/**
* 親タスクのID(任意、ツリー構造用)
*/
parentId,
/**
* 子を持たないリーフノードであるかどうか(任意)
* (便宜上任意だけど、セットすること)
*/
isLeaf, plotMap) {
this.sharp = sharp;
this.id = id;
this.level = level;
this.name = name;
this.assignee = assignee;
this.workload = workload;
this.startDate = startDate;
this.endDate = endDate;
this.actualStartDate = actualStartDate;
this.actualEndDate = actualEndDate;
this.progressRate = progressRate;
this.scheduledWorkDays = scheduledWorkDays;
this.pv = pv;
this.ev = ev;
this.spi = spi;
this.expectedProgressDate = expectedProgressDate;
this.delayDays = delayDays;
this.remarks = remarks;
this.parentId = parentId;
this.isLeaf = isLeaf;
this.plotMap = plotMap;
this.logger = (0, logger_1.getLogger)('domain/TaskRow');
/**
* 基準日(その日のみの)のPVを計算する。
* 基本、稼働予定の日数 / 予定工数 の値。
* その日に タスクがなければ0。
* なんらかの理由で、稼働予定日数がNaN/undefined/ゼロの場合、undefinedを返す。
* なんらかの理由で、予定工数がNaN/undefinedの場合、undefinedを返す。
*
* @param baseDate 基準日
* @return
*/
this.calculatePV = (baseDate) => {
if (!this.checkStartEndDateAndPlotMap()) {
return undefined;
}
const { startDate, endDate, plotMap } = this;
const workloadPerDay = this.workloadPerDay;
if (workloadPerDay === undefined) {
return undefined;
}
// レンジ外なら0
if (!isInRange(baseDate, startDate, endDate, plotMap)) {
return 0.0;
}
return workloadPerDay;
};
/**
* 基準日終了時点の累積PVを返す。タスクが始まっていなかったら0.
* plotMapからカウントしている。(Excelから読む際は稼働予定日数より□のプロット優先となるってこと)
* plotMapが取れなかったらゼロ
* 開始日終了日が取れなかったらゼロ
*
* @param baseDate
* @return
*/
this.calculatePVs = (baseDate) => {
if (!this.checkStartEndDateAndPlotMap()) {
return 0.0;
}
let pvs = 0;
const baseSerial = (0, excel_csv_read_write_1.date2Sn)(baseDate);
// plotMapのキー値(ExcelのDateのシリアル値)
// 指定した基準日「まで(含む)」のpv値は足す。
// ホントは土日を除去しないと、親タスクの計算はバグってしまう
// (土日もプロットされているため。かつ、稼働予定日数も間違っている)
for (const [serial /*value*/] of this.plotMap.entries()) {
if (serial <= baseSerial) {
const pv = this.calculatePV((0, excel_csv_read_write_1.dateFromSn)(serial));
pvs += pv ?? 0.0;
}
}
return pvs;
};
/**
* Schedule Performance Index (SPI = EV/PV)
* baseDateを元に導出した累積PVを用いて計算した、SPI
* @param baseDate
* @returns
*/
this.calculateSPI = (baseDate) => {
const pvs = this.calculatePVs(baseDate);
return (0, common_1.calcRate)(this.ev, pvs);
};
/**
* Schedule Variance (SV = EV-PV)を返す
* baseDateを元に導出した累積PVを用いて計算した、EV-PV
* @param baseDate
* @returns
*/
this.calculateSV = (baseDate) => {
const pvs = this.calculatePVs(baseDate);
return (0, common_1.subtract)(this.ev, pvs);
};
/**
* startDate, endDate ,plotMap がundefinedだったらfalse
* @returns
*/
this.checkStartEndDateAndPlotMap = () => {
const { startDate, endDate, plotMap, id, name } = this;
if (!startDate || !endDate) {
this.logger.warn(`タスクID:${id} 日付エラー。開始日:[${(0, common_1.dateStr)(startDate)}],終了日:[${(0, common_1.dateStr)(endDate)}]が取得できず.タスク名: ${name}`);
return false;
}
if (!plotMap) {
this.logger.warn(`タスクID:${id} plotMapエラー(undefined)`);
return false;
}
return true;
};
}
/**
* 予定工数 / 稼働予定日数 による一日あたりの工数(任意)
* 計算不能な場合は undefined
*/
get workloadPerDay() {
const { workload, scheduledWorkDays } = this;
const { id, name } = this;
if ((0, common_1.isValidNumber)(workload) &&
(0, common_1.isValidNumber)(scheduledWorkDays) &&
scheduledWorkDays !== 0) {
return workload / scheduledWorkDays;
}
this.logger.warn(`タスクID:${id} 日数エラー(0/空)。稼動予定日数:[${scheduledWorkDays}],予定工数:[${workload}]. タスク名: ${name}`);
return undefined;
}
/**
* 進捗率が100% ならtrueそれ以外はfalse
*/
get finished() {
return this.progressRate === 1.0;
}
/**
* 指定した基準日で、タスクが期限切れかどうかを判定する。
* - 終了日 <= 基準日 かつ 未完了 の場合に true を返す
* (あくまで基準日の業務が終わったときの状況を算出する考えなので、等号を入れた)
* progressRate はundefinedの場合もある(未完了と見なす)
*/
isOverdueAt(baseDate) {
if (!this.endDate)
return false;
const isNotFinished = this.progressRate === undefined || this.progressRate < 1.0;
return isNotFinished && this.endDate <= baseDate;
}
/**
* TaskNode から、TaskRowを作る
* @param node
* @param level
* @param parentId
* @returns
*/
static fromNode(node, level, parentId) {
return new TaskRow(node.sharp, node.id, level, node.name, node.assignee, node.workload, node.startDate, node.endDate, node.actualStartDate, node.actualEndDate, node.progressRate, node.scheduledWorkDays, node.pv, node.ev, node.spi, node.expectedProgressDate, node.delayDays, node.remarks, parentId, node.isLeaf, node.plotMap);
}
}
exports.TaskRow = TaskRow;
/**
* baseDateがレンジにあるかどうか
* baseDateがplotMapにある、かつ、start/endに含まれていたらtrue
* @param baseDate
* @param startDate
* @param endDate
* @returns
*/
function isInRange(baseDate, startDate, endDate, plotMap) {
const baseSerial = (0, excel_csv_read_write_1.date2Sn)(baseDate);
const startSerial = (0, excel_csv_read_write_1.date2Sn)(startDate);
const endSerial = (0, excel_csv_read_write_1.date2Sn)(endDate);
return plotMap.get(baseSerial) === true && startSerial <= baseSerial && baseSerial <= endSerial;
// return startDate <= baseDate && baseDate <= endDate
}
// const baseDates = [
// new Date('2025-06-09T00:00:00+09:00'),
// new Date('2025-06-10T00:00:00+09:00'),
// new Date('2025-06-11T00:00:00+09:00'),
// new Date('2025-06-12T00:00:00+09:00'),
// new Date('2025-06-13T00:00:00+09:00'),
// ]
// const startDate = new Date('2025-06-08T15:00:00.000Z')
// const endDate = new Date('2025-06-12T15:00:00.000Z')
// for (const baseDate of baseDates) {
// const result = getElapsedDays(baseDate, startDate, endDate)
// console.log(result) // => 11(日数:10日差 + 1)
// }
//# sourceMappingURL=TaskRow.js.map