【実務・中級編】【上級者向け】複数プロジェクトのカスタムフィールド定義を統合管理するマスターツール – Project VBA解析バイブル

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

【極限のProject VBA】複数プロジェクトのカスタムフィールド定義を統制する「同期エンジン」の設計と実装

ポートフォリオ管理に携わるエンジニアが必ずぶち当たる壁がある。それは「プロジェクトファイル(.mpp)が乱立した瞬間、カスタムフィールドの定義がバラバラになる」という地獄だ。

あるファイルでは `Text1` が「進捗ステータス」で、別のファイルでは「顧客名」になっている。計算式(Formula)を修正したいとき、100個のファイルを開いて手動で書き換えるのか? そんな非効率な作業に時間を溶かすのは、プロの仕事ではない。

本記事では、複数のプロジェクトファイル間でカスタムフィールドの定義(名称、型、計算式)を完全に同期させる「カスタムフィールド同期エンジン」のアーキテクチャと実装を伝授する。

1. なぜ「単純なループ処理」では失敗するのか

多くの初心者は、「ファイルを順番に開き、フィールドを書き換えて保存する」という単純なコードを書く。しかし、プロダクションレベルでそれをやると以下の3つの問題に直面する。

① パフォーマンスの崩壊

MS Projectのファイルオープン(`FileOpen`)は、極めて重い処理だ。不適切にループ内でファイルを制御すると、メモリリークやCOMオブジェクトの解放漏れが発生し、途中でアプリケーションがクラッシュする。

② 計算式の「ローカライズ」と「構文」の罠

`CustomField.Formula` に代入する文字列は、Projectの内部形式に従う必要がある。また、言語設定によって関数名が変わるリスクがある。ここをハードコードすると、OSの言語環境が変わった瞬間にツールはゴミになる。

③ データの破壊リスク

定義を上書きした際、既存のデータが予期せず消えたり、計算結果が不可逆的に変更されたりすることがある。「冪等性(Idempotency)」——つまり、何度実行しても同じ正しい状態になり、かつ不要な書き換えを行わない設計が不可欠だ。

2. アーキテクチャ設計:定義の分離と注入

堅牢なツールを作るための正解は、「定義(Master)」と「実行(Engine)」を完全に分離することだ。

設計コンセプト

1. 定義マスター(Excel/JSON/CSV): どのフィールドに、どの名称を付け、どの計算式を割り当てるかを一元管理する。
2. 同期エンジン(VBA): マスターを読み込み、ターゲットとなる `.mpp` ファイル群に「差分がある場合のみ」定義を注入する。
3. バリデーション層: 計算式が正しい形式か、フィールド型が一致しているかを適用前にチェックする。

3. 【実装】プロダクションレベルの同期エンジン

以下に、実務でそのまま利用可能な設計思想に基づいたコードを示す。ここでは保守性を高めるため、定義を構造体(Type)で管理し、処理をモジュール化している。

Option Explicit

' --- 定義構造体: カスタムフィールドの仕様を保持 ---
Private Type FieldDefinition
    FieldName As String    ' 例: "Text1", "Cost1"
    DisplayName As String  ' 例: "プロジェクト優先度"
    Formula As String      ' 例: "IIf([% Complete]=100, ""完了"", ""進行中"")"
    FieldType As Long      ' pjCustomTaskText, pjCustomTaskCost 等
End Type

' --- メインエントリポイント ---
Public Sub SyncPortfolioCustomFields()
    Dim masterList As Collection
    Dim projectFiles As Variant
    Dim filePath As Variant
    Dim projApp As MSProject.Application
    
    On Error GoTo ErrorHandler
    
    ' 1. 定義マスターのロード (本来は外部Excel等から読み込むべきだが、ここでは例としてハードコード)
    Set masterList = LoadFieldDefinitions()
    
    ' 2. 同期対象ファイルのリストアップ (実際にはフォルダ走査などで取得)
    projectFiles = Array("C:\Projects\Project_A.mpp", "C:\Projects\Project_B.mpp")
    
    Set projApp = Application
    
    ' パフォーマンス最適化: 画面更新の停止
    projApp.ScreenUpdating = False
    
    For Each filePath In projectFiles
        Debug.Print "Processing: " & filePath
        
        ' ファイルを読み取り専用で開かず、書き込み権限を持って開く
        projApp.FileOpen ExName:=filePath, ReadOnly:=False
        
        ' 各フィールドの同期処理を実行
        Dim def As FieldDefinition
        For Each def In masterList
            ApplyFieldDefinition def
        Next def
        
        ' 保存して閉じる (保存しないと意味がない)
        projApp.FileSave
        projApp.FileClose
    Next filePath
    
    projApp.ScreenUpdating = True
    MsgBox "全プロジェクトのカスタムフィールド同期が完了しました。", vbInformation
    Exit Sub

ErrorHandler:
    projApp.ScreenUpdating = True
    MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical
End Sub

' --- フィールド定義の読み込みロジック ---
Private Function LoadFieldDefinitions() As Collection
    Dim col As New Collection
    Dim def As FieldDefinition
    
    ' 定義1: Text1をステータス管理に使用
    def.FieldName = "Text1"
    def.DisplayName = "プロジェクト状態"
    def.Formula = "IIf([% Complete]=100, ""完了"", ""進行中"")"
    def.FieldType = pjCustomTaskText
    col.Add def
    
    ' 定義2: Cost1を予算乖離率に使用
    def.FieldName = "Cost1"
    def.DisplayName = "予算乖離額"
    def.Formula = "[Baseline Cost] - [Actual Cost]"
    def.FieldType = pjCustomTaskCost
    col.Add def
    
    Set LoadFieldDefinitions = col
End Function

' --- 個別フィールドの適用ロジック(ここがエンジンの心臓部) ---
Private Sub ApplyFieldDefinition(def As FieldDefinition)
    Dim targetField As CustomField
    
    ' カスタムフィールドオブジェクトの取得
    ' ※ project.CustomFields は読み取り専用に近い挙動をするため注意
    Set targetField = ActiveProject.CustomFields(def.FieldName)
    
    ' 【重要】冪等性の確保: 変更が必要な場合のみ書き換える
    ' これにより、不必要な計算再評価(Recalculation)を防ぎ、パフォーマンスを向上させる
    Dim needsUpdate As Boolean
    needsUpdate = False
    
    If targetField.Title <> def.DisplayName Then
        needsUpdate = True
    End If
    
    If targetField.Formula <> def.Formula Then
        needsUpdate = True
    End If
    
    If needsUpdate Then
        With targetField
            .Title = def.DisplayName
            .Formula = def.Formula
            ' 型の変更はAPI制限があるため、不一致の場合はエラーを出す設計にする
        End With
        Debug.Print "Updated: " & def.FieldName
    End If
End Sub

4. チーフアーキテクトによる「極限の知見」:実装の急所

このコードを単にコピペして動かすだけでは、まだ「中級者」だ。真に堅牢なシステムにするために、以下の3点を深く理解せよ。

① `ScreenUpdating` の制御とCOMの解放

MS Projectは、一つのフィールドを変更するたびにプロジェクト全体の再計算(Recalculation)を走らせる傾向がある。`ScreenUpdating = False` は必須だが、それでも重い。大量のファイルを処理する場合、`Application.Calculation = pjManual`(手動計算モード)への切り替えを検討せよ。

② Formula(計算式)のエスケープ処理

VBA内で計算式を定義する場合、ダブルクォーテーションの重複(`””完了””`)に注意しろ。また、計算式に日本語のフィールド名を使わず、必ず `[Text1]` のような内部名を使用することだ。ユーザーがフィールド名を変えても、内部名が変わらなければ計算式は壊れない。

③ 型の不整合に対する防御策

`Text1` を `Cost1` に変えることはできない。もしマスター定義で型を変更した場合、既存のファイルで型不整合が起き、ランタイムエラーで停止する。実運用では、`On Error Resume Next` で逃げるのではなく、「現在の型」と「定義の型」を比較し、不一致ならログに出力してスキップするというバリデーションロジックを組み込め。

5. まとめ:自動化の先にある「ガバナンス」

技術的に同期エンジンを作ることは簡単だ。しかし、本当に重要なのは「誰がマスター定義を決定し、いつ同期をかけるか」という運用ガバナンスである。

1. 定義のバージョン管理: マスター定義(Excel等)をGitやSharePointでバージョン管理せよ。
2. 影響調査: 計算式を変更する前に、そのフィールドを参照している他のレポートやビューがないかを確認せよ。
3. バックアップ: 同期エンジンを実行する直前に、全ファイルのバックアップを自動で取る仕組みを組み込め。

このレベルまで設計して初めて、あなたは「単なるVBA書き」ではなく、「業務自動化エンジニア」と呼べるだろう。

以上だ。実装に取り掛かれ。

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