【テクニカル・上級編】【実務中級】ResourceAssignmentの「Work」と「RemainingWork」をVBAで同期させる工数管理の自動化 – Project VBA解析バイブル

スポンサーリンク

【実務中級】ResourceAssignmentの「Work」と「RemainingWork」をVBAで同期させる工数管理の自動化

Microsoft ProjectのVBA開発において、多くのエンジニアが直面する最初の、そして最大の壁が「工数(Work)と残工数(RemainingWork)の非同期問題」だ。

プロジェクトマネジメントの実務において、メンバーから上がってくる進捗報告は「今週何時間働いたか(ActualWork)」か「あと何時間で終わるか(RemainingWork)」のどちらかであることが多い。しかし、MS Projectのオブジェクトモデルは、これらを暗黙的に自動計算させようとするあまり、開発者が意図した通りの数値書き込みを拒絶することが多々ある。特に、`ResourceAssignment` オブジェクトを操作する際、この仕様を理解していないと、工数の総量が勝手に書き換わる「無限ループ地獄」や「予期せぬスケジュール崩壊」を引き起こす。

今回は、実務で即座に使える、`Work` と `RemainingWork` を完全に掌握し同期させるための実践的アーキテクチャを解説する。

1. MS Projectオブジェクトモデルの闇:WorkとRemainingWorkの関係

MS Projectのバックエンドエンジンは、タスクやアサインメントの変更を受けると、以下の数式に基づいて自動再計算を行う。

$$\text{Work} = \text{ActualWork} + \text{RemainingWork}$$

この一見シンプルな方程式が、VBAからのバルクインポートや実績自動反映処理において牙をむく。
例えば、`RemainingWork` だけを更新しようとVBAで値を代入した場合、MS Projectのタスク種類(Task Type:固定単位、固定期間、固定工数)の設定によっては、`Work` が勝手に再計算され、結果として計画全体が歪むのだ。

さらに、`ResourceAssignment` オブジェクトのメモリ管理は極めてデリケートであり、ループ処理の中で適切なオブジェクト解放を行わないと、巨大なプロジェクトファイルではメモリリークやCOM例外(エラー 1101等)を引き起こす。

2. 実務で必須となる設計方針

今回の自動化スクリプトにおける設計指針は以下の通りだ。

1. 計算モードの制御(Manual Calculation):
処理中の自動再計算を一時停止し、パフォーマンスと意図しない値の書き換わりを防ぐ。
2. 安全なアサインメント走査:
すべてのタスク、すべてのリソースアサインメントを安全に巡回し、指定されたルールに従って `RemainingWork` を同期。
3. 明示的なメモリ解放:
`Set obj = Nothing` を徹底し、VBAランタイムとMS ProjectのCOM境界におけるメモリリークを根絶。

3. 実装コード:WorkとRemainingWorkの同期自動化エンジン

以下のコードは、外部システム(CSVやExcel等)から取得した「最新の残工数」を、MS Project側の `ResourceAssignment` に安全かつ正確に適用するための実務レベルのプロシージャである。

Option Explicit

‘ ==============================================================================
资源アサインメント工数同期エンジン
‘ Author: 伝説のチーフアーキテクト
‘ Description: ResourceAssignmentのWorkとRemainingWorkを整合性を保ちながら同期
‘ ==============================================================================
Public Sub SyncAssignmentWorkload()
Dim prj As Project
Set prj = ActiveProject

‘ パフォーマンス最適化と予期せぬ自動計算の抑止
Dim origCalc As Boolean
origCalc = Application.Calculation
Application.Calculation = pjCalculationManual

‘ エラーハンドリングの確実な復旧を担保
On Error GoTo ErrorHandler

Dim tsk As Task
Dim assn As ResourceAssignment

Dim updatedCount As Long
updatedCount = 0

Debug.Print “=== 工数同期処理を開始します ===”

‘ プロジェクト内の全タスクを走査
For Each tsk In prj.Tasks
If Not tsk Is Nothing Then
‘ サマリータスクやマイルストーンを除外する場合のガード句
If Not tsk.Summary And tsk.Milestone = False Then

‘ タスクに紐付くリソースアサインメントを走査
For Each assn In tsk.ResourceAssignments
If Not assn Is Nothing Then

‘ 【コアロジック】
‘ 実績工数(ActualWork)が存在する場合の残工数(RemainingWork)の安全な同期
‘ 例:外部DBから取得した「真の残工数」を反映するシミュレーション
‘ ここでは「残工数が計画の50%未満になったタスクの調整」を例示するが、
‘ 実際には外部配列やDictionaryから値を取得してバインドする。

Dim currentWork As Double
Dim currentActual As Double
Dim currentRemaining As Double

currentWork = assn.Work / 60 ‘ 分単位から時間に変換
currentActual = assn.ActualWork / 60
currentRemaining = assn.RemainingWork / 60

‘ 実績がすでにある場合の整合性チェック
If currentActual > 0 Then
‘ 例:残工数を強制的に実績ベースで再計算・同期させる場合
‘ ※MS Projectの仕様上、Work = ActualWork + RemainingWork を強制する
Dim targetRemaining As Double

‘ ここではビジネスロジックに応じた計算式を適用(例:残りを一律で調整)
‘ targetRemaining = [外部ソースからの値]

‘ 今回はデモとして、RemainingWorkをWorkからActualWorkを引いた値に厳密に一致させる例
‘ (自動計算のズレを補正するパージ処理)
Dim calculatedWork As Double
calculatedWork = currentActual + currentRemaining

If assn.Work <> calculatedWork Then
‘ Workと(Actual + Remaining)が乖離している場合の強制同期
assn.Work = calculatedWork 60
updatedCount = updatedCount + 1
End If
End If

‘ 【重要】メモリ最適化:アサインメントオブジェクトの解放
Set assn = Nothing
End If
Next assn

End If
‘ 【重要】メモリ最適化:タスクオブジェクトの解放
Set tsk = Nothing
End If
Next tsk

‘ 変更を反映するために再計算を実行
Application.Calculation = origCalc
prj.Calculate

Debug.Print “=== 同期処理が正常に完了しました。更新件数: ” & updatedCount & “件 ===”
Exit Sub

ErrorHandler:
‘ 異常終了時も必ず計算モードとメモリを復旧する
Application.Calculation = origCalc
Set assn = Nothing
Set tsk = Nothing
Set prj = Nothing

MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical, “工数同期マクロ”
End Sub

4. チーフアーキテクトが教える、実務運用上の「罠」と対策

罠1: 「タスクの種類 (Task Type)」による値のハレーション

MS Projectには「固定単位 (Fixed Units)」「固定期間 (Fixed Durationphthalm)」「固定工数 (Fixed Work)」の3種類が存在する。
アサインメントの `RemainingWork` を変更した瞬間、タスクの種類によっては期間(Duration)が勝手に伸び縮みし、プロジェクト全体のクリティカルパスを破壊することがある。

  • 対策: スクリプトを実行する前に、対象タスクの `Task.Type` を意図したモード(基本は `pjTaskTypeFixedWork` など)に固定するか、変更後に `Duration` が意図せず変動していないかアサーションを挟むこと。

罠2: 分単位(Minutes)のデータ構造

MS Projectの内部保持データは、時間の単位に関わらずすべて「分 (Minutes)」で格納されている。
VBAから `10` と代入すると、それは「10時間」ではなく「10分」になってしまう。時間単位で計算・代入を行う場合は、必ず ` 60` を忘れないこと。今回のコード例でも `assn.Work / 60` のようにラップしているのはこのためだ。

罠3: 巨大プロジェクトにおけるCOMコンテナの肥大化

`For Each` ループの中でオブジェクトを変数に格納し続けると、VBAの背後にあるCOMラッパーが解放されずメモリを圧迫する。上記のコードで `Set assn = Nothing` や `Set tsk = Nothing` をループの終端で必ず実行しているのは、数万行規模の大規模工程表を扱う際にプロセスが突然落ちる(メモリ不足)現象を防ぐための必須の作法である。

総括

Project VBAにおける工数管理の自動化は、単なるAPIの叩き合いではない。MS Projectのコアエンジンが持つ「自動計算の哲学」と正面から向き合い、それを手なづける高度なエンジニアリングだ。

今回紹介したコードと設計思想をベースに基幹システムやExcelからのインポートパイプラインを構築すれば、計画と実績の乖離に悩まされる現場から解放されるだろう。妥協なきコードで、真のプロジェクトコントロールを実現してほしい。

タイトルとURLをコピーしました