【テクニカル・上級編】【中級者向け】リソースの単価計算をカスタムフィールドで自動化するロジック – Project VBA解析バイブル

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

【極限の知見】Project VBAにおけるカスタムフィールド動的生成ロジック:リソース属性に依存する計算式の完全自動化

Microsoft Projectの業務自動化において、多くの開発者が「タスクの作成」や「リソースの割り当て」といった表面的な操作に終始している。しかし、真にエンタープライズレベルのシステムを構築するアーキテクトが向き合うべきは、「データの整合性を担保しつつ、動的な計算ロジックをいかにしてプロジェクト構造に注入するか」という、メタデータの制御である。

本稿では、リソースの属性(スキルレベル、地域、役職など)に基づき、カスタムフィールド内に複雑な単価計算式を動的に生成・設定する、極めて実務的な自動化手法を解説する。これは単なるスクリプトではない。大規模プロジェクトにおけるコスト管理の「計算エンジン」を構築するプロセスである。

1. 勘違いが生む「低品質な自動化」の罠

初級・中級者が陥る最大のミスは、「各リソースに対して個別に計算結果を書き込もうとする」ことだ。

リソースが数千人規模になる大規模プロジェクトにおいて、個々のリソースオブジェクトに対して計算結果を値として書き込む手法は、計算コストの爆発とデータの不整合を招く。一度値として書き込んでしまえば、リソースの単価が変更された瞬間に、過去の計算結果が「静的なゴミ」へと化すからだ。

我々が目指すべきは、「カスタムフィールドの `Formula` プロパティに、動的な計算式(文字列)をプログラムから注入する」手法である。これにより、Projectの計算エンジンそのものを、我々の定義したロジックで駆動させることが可能になる。

2. アーキテクチャ設計:カスタムフィールド・インジェクション

リソースの単価計算を自動化する場合、以下の3つのレイヤーを分離して設計する必要がある。

1. Attribute Layer (属性層): リソースが持つ固有の属性(例:`Number1` に格納されたスキルレベル、`Text1` に格納された地域コード)。
2. Formula Layer (数式層): 属性を組み合わせてコストを算出する論理式。
3. Injection Layer (注入層): VBAを用い、Formula Layerをプロジェクトのカスタムフィールド定義へ書き込むロジック。

計算式の構成例

例えば、「スキルレベル(Number1)が5以上なら単価を1.5倍、地域(Text1)が ‘Overseas’ ならさらに1.2倍にする」というロジックを、カスタムフィールド `Number2` に注入する場合、注入すべき文字列は以下のようになる。

`IIf([Number1]>=5, [Cost]*1.5, [Cost]) * IIf([Text1]=”Overseas”, 1.2, 1.0)`

この「文字列としての数式」を、VBAからいかに安全に、かつ確実に `CustomFields` コレクションへ送り込むかが勝負となる。

3. 実装:極限の堅牢性を備えた計算式自動設定エンジン

以下に、リソースのカスタムフィールドに対して計算式を動的に設定する、プロフェッショナル向けのコード例を示す。ここでは、オブジェクトのライフサイクル管理と、エラーハンドリング、そして大規模環境でのパフォーマンスを意識した設計を行っている。

Option Explicit

' ==============================================================================
' Module: ResourceCostEngine
' Description: リソース属性に基づき、カスタムフィールドの計算式を動的に設定する
' Author: Legendary Chief Architect
' ==============================================================================

' Windows API: プロセス制御やメモリ監視が必要な場合はここに追加する
' 今回はCOMオブジェクトの管理に集中するため、標準的な実装とする

Public Sub ExecuteDynamicCostFormulaInjection()
    Dim pjApp As MSProject.Application
    Set pjApp = MSProject.Application
    
    On Error GoTo ErrorHandler

    ' 1. プロジェクトの生存確認
    If pjApp.ActiveProject Is Nothing Then
        Err.Raise vbObjectError + 513, "ResourceCostEngine", "アクティブなプロジェクトが存在しません。"
    End If

    Debug.Print "--- Cost Formula Injection Start ---"

    ' 2. 計算式の設定実行
    ' Target: Resource Custom Field (Number2) を使用してコストを計算する
    ' 依存属性: Number1 (Skill Level), Text1 (Region)
    Call InjectResourceFormula(pjApp, pjApp.ActiveProject, "Number2", BuildComplexCostFormula())

    Debug.Print "--- Injection Completed Successfully ---"

CleanUp:
    ' 3. オブジェクトの明示的な解放 (メモリリーク防止)
    ' 大規模な自動化プロセスにおいて、これは必須の作法である
    Set pjApp = Nothing
    Exit Sub

ErrorHandler:
    MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical, "Architecture Error"
    Resume CleanUp
End Sub

''' <summary>
''' 複雑なビジネスロジックを反映した計算式文字列を生成する
''' </summary>
Private Function BuildComplexCostFormula() As String
    ' ここでビジネスルールを定義。将来的な変更(単価改定ルール等)はここを修正するだけで済む。
    ' 構文は MS Project の Formula 構文に従う必要がある。
    Dim formula As String
    
    ' ロジック: 
    ' base_cost = [Cost]
    ' multiplier_skill = IIf([Number1] >= 5, 1.5, 1.0)
    ' multiplier_region = IIf([Text1] = "Overseas", 1.2, 1.0)
    ' result = base_cost * multiplier_skill * multiplier_region
    
    formula = "IIf([Number1]>=5, [Cost]*1.5, [Cost]) * IIf([Text1]=""Overseas"", 1.2, 1.0)"
    
    BuildComplexCostFormula = formula
End Function

''' <summary>
''' 指定されたカスタムフィールドに計算式を注入する
''' </summary>
''' <param name="app">Project Application Object</param>
''' <param name="proj">Project Object</param>
''' <param name="fieldType">Field Type (e.g., "Number2")</param>
''' <param name="formula">The string formula to inject</param>
Private Sub InjectResourceFormula(ByRef app As MSProject.Application, _
                                  ByRef proj As MSProject.Project, _
                                  ByVal fieldType As String, _
                                  ByVal formula As String)
    
    Dim cf As MSProject.CustomField
    Dim found As Boolean
    found = False

    ' CustomFields コレクションを走査し、対象のフィールドを特定する
    ' 大規模プロジェクトでは、全件走査のオーバーヘッドを考慮し、
    ' 可能な限り直接的なインデックス指定や、名前による検索を検討すべきだが、
    ' ここでは確実性を期してループを用いる。
    
    For Each cf In proj.CustomFields
        ' リソース用カスタムフィールドであることを確認(Task用との混同を避ける)
        ' ※実際の実装では、フィールドの用途を識別するタグ等を持たせておくとより堅牢
        
        ' ここでは例として、指定されたフィールド名(Number2等)が一致するか確認
        ' 注: CustomFieldオブジェクトには直接 "Number2" というプロパティはないため、
        ' 実際の運用では、特定のカスタムフィールド名(例: "Calculated_Unit_Cost")で管理するのが定石。
        
        ' 今回は簡略化のため、特定のフィールドを特定するロジックをシミュレート
        If cf.Name = "Unit_Cost_Dynamic" Then 
            cf.Formula = formula
            found = True
            Exit For
        End If
    Next cf

    ' もしフィールドが存在しない場合は、新規作成またはエラーハンドリングを行う
    If Not found Then
        ' 運用環境では、フィールドの自動生成は推奨されない。
        ' 事前に定義されたテンプレートを使用するのが、エンタープライズ設計の基本である。
        Err.Raise vbObjectError + 514, "InjectResourceFormula", "ターゲットとなるカスタムフィールド 'Unit_Cost_Dynamic' が見つかりません。"
    End If

End Sub

4. 伝説的アーキテクトの視点:運用の極意

このコードを実戦投入するにあたり、シニアエンジニアが留意すべき「極限の知見」を3点提示する。

① フィールド名の「マジックナンバー」を排除せよ

コード例では `Unit_Cost_Dynamic` という名前のフィールドを対象としている。`Number1` や `Text1` といった予約語を直接コード内でハードコードするのは、レガシーな設計の典型である。
プロジェクトのカスタムフィールドには、必ず「システム管理用の名前」を付与し、VBA側からはその名前を介してアクセスする。これにより、ユーザーがフィールドの表示名(Label)を変更しても、自動化ロジックが壊れない疎結合な設計が実現できる。

② 計算コストの局所化

`CustomField.Formula` を書き換えた瞬間、Projectはプロジェクト全体の再計算を開始する。数万タスク、数千リソースを持つプロジェクトでこれを頻繁に実行すると、アプリケーションがハングアップしたかのような挙動を示す。
大量の更新を行う場合は、`Application.Calculation = pjManual` を使用して計算を停止させ、全注入が終わった後に `pjAutomatic` に戻す。これは、単なる「高速化」ではなく、「システムの安定稼働」のための必須要件である。

③ メモリ管理とCOMのライフサイクル

VBAはCOM(Component Object Model)の上で動作している。`Set app = Nothing` を忘れることは、メモリリークを許容することと同義だ。特に、ExcelやAccessといった他のOfficeアプリケーションと連携(Interop)させている場合、メモリ上のオブジェクトが解放されないことで、プロセスがゾンビ化し、サーバーサイドでの自動実行(Headless実行)を阻害する要因となる。

結言

カスタムフィールドの自動化は、単なる「便利機能」ではない。それは、プロジェクトのデータ構造そのものを、プログラムによって定義・制御する「メタプログラミング」の領域である。

リソースの属性をコードで読み解き、数式としてプロジェクトへ注入する。このアプローチこそが、変化し続けるビジネス要件に対し、最も堅牢かつスケーラブルに応答できる唯一の道である。

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