【入門編】実績工数入力時にリソースの残工数を自動再計算するトリガー処理 – Project VBA解析バイブル

スポンサーリンク

こんにちは!Project VBAの世界へようこそ。いつも業務自動化や進捗管理の効率化、お疲れ様です。

Excel VBAはある程度書けるようになったけれど、Microsoft Project(以下、MS Project)のVBAに足を踏み入れた途端、「あれ?思ったように動かないぞ…」と戸惑う方は非常に多いです。それもそのはず。MS ProjectはExcelとは異なり、裏側で強力な「スケジュール計算エンジン」が常に動き続けているからです。

今回は、MS Projectの核心部である「実績工数(ActualWork)を入力した際、残工数(RemainingWork)を自動で再配分し、プロジェクト終了予定日への影響をリアルタイムに算出する仕組み」をテーマに解説します。

「マクロの記録」を卒業し、Projectの真の力を引き出すプログラミングへの第一歩を、私と一緒に踏み出しましょう。ここをクリアすれば、Project VBAの基本はバッチリですよ!

—

1. Excel脳からProject脳へ!「Assignment(割り当て)」の概念を理解しよう

コードを書く前に、まずはMS Projectの最も重要なデータ構造を理解しましょう。ここが最大の難所であり、ここさえ理解できれば勝率九割です。

MS Projectのデータは、以下の3つの要素が絡み合って成り立っています。

[ タスク (Task) ] ─── ( 割り当て: Assignment ) ─── [ リソース (Resource) ]
(例: 設計書作成) (例: 山田さん)

1. タスク (Task): やるべき仕事(例:「設計書作成」)
2. リソース (Resource): 実行する人や物(例:「山田さん」)
3. 割り当て (Assignment): 「誰がどのタスクをどれくらいやるか」という組み合わせ情報

実績工数を入力する際、私たちは「山田さんが設計書作成を10時間やった」というAssignment(割り当て)に対してデータを入力します。
タスクやリソースに直接実績を入れるのではなく、この「中間にある結びつき(Assignment)」を操作するのが、Project VBAを美しく制御する極意です。

—

2. 実績工数(ActualWork)が更新されると、Projectの裏で何が起きる?

MS Projectでは、実績工数を入力すると、エンジンが以下の数式に沿って他の値を自動的に再計算します。

$$\text{総工数 (Work)} = \text{実績工数 (ActualWork)} + \text{残存工数 (RemainingWork)}$$

例えば、当初予算が「40時間」のタスクに対して、実績工数「10時間」を入力したとします。

  • 標準の挙動: 残存工数は自動的に「30時間」に減ります。
  • もし遅れが発生していたら?: 実績を10時間消化したけれど、残りの作業に「35時間」かかりそうだとわかった場合、残存工数を手動(またはVBA)で「35時間」に書き換える必要があります。すると、総工数は「45時間」になり、タスクの終了予定日が自動的に後ろにズレ込みます。

この「実績を入れた瞬間に、自動で残工数や終了日への影響を計算してユーザーに教えてくれる仕組み」を、VBAの「イベント処理」を使って実装してみましょう。

—

3. 実践コード:実績入力トリガーで影響度をリアルタイム算出する

MS Projectで「値が変わった瞬間」を検知するには、クラスモジュールを使ったイベントハンドラーの実装が必要です。

Project VBAには、Excel VBAにあるような `Application.EnableEvents = False`(イベント発生を一時的に止めるプロパティ)が存在しません。そのため、無限ループ(値を書き換えたらまたイベントが発生して…のループ)を防ぐための「プロの工夫」もコードに盛り込んでいます。

ステップ1:クラスモジュールの作成

VBAエディタで `挿入` -> `クラスモジュール` をクリックし、オブジェクト名を `EventClass_Project` に変更して、以下のコードを貼り付けてください。

Option Explicit

‘ Projectアプリケーションのイベントを監視するための宣言
Public WithEvents App As MSProject.Application

‘ 無限ループを防ぐためのフラグ
Private IsProcessing As Boolean

‘— 割り当て(Assignment)が変更されたときに発生するイベント —
Private Sub App_ProjectBeforeAssignmentChange(ByVal asg As Assignment, _
ByVal Field As PjAssignmentField, _
ByVal NewVal As Variant, _
Cancel As Boolean)

‘ 既に処理中なら二重起動を防ぐために抜ける(無限ループ対策)
If IsProcessing Then Exit Sub

‘ 変更されたフィールドが「実績工数(ActualWork)」であるか判定
‘ pjAssignmentActualWork は、実績工数フィールドを表す定数です(値は318767123)
If Field = pjAssignmentActualWork Then

‘ 処理開始フラグを立てる
IsProcessing = True

On Error GoTo ErrorHandler

‘ 1. 変更前の情報を取得(単位は「分」で保持されているため、時間に変換)
Dim oldActual As Double
oldActual = asg.ActualWork / 60

‘ 2. 新しく入力された値を取得(入力値は「分」単位の文字列、または数値で渡されます)
Dim newActual As Double
newActual = CDbl(NewVal) / 60

‘ 3. 進捗状況の計算
Dim workDiff As Double
workDiff = newActual – oldActual

‘ イミディエイトウィンドウに状況を出力
Debug.Print “========================================”
Debug.Print “【検知】実績工数が変更されました!”
Debug.Print “タスク名: ” & asg.Task.Name
Debug.Print “担当者 : ” & asg.ResourceName
Debug.Print “変更前実績: ” & oldActual & ” 時間”
Debug.Print “変更後実績: ” & newActual & ” 時間 (差分: ” & workDiff & ” 時間)”

‘— ここが極限の知見:残工数の自動調整ロジック —
‘ 実績が予定より多くかかった場合、残存工数を自動で維持・調整し、
‘ プロジェクト全体の終了日にどう影響するかを算出します。

Dim taskType As Integer
taskType = asg.Task.Type

‘ 固定期間タスクなどの場合、終了日が変わらないように調整が入るため注意喚起
If taskType = pjFixedDuration Then
Debug.Print “【警告】このタスクは「期間固定」です。終了日は移動せず、稼働率が調整されます。”
End If

‘ 将来の日付への影響を予測
‘(Projectの再計算エンジンが裏で走るため、変更後の終了予定日を事前に予測表示)
Dim currentFinish As Date
currentFinish = asg.Task.Finish

Debug.Print “現在のタスク終了予定日: ” & Format(currentFinish, “yyyy/mm/dd hh:nn”)
Debug.Print “※実績確定後、エンジンによりスケジュールが自動再計算されます。”
Debug.Print “========================================”

End If

ExitSub:
‘ 処理終了時に必ずフラグを元に戻す
IsProcessing = False
Exit Sub

ErrorHandler:
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical, “エラー”
Resume ExitSub
End Sub

ステップ2:標準モジュールでのイベント有効化

次に、上記のクラスを起動するためのコードを記述します。
`挿入` -> `標準モジュール` をクリックし、以下のコードを貼り付けてください。

Option Explicit

‘ グローバル変数としてイベントクラスのインスタンスを保持する
Dim myProjectApp As New EventClass_Project

”
‘ プロジェクト起動時や、手動でイベント監視を開始するマクロ
”
Sub StartEventMonitoring()
‘ Projectアプリケーションをクラス内のWithEvents変数に紐付ける
Set myProjectApp.App = MSProject.Application
MsgBox “実績工数の自動監視システムが起動しました!” & vbCrLf & _
“割り当ての実績工数を変更すると、自動計算ログが出力されます。”, vbInformation, “システム起動”
End Sub

”
‘ 監視を停止したい場合に実行するマクロ
”
Sub StopEventMonitoring()
Set myProjectApp.App = Nothing
MsgBox “自動監視システムを停止しました。”, vbInformation, “システム停止”
End Sub

—

4. コードの解説と「なぜ動くのか?」

このコードが優れている理由は、MS Projectが持つ「BeforeChange(変更前)イベント」を捉えている点にあります。

`App_ProjectBeforeAssignmentChange` の魔法

このイベントは、値が実際にセルに書き込まれて確定する直前に発生します。

  • `asg` 引数によって、「誰の、どのタスクの割り当てか」が特定できます。
  • `Field` 引数によって、「どの列(フィールド)が書き換えられようとしているか」を特定できます。今回は `pjAssignmentActualWork`(実績工数)だけを狙い撃ちにしています。
  • `NewVal` 引数には、これから入力される新しい値が入っています。

Project VBA特有の「単位:分」問題

MS Projectの内部では、工数はすべて「分(Minutes)」単位で保持されています。
例えば、画面上で `10h`(10時間)と入力された場合、VBAに渡される値は `600`(分)になります。そのため、コード内で `/ 60` を行い、人間に分かりやすい「時間単位」に変換してログに出力しています。

—

5. 陥りやすい罠:タスクタイプ(Task Type)の壁

Project VBAを始めたばかりの人が必ず陥る罠があります。それが「タスクの種類(Task Type)」です。

タスクのダブルクリックで開く「タスク情報」ダイアログの「詳細」タブに、「タスクの種類」という項目があります。これには以下の3つがあります。

1. 固定作業量 (Fixed Work)
2. 固定期間 (Fixed Duration)
3. 固定稼働率 (Fixed Units)

実績工数(ActualWork)を入力した際、タスクタイプが「固定期間」になっていると、実績が増えても「期間(タスクの長さ)」は絶対に変わりません。代わりに「リソースの稼働率(忙しさ)」が自動で下がります。
逆に、「固定作業量」になっていると、実績が増えた分、残りの期間が自動で伸び縮みし、終了予定日がリアルタイムに変動します。

> ★先輩のアドバイス
> 「コードは正しく書けているのに、なぜか終了予定日が変わらない!」というときは、VBAのバグではなく、このタスクタイプの設定が原因であることがほとんどです。テストする際は、タスクタイプが「固定作業量」になっていることを確認してくださいね。

—

6. まとめ

お疲れ様でした!今回は、MS Project VBAの最重要オブジェクトである `Assignment` をコントロールし、実績入力時にリアルタイムで計算を行うトリガー処理を解説しました。

今回のポイントを整理しましょう。

  • タスク・リソース・割り当て(Assignment)の3層構造を意識する。
  • 実績工数は `Assignment.ActualWork` で管理され、内部単位は「分」である。
  • イベントの二重発生を防ぐために、`IsProcessing` フラグで無限ループをガードする。
  • 計算結果(終了日の移動など)は、タスクの「タスクの種類(Task Type)」に影響される。

これが理解できれば、あなたはもう「マクロの記録」を脱出した、立派なProject VBAアーキテクトです。
現場でのスケジュール管理を自動化し、チームメンバーの稼働をスマートに守るツールを、ぜひこのコードをベースに作り上げてみてください。

もし「ここがうまく動かない」「こういうカスタマイズをしたい」といった疑問があれば、いつでも聞いてくださいね。応援しています!

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