【実務・中級編】タスクの「完了率」に応じて依存関係のリンクを自動的に「実績」として保護する制御法 – Project VBA解析バイブル

スポンサーリンク

Project VBAを掌握する極限の知見:完了済みタスクの依存関係を「実績」として鉄壁に保護するイベント駆動型制御法

開発現場のリーダーであるあなたなら、痛いほど共感しているはずだ。
プロジェクトが終盤に差し掛かった頃、若手メンバーや他部署の担当者が、悪気なく過去の完了タスク(完了率100%)のスケジュールをいじったり、依存関係(先行タスク・後続タスク)を付け替えたりして、全体スケジュールが雪崩式に崩壊する――あの悪夢を。

Microsoft Project(Project VBA)において、タスクの「完了率(% Complete = 100%)」に達したものは、もはや「計画(Plan)」ではなく「実績(Actual)」であるべきだ。実績データが後からいじれる状態にあること自体が、プロジェクトマネジメントの観点から致命的な欠陥と言える。

今回は、タスクが100%完了した瞬間に、その依存関係(TaskPredecessors / TaskSuccessors)を自動的にロックし、誤操作によるスケジュール変動を物理的・論理的にシャットアウトする「イベント駆動型の要塞コード」を授けよう。

なぜ従来の運用では破綻するのか?

多くの現場では、「ルール」や「マニュアル」でこれを防ごうとする。

  • 「100%になったタスクのリンクはいじらないでください」
  • 「変更時はPMの承認を得てください」

これらはすべて無駄だ。人間はミスをする生き物であり、期限に追われればルールなど平気で破られる。

また、VBAの初学者がやりがちなアプローチとして、「定期的にタイマーで全タスクをスキャンして保護する」というバッチ処理的な実装があるが、これではパフォーマンスが重くなるだけでなく、変更された「後」に検知するため、一瞬の隙が生まれる。

我々が実装すべきは、Projectのイベントエンジン(`App` オブジェクトのイベント)をフックし、「ユーザーが変更しようとしたその瞬間(Before)」に介入して不正な変更を握り潰す、ノンブロッキングかつ堅牢なアーキテクチャである。

堅牢な設計の要点:イベントの捕捉とキャンセル

Project VBAでタスクの変更を検知するには、クラスモジュールを用いて `MSProject.App` のイベントを捉える必要がある。
特に重要なのは `ProjectBeforeTaskChange` イベントだ。このイベントの引数には `Cancel As Boolean` が用意されており、条件に一致しない不正な操作は、その場で処理をキャンセル(`Cancel = True`)できる。

しかし、ここで一つ罠がある。
「タスクの完了率が100%になったタスク 自体 を変更する場合」と、「100%のタスクに対する 依存関係のリンク を変更する場合」では、監視すべきトリガーが異なる。

今回は実務的な要件として、以下の2点を厳格に制御する。
1. 完了率が100%のタスクは、基本情報(期間や開始日など)の改変を禁止する。
2. 完了率が100%のタスクに絡むプレデセッサ(先行タスク)・サクセッサ(後続タスク)の追加・削除・変更を禁止する。

プロダクションコードの実装

ここから、実際のプロジェクトファイルに組み込めるプロダクションコードを公開する。
この実装は、クラスモジュールと標準モジュールの2つで構成される。

1. クラスモジュール:`CProjectEvents`

プロジェクト全体のイベントを監視し、改変を検知・阻止する中枢頭脳。

‘ =================================================================oyan
‘ クラスモジュール名: CProjectEvents
‘ 役割: Projectのイベントをフックし、完了タスクの改変を阻止する
‘ =================================================================
Option Explicit

Public WithEvents AppEvents As MSProject.Application

‘ タスク変更前のイベント
Private Sub AppEvents_ProjectBeforeTaskChange(ByVal Tsk As Task, ByVal Field As PjField, ByVal NewValue As Variant, Cancel As Boolean)
On Error GoTo ErrorHandler

‘ タスクがNothing(新規作成時など)の場合はスルー
If Tsk Is Nothing Then Exit Sub

‘ 【防御ロジック1】すでに完了(100%)しているタスクの保護
‘ ※ただし、完了率フィールド自体の変更(0->100への遷移)は許可する
If Tsk.PercentComplete = 100 Then
If Field <> pjTaskPercentComplete Then
‘ 完了済みタスクへの変更試行を検知
MsgBox “【警告】タスク ID: ” & Tsk.ID & ” (” & Tsk.Name & “) は既に完了(100%)しているため、変更が制限されています。”, _
vbCritical + vbOKOnly, “Project VBA セキュリティガード”
Cancel = True
Exit Sub
End If
End If

‘ 【防御ロジック2】依存関係(リンク)に関する変更の保護
‘ 先行タスク(Predecessors)や後続タスク(Successors)に関するフィールドをガード
Select Case Field
Case pjTaskPredecessors, pjTaskSuccessors, pjTaskLinkType, pjTaskLag
‘ 関連するタスクのいずれかが100%完了している場合はリンク変更を禁止
If IsTaskOrLinkedTaskCompleted(Tsk) Then
MsgBox “【警告】このタスクに関連するリンクの変更は、完了済みタスク(実績)を変動させるため禁止されています。”, _
vbCritical + vbOKOnly, “Project VBA セキュリティガード”
Cancel = True
Exit Sub
End If
End Select

Exit Sub

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

‘ —————————————————————–
‘ 補助関数: 自身またはリンク先のタスクに100%完了のものが含まれるか判定
‘ —————————————————————–
Private Function IsTaskOrLinkedTaskCompleted(ByVal Tsk As Task) As Boolean
Dim predTask As Task
Dim succTask As Task

IsTaskOrLinkedTaskCompleted = False

‘ 自身が100%ならTrue
If Tsk.PercentComplete = 100 Then
IsTaskOrLinkedTaskCompleted = True
Exit Function
End If

‘ 先行タスクに100%のものが含まれているか
For Each predTask In Tsk.PredecessorTasks
If predTask.PercentComplete = 100 Then
IsTaskOrLinkedTaskCompleted = True
Exit Function
End If
Next predTask

‘ 後続タスクに100%のものが含まれているか
For Each succTask In Tsk.SuccessorTasks
If succTask.PercentComplete = 100 Then
IsTaskOrLinkedTaskCompleted = True
Exit Function
End If
Next succTask
End Function

2. 標準モジュール:`MEventInitializer`

クラスをインスタンス化し、アプリケーションライフサイクルにイベントをバインドするエントリーポイント。

‘ =================================================================
‘ 標準モジュール名: MEventInitializer
‘ 役割: イベント監視クラスのライフサイクルを管理する
‘ =================================================================
Option Explicit

Private GlbEventListener As CProjectEvents

‘ プロジェクトオープン時等に自動実行(または手動実行)
Public Sub InitializeProjectGuard()
If GlbEventListener Is Nothing Then
Set GlbEventListener = New CProjectEvents
Set GlbEventListener.AppEvents =illedApplication ‘ MSProject.Applicationをバインド
MsgBox “タスク実績保護ガード(Project VBA)が正常に有効化されました。”, vbInformation, “システム稼働通知”
Else
MsgBox “ガードシステムは既に稼働中です。”, vbInformation
End Sub
End Sub

‘ ガードを解除する場合(デバッグ用など)
Public Sub TerminateProjectGuard()
Set GlbEventListener = Nothing
MsgBox “タスク実績保護ガードを解除しました。”, vbExclamation, “システム停止通知”
End Sub

‘ MSProjectの仕様として自動起動させたい場合は Auto_Open を利用する
Public Sub Auto_Open()
Call InitializeProjectGuard
End Sub

開発現場のリーダーが知るべき「運用上の注意点」

このコードを導入するにあたり、プロフェッショナルとして考慮すべきインフラ・環境面のポイントを共有しておく。

1. マクロの有効化とセキュリティ署名
企業内の厳格なセキュリティ環境では、未署名のVBAプロジェクトは実行すらブロックされる。プロダクション環境へ投入する際は、必ず自己証明書または企業内CAによるデジタル署名をVBAプロジェクトに付与し、信頼できる発行元として登録しておくこと。
2. 「計画の修正」が必要な場合の逃げ道(管理者用バックドア)
現場で本当のトラブルがあり、どうしても完了済みタスクの依存関係を修正しなければならない特例が発生する。このガードがガチガチに効いていると、PMすら身動きが取れなくなる。
実運用では、環境変数や特定のグローバルフラグ、あるいはパスワード入力を求める「メンテナンスモード切替関数」を別途用意し、一時的に `GlbEventListener` を無効化できるように設計しておくのがプロの技だ。

結論

Excelと異なり、Microsoft Projectは「時間とリソースの制約」を数学的に計算し続ける巨大なエンジンである。そのエンジンの中で、一度確定した「実績(100%完了タスク)」が狂わされることは、プロジェクト全体の信頼性を根底から揺るがす。

今回紹介したイベント駆動型の制御法を取り入れることで、ツールの運用は「人の注意力」に依存するものから、「システムによる絶対的な統制」へと昇華される。
あなたのプロジェクトのスケジュールを、無知な手戻りから守り抜け。

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