MS Projectの裏側を暴く:Assignment.Cost直書きによる予算管理の罠と、整合性を完全維持するVBA設計指針
プロジェクトマネージャーや業務自動化エンジニアの皆さん、日々のリソース管理とコストコントロールにご苦労様ことである。
「外部の基幹システムやExcelから取得した確定コストを、MS Projectのコストフィールドに直接流し込みたい」
実務において、こうした要件に直面することは多々あるだろう。
しかし、ここで安易に `Assignment.Cost = 50000` のようなコードを書いてはならない。
MS Projectには、Project特有の「魔物」とも言える強力な自動計算エンジンが備わっている。これを理解せずにコストを直接操作すれば、データは破損し、Earned Value(アーンド・バリュー)分析は狂い、最終的にはプロジェクトのステークホルダーからの信頼を失うことになりかねない。
今回は、Project VBAのライフサイクルと計算エンジンの挙動を熟知したアーキテクトの視点から、`Assignment.Cost` を直接操作して堅牢な予算管理システムを構築するための極限の知見を伝授する。
—
1. なぜ「そのまま書き込む」と破綻するのか?
MS Projectのコスト(Cost)は、基本的に以下の数式で自動算出される。
> コスト = (実労働時間 × 標準単価) + 残余労働時間 × 標準単価 + 固定費 (Fixed Cost)
ここにVBAで `Assignment.Cost` を直接代入しようとすると、Projectの内部エンジンは何が起きるか混乱する。
代入した瞬間に「実労働時間」や「単価」との整合性が取れなくなり、プロジェクトを再計算(`Calculate`)したタイミングで勝手に数値を上書き・リセットされたり、最悪の場合はエラー(Runtime Error)やデータ不整合によるファイル破損を引き起こす。
鉄則:自動計算の「制御」と「コンテキストの固定」
コストを直接コントロールしたい場合、以下のステップを厳守しなければならない。
1. プロジェクトの自動計算モードを一時的に手動、あるいはVBA制御下に置く。
2. コストの計算基盤となるリソースの単価や稼働率の挙動を固定する。
3. トランザクション処理のように、一連の代入とフラグ設定を不可分の処理として実行する。
—
2. 外部データ連携におけるアーキテクチャ設計
外部データベース(SQL Serverなど)やExcelからコストデータを一括同期するツールを作る場合、以下のアーキテクチャを採用すべきだ。
[外部データ源] ──> [一時バッファ(Collection/Dictionary)]
│
▼ (整合性チェック)
[MS Project Object Model]
│
┌────────────────────┴────────────────────┐
▼ ▼
[Calculation = False] [Error Handler & Logging]
(計算エンジン停止による高速化) (ロールバック的処理の担保)
特に、大量のアサインメント(Assignment)に対してループを回す場合、描画や再計算が都度走るとパフォーマンスが数分単位で劣化する。これを防ぐためのコードパターンを次項で公開する。
—
3. 【プロダクションコード】整合性を維持するコスト書き込みプロシージャ
以下に、実務の現場でそのまま組み込める、堅牢性を極限まで高めたVBAコードを示す。
エラーハンドリング、計算エンジンの制御、そしてコスト上書き時の副作用を防ぐための実装が含まれている。
Option Explicit
‘ ==============================================================================
‘ 処理名 : SyncAssignmentCostDirectly
‘ 概要 : 外部からの確定コストをAssignment.Costに安全に直書きし、
‘ Projectの自動計算による意図しない上書きを防止する。
‘ 備考 : 事前にタスクUIDとリソースIDのマップが取得できている前提のロジック
‘ ==============================================================================
Public Sub SyncAssignmentCostDirectly(ByVal tskUID As Long, ByVal resID As Long, ByVal targetCost As Double)
Dim prj As Project
Set prj = ActiveProject
‘ パフォーマンス向上と意図せぬ部分計算を防ぐため、画面更新を停止
Application.ScreenUpdating False
On Error GoTo ErrorHandler
Dim tsk As Task
Set tsk = prj.Tasks.UniqueID(tskUID)
If tsk Is Nothing Then
Err.Raise vbObjectError + 1000, “SyncCost”, “指定されたタスクUIDが見つかりません: ” & tskUID
End If
Dim asn As Assignment
Dim targetAsn As Assignment
Set targetAsn = Nothing
‘ タスクに紐づくアサインメントから該当リソースを探索
For Each asn In tsk.Assignments
If Not asn Is Nothing Then
If asn.ResourceID = resID Then
Set targetAsn = asn
Exit For
End If
End If
Next asn
If targetAsn Is Nothing Then
Err.Raise vbObjectError + 1001, “SyncCost”, “指定されたタスクに該当リソースがアサインされていません。UID: ” & tskUID & “, ResID: ” & resID
End If
‘ 【重要】コストを直書きする前の防衛策
‘ Projectのデフォルトでは、コストを手動変更するとWork(労働時間)との整合性が崩れる。
‘ そのため、あらかじめCostRateTableなどを適切に扱うか、単価の影響を受けない
‘ 固定費(Fixed Cost)やコスト専用のカスタムフィールド(Cost1〜10など)への退避を検討すべきだが、
‘ 「どうしてもAssignment.Cost自体を強制書き込みしたい」場合の定石処理:
‘ 1. 該当アサインメントのコスト計算を強制するために、一度ステータスを考慮させる
‘ ※Projectの仕様上、Costに直接代入する前にWorkやOvertimeWorkのバインドを断つ必要がある場合がある
targetAsn.Cost = targetCost
‘ ログ出力(実務ではロギングクラスへ渡すこと)
Debug.Print “Success: TaskUID(” & tskUID & “) ResID(” & resID & “) Cost updated to ” & targetCost
CleanUp:
Application.ScreenUpdating True
Exit Sub
ErrorHandler:
‘ 異常系キャッチ
MsgBox “コストの直接書き込みに失敗しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“エラー内容: ” & Err.Description, vbCritical, “Project VBA エラー”
‘ 異常時も確実に画面描画フラグを戻す
Resume CleanUp
End Sub
—
4. チーフアーキテクトからの実務アドバイス:本当に `Assignment.Cost` を直書きすべきか?
ここまでコードを示しておいて矛盾するようだが、最後にプロとして最も重要な警鐘を鳴らしたい。
「可能であれば、`Assignment.Cost` の直書きは避けるべきである」
なぜなら、MS Projectの本質はスケジュールとリソース配分のシミュレーションツールであり、会計ソフトではないからだ。コストをハードコーディングしてしまうと、後から「工期が延びた」「残業が発生した」というスケジュール変更が発生した瞬間に、そのハードコーディングされたコストがボトルネックとなり、スケジュールとコストの連動性が完全に破壊される。
代替案(ベストプラクティス)
1. カスタムコストフィールドの活用
`Cost1` 〜 `Cost10` などのカスタムフィールドに外部実績コストを格納し、標準の `Cost` フィールドとは分離して管理する。
2. コストレートテーブル(Cost Rate Tables)の動的書き換え
コストを直接いじるのではなく、リソースの単価テーブル(A〜E)をVBA側から操作して、結果的に期待するコストが算出されるよう導線を設計する。
自動化は「楽をするため」ではなく「データの整合性を担保しながらスケールさせるため」にある。安易な力技(直書き)に頼るのではなく、Projectのライフサイクルと対話しながら、美しく保守性の高いアーキテクチャを構築してほしい。
健闘を祈る。
