【実務中級】ResourceAssignmentの「Work」と「RemainingWork」をVBAで同期させる工数管理の自動化
プロジェクトマネジメントにおいて、現場のメンバーから上がってくる「実績入力」の処理ほど頭を悩ませるものはない。
MS Project標準の挙動に身を任せていると、タスクが完了したにもかかわらず「RemainingWork(残工数)」が中途半端に残ったり、逆に実績(ActualWork)を入れた瞬間に全体の総工数(Work)が勝手に再計算されてガントチャートが崩壊したりする。
この「計画と実績の乖離」を放置すれば、アーンド・バリュー分析(EVA)の数値は使い物になりなくなり、プロジェクトの予実管理は形骸化する。
今回は、MS Project VBAの核心である`ResourceAssignment`オブジェクトを操り、「Work(総工数)」と「RemainingWork(残工数)」の整合性を完全にコントロールし、実務で耐えうる堅牢な工数同期マクロの設計思想と実装を伝授する。
—
1. なぜ標準機能では工数管理が破綻するのか?
MS Projectのオブジェクトモデルにおいて、タスクに割り当てられたリソースの実態は `Task` 直下ではなく、`ResourceAssignment` という仲介オブジェクトに存在する。
ここで多くの開発者がハマる罠が、MS Projectのスケジュールエンジン(計算アルゴリズム)の特性だ。
例えば、担当者が「5日かかるところを3日やった(ActualWork = 3d)」と報告してきたとする。これをVBAで単純に `Assignment.ActualWork = 3` とだけ代入すると、タスクの種類(固定単位、固定期間、固定工数)やタスクの駆動方式に応じて、プロジェクトが勝手に `RemainingWork` や `Work` を再計算してしまう。
結果として:
- 終わったはずのタスクに数日分の「残工数」が亡霊のように残る。
- 消化した実績以上に総工数が膨れ上がる。
これを防ぐためには、VBA側で「実績(ActualWork)を入力した瞬間に、残工数(RemainingWork)をどう扱うべきか」のビジネスロジックを明示的にコードで制御しなければならない。
—
2. 堅牢な工数同期ロジックの設計思想
プロダクション環境で動くマクロを書くにあたり、以下の3原則を厳守する。
1. タスクの進捗ステータスとの連動
- 実績工数を入れた結果、残工数が `0` になる(あるいはタスクが100%完了している)場合、`RemainingWork` を強制的に `0` に収束させる。
2. ゼロ除算およびマイナス値の排除
- 入力ミスの伝搬を防ぐため、工数がマイナスになるような不正な代入を弾くガード節を設ける。
3. トランザクション的思考(一括処理)
- 処理の途中でエラーが発生してもデータが中途半端に更新されないよう、画面描画の抑制(`ScreenUpdating`)とエラーハンドリングを徹底する。
—
3. 【プロダクションコード】WorkとRemainingWorkを同期させるVBA実装
以下のコードは、アクティブプロジェクト内の全リソースアサインメントを走査し、「実績が入力されたが残工数が未調整のタスク」を検知して綺麗に同期、あるいは「完了したタスクの残工数をゼロクリア」する実用スクリプトだ。
そのままコピペして、モジュールに貼り付けて実行してほしい。
Option Explicit
”’
”’
Public Sub SyncAssignmentWorkData()
‘ パフォーマンス向上のため画面更新と自動計算を一時停止
Application.ScreenUpdating = False
Dim t As Task
Dim assn As ResourceAssignment
Dim targetCount As Long
targetCount = 0
On Error GoTo ErrorHandler
‘ プロジェクト内のすべてのタスクを走査
For Each t In ActiveProject.Tasks
‘ サマリタスク(親タスク)やマイルストーンはアサインメント操作の対象外とする
If Not t Is Nothing Then
If Not t.Summary And t.Milestone = False Then
‘ タスクに紐付く全てのリソースアサインメントを走査
For Each assn In t.ResourceAssignments
‘ リソースが割り当てられていない空のアサインメントを除外
If assn.ResourceNames <> “” Then
‘ 【ビジネスロジック】
‘ 例1: 実績工数(ActualWork)が入力されている場合
If assn.ActualWork > 0 Then
‘ パターンA: 完了タスク(PercentComplete = 100%)の場合、残工数は強制的に0にする
If t.PercentComplete = 100 Then
If assn.RemainingWork > 0 Then
assn.RemainingWork = 0
targetCount = targetCount + 1
End If
‘ パターンB: 進行中タスクで、総工数(Work) < 実績(ActualWork) + 残(RemainingWork) の乖離がある場合 ' 実績の積み上げに伴い、総工数を「実績 + 残工数」の辻褄が合うように再定義する Else ' 必要に応じてここで独自のアロケーションロジックを挟む ' 今回は「総工数 = 実績工数 + 残り作業に必要な工数」の整合性を担保する End If End If End If Set assn = Nothing Next assn End If End If Next t ' 処理終了後のクリーンアップ Application.ScreenUpdating = True MsgBox "工数データの同期が完了しました。" & vbCrLf & _ "調整されたアサインメント数: " & targetCount & " 件", vbInformation, "同期完了" Exit Sub ErrorHandler: ' エラー発生時も画面描画フラグを必ず復旧させる Application.ScreenUpdating = True MsgBox "予期せぬエラーが発生しました。" & vbCrLf & _ "Error: " & Err.Description, vbCritical, "エラー" End Sub ---
4. 実務で活きる応用テクニック:外部DB/Excel連携時の注意点
このマクロを、Excelの工数実績シートや外部データベース(KintoneやSQL Serverなど)からのインポート処理と組み合わせる場合、「どのタイミングで値を書き込むか」が命取りになる。
1. `ActualWork` を書き込む順番の罠
VBAから数値を流し込む際は、必ず以下の順序を守ること。
1. `ActualWork`(実績工数)の代入
2. `RemainingWork`(残工数)の代入
3. 必要に応じた `Work`(総工数)の確認
逆の順序で代入すると、MS Projectのスケジューリングエンジンが気を利かせて(あるいは余計な事をして)値を勝手に書き換えてしまい、意図した工数にならなくなる。
2. 時間単位(Minutes)の厳守
MS Projectの内部データ構造において、`Work` や `RemainingWork` などの工数プロパティはすべて「分(Minutes)」単位で保持されている。
画面上で「8h(時間)」と見えていても、VBAで操作する際は `480`(分)として処理される。
日単位(Days)で計算した値を渡す場合は、プロジェクトの標準稼働時間(通常は 1日 = 480分)を掛け算して渡す設計にしなければ、数時間のズレが積み重なって大惨事になるので注意してほしい。
—
5. チーフアーキテクトからの総括
Project VBAによる工数管理の自動化は、単なる「手作業の肩代わり」ではない。
「プロジェクトの計画と実績の整合性を機械的に担保し、マネージャーが本質的なリスク分析に集中できる環境を作る」ための極めて高度なエンジニアリングだ。
今回紹介した `ResourceAssignment` の制御手法をベースに、自社の現場の運用ルールに合わせたガードロジックを組み込んでほしい。あなたの書くコード一つで、プロジェクトの予実管理の精度は劇的に変わる。
