【入門編】【実務中級】サブプロジェクト統合時の制約条件競合を自動解消するVBAスクリプト – Project VBA解析バイブル

スポンサーリンク
スポンサーリンク

Project VBAの深淵へようこそ:サブプロジェクト統合時の「制約条件競合」を自動で鎮める技術

こんにちは。現場の自動化を支えるエンジニアの皆さん。
マクロの記録機能を使って「やりたいことの断片」を形にできるようになってきた頃でしょうか。それとも、もっと複雑なロジックを組もうとして、プロジェクトのスケジュールが意図せず跳ね上がり、途方に暮れてしまった経験があるでしょうか?

今日は、MS Projectを扱う実務者なら必ず一度は直面する、非常にエキサイティングで、かつ厄介な問題についてお話しします。

テーマは「サブプロジェクト統合時の制約条件(Constraint)の自動解消」です。

「マクロの記録」から一歩踏み出し、「プロジェクトの計算エンジンがどう動いているか」という本質的な視点を持つための、極めて実践的なレッスンを始めましょう。

1. なぜ、統合した瞬間にスケジュールが「爆発」するのか?

複数のサブプロジェクトを一つのマスタープロジェクトにまとめる作業は、プロジェクト管理の要です。しかし、単純に結合(Insert Subproject)しただけで、全体のクリティカルパスが崩壊したり、タスクが数ヶ月先へ吹き飛んだりすることがあります。

その犯人は、「制約条件(Constraint)」の衝突です。

制約条件の「硬さ」を理解する

MS Projectのタスクには、それぞれ「いつ動けるか」というルールが設定されています。これをVBAの世界では `ConstraintType` と呼びます。

  • ASAP (As Soon As Possible / 可能限り早く):

制約なし。前工程が終わればすぐ動ける。もっとも「柔軟」で、スケジュール計算エンジンが最も得意とする状態です。

  • Must Start On (指定開始日):

「絶対にこの日に始めろ」という命令。これは「硬い制約(Hard Constraint)」と呼ばれます。

【ここがトラブルの本質】
サブプロジェクト側で「Must Start On」が設定されているタスクをマスターに統合すると、そのタスクはマスター側の論理的なつながり(先行・後続関係)を無視して、その日付に居座ろうとします。
その結果、後続タスクが無理やり押し出され、スケジュール全体が歪んでしまうのです。

2. 戦略:エンジンの「柔軟性」を取り戻す

プロフェッショナルな自動化エンジニアは、力ずくで日付を書き換えることはしません。そうではなく、「計算エンジンが自由に動けるように、制約の硬さを調整する」というアプローチを取ります。

今回のスクリプトの設計思想はこうです:
1. 統合された全タスクを走査する。
2. 「Must Start On」などの「硬い制約」を持つタスクを特定する。
3. それらを「ASAP(制約なし)」に書き換え、論理的なつながりに基づいて再計算させる。
4. ただし、勝手に変えていいのか不安な人のために、変更したタスクをログに残す。

3. 実践:制約条件自動解消スクリプト

それでは、魂を込めて書いたコードを見ていきましょう。このコードは、単なる「書き換え」ではなく、パフォーマンスと安全性を考慮した設計になっています。

Option Explicit

''' <summary>
''' サブプロジェクト統合時に発生する硬い制約条件を検知し、
''' ASAP(制約なし)へ自動変換してスケジュールの整合性を確保する。
''' </summary>
Sub ResolveConstraintConflicts()
    Dim t As Task
    Dim countChanged As Integer
    Dim startTime As Double
    
    ' パフォーマンス最適化: 計算モードを「手動」に切り替える
    ' ループ内で毎回再計算が走ると、プロジェクトが巨大な場合にフリーズします。
    ' これが「オブジェクトのライフサイクル」を理解する第一歩です。
    Application.Calculation = pjManual
    
    countChanged = 0
    startTime = Timer
    
    Debug.Print "--- 制約条件解消プロセス開始 ---"

    ' プロジェクト内の全タスクをループ
    For Each t In ActiveProject.Tasks
        ' タスクが存在しない(空行など)場合はスキップ
        If Not t Is Nothing Then
            
            ' 【重要】制約条件の判定
            ' pjTaskMustStartOn (Must Start On) や 
            ' pjTaskStartNoEarlierThan (開始指定) など、
            ' スケジュールの柔軟性を奪う条件をチェックします。
            Select Case t.ConstraintType
                
                Case pjTaskMustStartOn, pjTaskMustFinishOn, pjTaskStartNoEarlierThan
                    
                    ' ログ出力(イミディエイトウィンドウ)
                    Debug.Print "変更対象: [" & t.Name & "] 現在の制約: " & GetConstraintName(t.ConstraintType)
                    
                    ' 制約を「ASAP (可能限り早く)」に変更
                    ' これにより、タスクは論理的な先行関係に従うようになります。
                    t.ConstraintType = pjTaskConstraintAsSoonAsPossible
                    
                    ' 変更したことを視覚的にわかりやすくするため、テキストを付与(任意)
                    ' t.Text1 = "Constraint Resolved by VBA"
                    
                    countChanged = countChanged + 1
                    
                Case Else
                    ' 制約が柔軟な場合は何もしない
            End Select
            
        End If
    Next t

    ' 計算モードを「自動」に戻し、一括でスケジュールを再計算させる
    Application.Calculation = pjAutomatic
    
    ' 結果報告
    Debug.Print "--- プロセス完了 ---"
    Debug.Print "変更したタスク数: " & countChanged
    Debug.Print "実行時間: " & Round(Timer - startTime, 2) & " 秒"
    
    MsgBox "制約条件の解消が完了しました。" & vbCrLf & _
           "変更数: " & countChanged & " 件", vbInformation, "自動化エンジン実行完了"
End Sub

''' <summary>
''' 制約タイプを人間が読める形式の文字列に変換する補助関数
''' </summary>
Private Function GetConstraintName(ByVal cType As Long) As String
    Select Case cType
        Case pjTaskConstraintAsSoonAsPossible: GetConstraintName = "ASAP"
        Case pjTaskConstraintMustStartOn:      GetConstraintName = "Must Start On"
        Case pjTaskConstraintStartNoEarlierThan: GetConstraintName = "Start No Earlier Than"
        Case pjTaskConstraintStartNoLaterThan:  GetConstraintName = "Start No Later Than"
        Case pjTaskConstraintMustFinishOn:     GetConstraintName = "Must Finish On"
        Case Else:                             GetConstraintName = "Other/Flexible"
    End Select
End Function

4. ここがプロの分かれ道:エンジニアの知見

このコードをコピーして動かすだけで満足してはいけません。伝説的なアーキテクトを目指すなら、以下の3点を意識してください。

① `Application.Calculation` の制御

コードの中に `pjManual`(手動計算)と `pjAutomatic`(自動計算)の切り替えを入れましたね。
もしこれを入れずに、ループの中でタスクを書き換えると、「1タスク書き換えるたびに、MS Projectがプロジェクト全体を再計算する」という地獄のような挙動になります。タスクが1,000個あれば、1,000回再計算が走るのです。
「計算の重み」を知ることは、大規模な業務自動化において最も重要なスキルの一つです。

② `If Not t Is Nothing` の重要性

MS Projectの `Tasks` コレクションには、ユーザーが削除したタスクの「跡地(Nothing)」が含まれることがあります。これを確認せずに `t.Name` にアクセスしようとすると、`Object variable or With block variable not set` というエラーでプログラムが止まってしまいます。
「例外的な状態」を事前に潰しておく。これがプロの作法です。

③ 制約の「緩和」は「破壊」ではない

このスクリプトは、制約を消しているのではなく、「論理的なつながり(Predecessors/Successors)に従う権利をタスクに与えている」のです。
もし、どうしてもその日付を守らなければならないタスクがある場合は、そのタスクだけを `Select Case` の対象外にする、あるいは「特定のフラグが立っているものだけ処理する」といったロジックを追加して、カスタマイズしていきましょう。

最後に

いかがでしたか?
「マクロの記録」で得られるのは「操作の記録」ですが、「VBAのプログラミング」で得られるのは「プロジェクトの意思決定の自動化」です。

制約条件を制御できるようになると、あなたは単なる「作業者」から、プロジェクトの構造を設計する「アーキテクト」へと進化します。

一歩ずつ、着実に。
このコードが、あなたの業務を劇的に変える一助となることを願っています。

ここをクリアすれば、Project VBAの基本はバッチリですよ。次はもっと複雑な、リソース平準化の自動化に挑戦してみましょう!

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