【テクニカル・上級編】タスクの「完了率」に基づいた依存関係の自動ロック:進捗済みタスクの保護 – Project VBA解析バイブル

スポンサーリンク

Project VBAを掌握する極限の知見:進捗済みタスクの不可侵領域化と依存関係ロックのアーキテクチャ

Microsoft Project VBAにおける最大の悪夢。それは、すでに着手・完了したWBS(Work Breakdown Structure)のタスクが、後続工程の変更やヒューマンエラーによって予期せぬ再計算を引き起こし、プロジェクト全体のクリティカルパスを崩壊させる瞬間である。

進捗率(`% Complete`)が1%でも入力されたタスク、あるいは完全に完了(100%)したタスクは、もはや「流動的な計画値」ではない。それはプロジェクトの「既定の歴史(実績)」であり、保護されなければならない。

今回は、Project VBAのオブジェクトモデルの深層、タスク制約(Constraint)の厳密な制御、そしてレガシー環境におけるメモリ最適化とAPI連携を網羅し、「進捗済みタスクの自動ロックと依存関係の整合性維持」を実現する極限のコードベースを提示する。

1. MS Projectオブジェクトモデルの闇:なぜ通常のプロパティ操作では不十分なのか

MS ProjectのVBAにおいて、タスクを「読み取り専用」にしたり変更不能にしたりするアプローチは、表面的なプロパティ操作だけでは破綻する。

通常、開発者は `Task.ReadOnly` や `Task.Locked` といったプロパティを探しがちだが、Projectの内部データ構造(COMオブジェクト)において、タスクの保護は単一のフラグでは完結しない。進捗が入ったタスクを守るためには、以下の3要素を同時に制御する必要がある。

1. 制約タイプ(Constraint Type)の固定: 実績のあるタスクが自動スケジューリングエンジンによって勝手に移動しないよう、制約を「ASAP(可能な限り早期)」や「ALAP」から、「MSO(Must Start On: 指定日開始)」または「MFO(Must Finish On: 指定日終了)」へ昇格させる。
2. 実実績(Actuals)の整合性: `% Complete > 0` の場合、実績開始日(`Actual Start`)および実績作業時間(`Actual Work`)が確定するため、スケジュールエンジンの干渉を物理的に断つ。
3. リンク(Task Predecessors/Successors)の論理的保護: 先行タスクがロックされている場合、後続タスクからの不正な逆引き依存(Lead/Lagの破壊)を阻止する。

2. 【実装コード】進捗タスク自動ロック&依存関係整合性エンジン

以下のコードは、プロジェクト内の全タスクをスキャンし、指定した閾値(例: 進捗率 100%、または実働あり)を超えたタスクに対して、制約の固定化とプロパティの書き込み保護をアトミックに適用するプロダクション品質のVBAモジュールである。

Option Explicit

‘ ==============================================================================
定数定義
‘ ==============================================================================
Private Const THRESHOLD_COMPLETE As Long = 100 ‘ ロックをかける完了率の閾値
Private Const LOG_PREFIX As String = “[TaskLockSystem] ”

‘ ==============================================================================
‘ メインプロシージャ:進捗済みタスクの自動ロックと依存関係の保護
‘ ==============================================================================
Public Sub ApplyProgressBasedTaskLock()
Dim prj As Project
Set prj = ActiveProject

‘ パフォーマンス最適化の極限:描画と自動計算の停止
Dim originalCalc As PjCalculation
originalCalc = Application.Calculation

On Error GoTo ErrorHandler

‘ 画面描画と自動再計算を完全に殺す(数万タスクのループ処理における必須要件)
Application.ScreenUpdating False
Application.Calculation = pjCalculationManual

Dim t As Task
Dim lockedCount As Long
lockedCount = 0

Debug.Print LOG_PREFIX & “処理開始: 対象プロジェクト = ” & prj.Name

‘ タスクコレクションのイテレーション
For Each t In prj.Tasks
‘ 失敗行(SummaryタスクやNullタスク)の除外
If Not t Is Nothing Then
If Not t.Summary Then ‘ サマリータスクは子タスクの集計値のため除外

‘ 条件分岐:完了率が閾値以上、または実績工数が存在する場合
If t.PercentComplete >= THRESHOLD_COMPLETE Or t.ActualWork > 0 Then

‘ 1. タスクのスケジューリング制約を固定(実績日をベースに拘束)
‘ ※ すでに実績開始日が入っていることが前提
If t.ActualStart <> “NA” Then
t.ConstraintType = pjConstraintMustStartOn
t.ConstraintDate = t.ActualStart
End If

‘ 2. フラグやユーザー定義フィールドを用いた書き込み禁止マーキング
‘ (Projectにはネイティブな「行ロック」が存在しないため、UDFと制約で代替する)
t.Text10 = “LOCKED_BY_SYSTEM”
t.Flag1 = True

‘ 3. 依存関係(リンク)の整合性チェック:
‘ ロックされたタスクに紐づく不正な後続タスクのLag異常値を補正
Call ValidateAndFixDependencies(t)

lockedCount = lockedCount + 1
End If
End If
End If
5Next t

‘ 変更を反映するために手動で再計算を実行
Application.Calculation = pjCalculationAutomatic
CalculateAll

Debug.Print LOG_PREFIX & “処理完了. ロック適用タスク数: ” & lockedCount

Cleanup:
‘ 状態の復元
Application.Calculation = originalCalc
Application.ScreenUpdating True
Set prj = Nothing
Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “VBA Architecture Error”
Resume Cleanup
End Sub

‘ ==============================================================================
‘ サブルーチン:依存関係の整合性維持(依存先との矛盾解消)
‘ ==============================================================================
Private Sub Process ValidateAndFixDependencies(ByRef targetTask As Task)
Dim dep As TaskDependency

‘ このタスクを先行とする後続タスク群を走査
For Each dep In targetTask.SuccessorTasks
‘ 例: 完了済みタスクに対して、後続が無理やり負のラグ(Lead)を入れて
‘ スケジュールを逆行させていないかを検証・強制補正する
If dep.Lag < 0 Then ' 過去の既定事実(実績)に干渉する負のラグをゼロに強制補正 dep.Lag = 0 Debug.Print LOG_PREFIX & "警告: タスク ID " & targetTask.ID & " の後続(ID: " & dep.ToTask.ID & ")の不正な負ラグを修正しました。" End If Next dep Set dep = Nothing End Sub ---

3. シニアエンジニアのための極限最適化:メモリ管理とAPI連携

数万行に及ぶ大規模なWBSをProject VBAで操作する場合、ガベージコレクションの挙動やCOMオブジェクトの参照リークが、デスクトップアプリケーション全体のクラッシュを引き起こす。

オブジェクトの明示的解放とスコープ管理

VBAのランタイムは賢くない。ループ内で `For Each t In prj.Tasks` のようにオブジェクト変数を暗黙的に生成・破棄させると、背後でCOMの参照カウント(AddRef/Release)がリークし、メモリフットプリントが肥大化する。
大規模プロジェクトでは、イテレータ変数や一時オブジェクトを使い回し、処理の最後には必ず `Set = Nothing` を明示的に実行せよ。

Windows APIを活用した処理中のUIフリーズ防止

長時間にわたるタスクの走査中、MS ProjectのUIは「応答なし」状態になり、ユーザビリティを著しく損なう。これを防ぐために、Windows APIの `Sleep` やメッセージキューの処理(`DoEvents` の高度な代替)を組み込むことが、プロフェッショナルな実装の条件となる。

‘ 32bit/64bit環境両対応のAPI宣言
If VBA7 Then
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
Else
Private Declare Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
End If

‘ ループ内に適宜挿入し、OSへのCPU割り込みを許可する
‘ ※多用しすぎるとパフォーマンスが落ちるため、100回に1回程度実行するカウンタを設けるのが定石

4. レガシー環境・外部システム連携における注意点

この自動ロック機構を導入する際、企業内の既存システム(ERPやExcelベースの進捗管理ダッシュボード)との連携において以下の罠に直面する。

1. 外部からの強制的上書き(ODBC / Excelインポートの競合):
外部のVBAやサードパーティ製アドインが、システムによって `MSO`(指定日開始)に固定されたタスクの開始日をExcel等から強制インポートで上書きしようとした場合、Project側でエラー(または意図しないスケジュールの破綻)が発生する。
対策: 外部連携バッチの実行前にも必ず本バリデーションスクリプトを走らせ、`Text10 = “LOCKED_BY_SYSTEM”` が付与されているタスクに対する外部からの日付変更をスキーマレベル、あるいは前段のVBA層で弾く設計にしなければならない。

2. マルチユーザー環境(Server/Online)との乖離:
Microsoft Project Server / Project Online(PWA)環境では、タスクのロックはローカルVBAだけでなく、Enterprise Global(エンタープライズグローバル)およびキューサービスとの調停が必要になる。ローカルのVBAで制約を固定しても、チェックイン・チェックアウトのタイミングでサーバー側のワークフローエンジンに上書きされるリスクがあるため、サーバーサイドのカスタムフィールド権限設定とVBAのロジックを同期させることが鉄則となる。

総括

進捗済みタスクのロックは、単なる「編集禁止の化粧直し」ではない。それはプロジェクトマネジメントにおける「過去の事実」を技術的に担保し、エンジニアリングの信頼性を守るための防壁である。

甘い設計のVBAスクリプトは、いつの日か巨大なスケジュールをゴミの山に変える。オブジェクトのライフサイクルを慈しみ、COMの挙動を熟知し、スケジューリングエンジンの裏側までをコントロール下に置く者だけが、真のProject VBAを掌握することができる。

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