こんにちは!Microsoft Projectの裏側でうごめくVBAの深淵へようこそ。
今回は、多くの現場で「なぜか計画が狂う」「実績を入れたのに残作業が減らない!」とエンジニアを悩ませる、ResourceAssignment(リソース割り当て)オブジェクトの「Work(総工数)」と「RemainingWork(残工数)」の同期について、徹底的に解説していきます。
「マクロの記録」ボタンを押すだけの日々は、もう終わりです。
オブジェクトのライフサイクルを正しく理解し、プロジェクトの「計画と実績の乖離」をVBAの力で美しくねじ伏せる――。ここをクリアすれば、あなたのProject VBAスキルは間違いなく中級者の域を突破し、現場のヒーローになれますよ。
さあ、知的でスマートな自動化の世界へ一歩を踏み出しましょう!
—
1. なぜ「Work」と「RemainingWork」の同期が必要なのか?
MS Projectを実務で使っていると、こんなイライラを感じたことはありませんか?
- 「メンバーが実作業時間を『実作業(Actual Work)』に入力したのに、なぜか『残工数(Remaining Work)』が減らない……!」
- 「気づけば完了したはずのタスクに残工数が残り続け、スケジュールが完全に崩壊している」
Excelの感覚でタスクを管理しているとハマる最大の罠がここです。MS Projectの内部では、`Work`(総工数)、`ActualWork`(実作業)、`RemainingWork`(残工数)はそれぞれ独立したパラメータとして存在しています。
基本方程式はこれです:
$$\text{Work} = \text{ActualWork} + \text{RemainingWork}$$
メンバーが「今日、5時間働きました(ActualWork = 5)」と入力しても、Projectのデフォルトの挙動によっては、総工数(Work)が勝手に5時間増え、残工数(RemainingWork)がそのまま残ってしまうことがあります。「いやいや、予定通り進んだんだから残りを減らしてよ!」と思いますよね。
これを手動で毎回修正するのはヒューマンエラーの元。だからこそ、VBAで「実績が入ったら、その分を残工数から自動で削る(同期させる)」仕組みを作る必要があるのです。
—
2. Projectオブジェクトモデルの階層構造をハックする
VBAを書く前に、私たちがこれから操る「世界」の構造を確認しておきましょう。Excel VBAとは少し違う、Project特有の階層があります。
Application (MS Project全体)
└── ActiveProject (現在開いているプロジェクト)
└── Tasks (タスクの集合)
└── Task (個別のタスク)
└── Assignments (そのタスクに割り当てられたリソースの集合)
└── Assignment (リソース割り当て ※ここにWorkとRemainingWorkがある!)
勘の良い方なら気づいたはずです。
「工数」を持っているのは、タスク(Task)そのものではなく、「タスクに割り当てられたリソース(Assignment)」なのです。ここを間違えると、「いくらコードを書いても工数が変わらない…」という泥沼にハマります。
—
3. 【実践】工数同期を自動化するVBAコード
それでは、実務でそのまま使えるコードを公開します。
このマクロは、現在アクティブなプロジェクトのすべての割り当て(Assignment)を走査し、「すでに発生した実作業(ActualWork)の分だけ、残工数(RemainingWork)を正しく再計算して同期する」というものです。
VBAエディタ(`Alt + F11`)を開き、標準モジュールに以下のコードを貼り付けてください。
Sub SyncWorkAndRemainingWork()
Dim tsk As Task
Dim asn As Assignment
Dim processedCount As Long
‘ 画面描画を停止してマクロの実行速度を爆発的に上げる(プロの常識です)
App.ScreenUpdating = False
processedCount = 0
‘ プロジェクト内のすべてのタスクをループ
For Each tsk In ActiveProject.Tasks
‘ タスクが存在し、かつサマリータスク(親タスク)ではない場合のみ処理
If Not tsk Is Nothing Then
If Not tsk.Summary Then
‘ タスクに紐づくすべてのリソース割り当て(Assignment)をループ
For Each asn In tsk.Assignments
If Not asn Is Nothing Then
‘ 【核心】
‘ 実作業(ActualWork)が存在し、かつ残工数(RemainingWork)がある場合
‘ ※ここでは「総工数(Work) = 実作業 + 残工数」を維持するロジックを適用
‘ 例:もし実作業が入力されたら、本来の計画工数から実作業を引いた残りに同期させる、
‘ または単純に残工数を「元の総工数 – 実作業」に再計算するアプローチ
Dim originalWork As Double
Dim actual As Double
originalWork = asn.Work ‘ 現在の総工数(分単位で保持されていることに注意!)
actual = asn.ActualWork
‘ 実作業が入力されている場合のエラー防止と同期処理
If actual > 0 Then
‘ 残工数 = 総工数 - 実作業
‘ ※マイナスにならないようにスピンを入れる
Dim newRemaining As Double
newRemaining = originalWork – actual
If newRemaining < 0 Then newRemaining = 0 End If ' 残工数を更新 asn.RemainingWork = newRemaining processedCount = processedCount + 1 End If End If Next asn End If End If Next tsk ' 画面描画を復元 App.ScreenUpdating = True ' 完了メッセージ MsgBox "同期処理が完了しました。" & vbCrLf & _ "処理した割り当て数: " & processedCount & "件", vbInformation, "Project VBA Automation" End Sub
コードの重要ポイント解説
1. `App.ScreenUpdating = False` の魔法
Projectはデータ変更のたびにガントチャートの再描画走るため、大量のタスクを処理するとフリーズしたように遅くなります。これをオフにするのは、実務VBAの絶対的正義です。
2. 「分単位」という隠れた仕様への配慮
MS Projectの内部データにおいて、`Work`などの時間系プロパティは「分(Minutes)」単位で保持されています。例えば「1時間」は `60` です。今回のコードでは直接プロパティ同士の引き算をしているため単位のズレは起きませんが、数値を直撃で代入する際は「分単位であること」を忘れないでください。
3. サマリータスク(親タスク)の除外
`If Not tsk.Summary Then` がポイントです。親タスクの工数は子タスクの集計値(読み取り専用に近い挙動)なので、親に対して直接値を書き込もうとするとエラーになります。実務では必ずサマリータスクを弾くガードを入れましょう。
—
4. 陥りやすいエラーとデバッグの極意
現場でこのコードを走らせたときに、エンジニアがよく直面する罠を先回りして共有しておきます。
罠1: 「実行時エラー ‘1101’: このフィールドには値を設定できません。」
- 原因: サマリータスクや、すでに完了している(Complete = 100%)タスクのAssignmentに対して値を書き込もうとした場合に発生します。
- 対策: `tsk.Summary` のチェックに加え、`asn.PercentWorkComplete = 100` のような完了タスクをスキップする条件分岐を追加すると完璧です。
罠2: 値を代入したのに、プロジェクトを閉じると元に戻る?
- 原因: Projectのスケジュール計算エンジン(Task Type や Effort Driven の設定)が、VBAでの強制代入の後に自動再計算を行い、数値を上書きしてしまうことがあります。
- 対策: 必要に応じて、コードの最後に `CalculateGlobal` や、プロジェクト全体の再計算を強制するメソッドを挟むと安全です。
—
おわりに
お疲れ様でした!
今回は `ResourceAssignment` オブジェクトを操り、`Work` と `RemainingWork` を同期させる実務直結のテクニックを解説しました。
「マクロの記録」では絶対にたどり着けない、オブジェクトモデルの階層構造を意識したコードを書けるようになったあなたなら、どんな巨大なプロジェクトファイルの混沌も、VBAの美しいコードでコントロールできるはずです。
ここをクリアしたあなたへ――Project VBAの世界は、もうあなたの手の内にあります。
日々の定型作業を自動化し、もっと本質的なプロジェクトマネジメントに時間を使いましょう。それでは、次の現場でお会いしましょう!
