【テクニカル・上級編】Assignment.Costを直接操作して予算管理を行う際の注意点 – Project VBA解析バイブル

スポンサーリンク

伝説のチーフアーキテクトが説く:`Assignment.Cost` 直接操作の罠と、Project VBAにおける極限のコスト制御

Microsoft ProjectのVBA開発において、リソースコストの管理領域に踏み込んだ瞬間、多くのエンジニアは不可解な壁に突き当たる。
「なぜ、コードで `Assignment.Cost` に数値を代入した直後、Projectの再計算エンジンが走ると値が書き換わるのか?」
「なぜ、数千件のタスクを持つ大規模スケジュールでコストをバルク更新すると、メモリリークや致命的なパフォーマンス劣化を引き起こすのか?」

一般の入門書やリファレンスは、オブジェクトのプロパティを書き換える表層的な方法しか教えない。しかし、オブジェクトのライフサイクル、タスクとリソース、そしてアサインメント(割り当て)の三位一体の内部構造、さらにはProjectの計算エンジンが持つ「重み」を理解していなければ、実用に耐えるエンタープライズシステムなど構築できるはずがない。

今回は、`Assignment.Cost` をあえて直接操作し、ネイティブの自動計算機能を調教しながら予算管理を完全に掌握するための極限の知見を公開する。

—

1. Projectの計算エンジン(Calculation Engine)という「黒ミット」

大前提として、MS Projectは単なる表計算ソフトではない。CPM(クリティカルパス法)とリソース平準化アルゴリズムを裏で常時回し続ける、極めて重厚なスケジューリング・エンジンである。

通常、リソースのコスト(`Assignment.Cost`)は以下の公式で自動算出される。
$$\text{Cost} = \text{Work} \times \text{Standard Rate} + \text{Overtime Work} \times \text{Overtime Rate} + \text{Cost per Use}$$

この自動計算機能を無視して、VBAから無造作に `Assignment.Cost = 50000` などと代入すると、以下の矛盾が生じる。
1. エンジンの矛盾検知: 「工数(Work)と単価から導き出されるコスト」と「直接代入されたコスト」が乖離する。
2. 自動上書きの発生: Projectが再計算(Calculation)を実行した瞬間、入力したコストが元の計算値に強制リセットされる。
3. コスト変動の追跡不能: Earned Value(EV)分析におけるPV/EV/ACの整合性が崩壊し、PMOから致命的な差し戻しを受ける。

これを防ぐためには、Projectの自動計算チェーンを一時的に完全に停止させ、メモリ上で整合性を担保した上で、オブジェクトの状態を確定させるというアプローチが必要不可欠である。

—

2. 実装アーキテクチャ:安全かつ高速なコスト上書きのデザイパターン

以下のコードは、レガシー環境(Excel連携や外部ERPからのトランザクションデータ取り込み)を想定し、パフォーマンスの極限 optimization と安全性を両立させた実装例である。

画面の再描画(ScreenUpdating)や自動計算の停止をAPIレベル(VBAのネイティブ機能)で制御し、オブジェクトの解放まで徹底したプロダクション品質のコードだ。

Option Explicit

‘ ==============================================================================
‘ 模块名: clsCostController
‘ 概要: Assignment.Cost直接操作における整合性維持とパフォーマンス最適化
‘ 著者: 伝説のチーフアーキテクト
‘ ==============================================================================

Public Sub ForceUpdateAssignmentCost(ByVal tskID As Long, ByVal resUniqueID As Long, ByVal targetCost As Double)
Dim appPrj As MSProject.Application
Set appPrj = ActiveProject.Application ‘ 複数インスタンス混在を防ぐための厳密な参照

Dim originalCalcMode As Long

On Error GoTo ErrorHandler

‘ 1. パフォーマンスと整合性維持のための環境凍結
‘ 画面描画とバックグラウンド計算を停止し、CPUサイクルをVBAのメモリ操作に集中させる
originalCalcMode = appPrj.Calculation
appPrj.Calculation = pjManual
appPrj.ScreenUpdating = False

Dim tsk As MSProject.Task
Set tsk = FindTaskByID(appPrj, tskID)

If tsk Is Nothing Then
Err.Raise 9999, “ForceUpdateAssignmentCost”, “指定されたタスクIDが見つかりません: ” & tskID
End If

Dim assn As MSProject.Assignment
Set assn = FindAssignmentByResource(tsk, resUniqueID)

If Not assn Is Nothing Then
‘ 2. コスト固定化の布石
‘ ProjectはWorkが固定されているとCostを上書きするため、Workを固定モードに変更するか、
‘ あるいはCostRateTableやFixed Costを操作するアプローチをとる。
‘ ここでは予算管理の特命として直接Costプロパティを強制書き込みする。

‘ 警告: WorkとCostの同期を断つため、必要に応じてTaskのFixedCost等も併用する
assn.Cost = targetCost

‘ 3. オブジェクトの明示的解放(メモリリーク防止)
Set assn = Nothing
Else
Err.Raise 9998, “ForceUpdateAssignmentCost”, “指定されたリソースがタスクに割り当てられていません。”
End If

Set tsk = Nothing

‘ 4. 環境の復元と手動再計算のトリガー
appPrj.ScreenUpdating = True
appPrj.Calculation = originalCalcMode

‘ 整合性を強制的に保つための部分再計算
appPrj.Calculate
Exit Sub

ErrorHandler:
‘ 異常系:確実に環境を復元してメモリリークを防ぐ
On Error Resume Next
appPrj.ScreenUpdating = True
appPrj.Calculation = originalCalcMode

‘ オブジェクトの強制破棄
Set assn = Nothing
Set tsk = Nothing
Set appPrj = Nothing

MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical, “Cost Controller”
Resume Next
End Sub

‘ — ヘルパー関数群(オブジェクト探索の最適化) —

Private Function FindTaskByID(ByRef app As MSProject.Application, ByVal id As Long) As MSProject.Task
Dim t As MSProject.Task
For Each t In app.ActiveProject.Tasks
If Not t Is Nothing Then
If t.ID = id Then
Set FindTaskByID = t
Exit Function
End If
End If
Next t
Set FindTaskByID = Nothing
End Function

Private Function FindAssignmentByResource(ByRef tsk As MSProject.Task, ByVal resUniqueID As Long) As MSProject.Assignment
Dim a As MSProject.Assignment
For Each a In tsk.Assignments
If Not a Is Nothing Then
If a.ResourceUniqueID = resUniqueID Then
Set FindAssignmentByResource = a
Exit Function
End If
End If
Next a
Set FindAssignmentByResource = Nothing
End Function

—

3. シニアエンジニアが押さえるべき「3つのダークパターン」と対策

大規模なプロジェクトファイルをVBAで操作する際、以下の罠に落ちると、ファイルが破損するか、実行時エラー 1101(メモリ不足)などの不可解なクラッシュを引き起こす。

① `For Each` ループにおけるオブジェクト参照の放置

VBAのコレクション走査において、生成された `Assignment` や `Task` の参照 (`Set … = Nothing`) をループの各イテレーション、あるいはプロシージャ終了時に解放しないと、COMコンポーネントの参照カウンタがデクリメントされず、VBAのヒープ領域が肥大化する。

  • 対策: ループ内で一時的に取得したオブジェクトは、処理の終了時に必ず `Set obj = Nothing` を明示すること。

② 大量データ処理時の `ScreenUpdating` 放置

数千行のWBSに対して一括でコスト変更を行う際、描画フラグが `True` のままだと、1行書き換えるたびにGDI(Graphics Device Interface)経由でガントチャートの再描画が走る。

  • 対策: 処理の最初に `appPrj.ScreenUpdating = False`、最後に `True` を挟むだけで、実行速度が 最大50倍以上 向上する。

③ コスト手動上書き後の「アーンド・バリュー(EV)」の乖離

`Assignment.Cost` を無理やり書き換えると、プロジェクト管理指標である BCWP(得高) や ACWP(実コスト) の計算値がシステム標準のロジックと乖離する。

  • 対策: コストを直接書き換えたアサインメントについては、社内独自の監査ログ(Custom Fieldなど:`Text1` や `Cost1`)に「手動調整フラグ」と「調整前後の差分」を必ず書き込み、後続のBIツール(Power BI等)での集計時に補正が利く仕組みをアーキテクチャレベルで担保しておけ。

—

結言:自動化の裏にある「責任」

Project VBAにおけるリソース・コスト管理は、単なるプログラミングのスキルではない。それはProjectという強固なスケジューリング・エンジンの「内部思想」と対話し、意図通りに挙動をコントロールする高度な制御工学に等しい。

「動けばいい」という甘えを捨て、メモリのライフサイクル、スレッドの安全性、そしてデータの整合性を極限まで突き詰めたコードだけが、現場の信頼に耐えうる。
真の自動化エンジニアを目指すのであれば、APIの表層に惑わされず、その背後にあるオブジェクトの鼓動を聞き逃すな。

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