【実務・中級編】Assignment.ActualWorkの整合性を保ちながらリソースを置換する安全なマクロ – Project VBA解析バイブル

スポンサーリンク

MS ProjectのVBA(Project VBA)を扱うエンジニア諸君、ようこそ。

Microsoft ProjectはExcelとは似て非なる怪物だ。Excel感覚で「セルの値を書き換える」ような甘い考えでリソースの置換(リソース・サブスティテューション)に挑めば、瞬時にスケジュールの整合性は崩壊し、実績工数(Actual Work)は霧散し、プロジェクト全体のコスト計算は虚無へと消えるだろう。

特に、現場で頻発する「メンバーAが退場し、メンバーBが残りの作業を引き継ぐ」というシナリオ。これを単なるリソース名の書き換えや削除・追加で済ませていないか? それはデータの連続性を破壊する「禁忌」だ。

今回は、Assignment(割り当て)オブジェクトのライフサイクルを完全に掌握し、実績工数を1分たりとも損なうことなく、安全にリソースを置換する極限のロジックを伝授する。

—

1. なぜ「単純な置換」は失敗するのか

Project VBAにおいて、`Assignment` オブジェクトは「タスク」と「リソース」を結びつける単なるリンクではない。そこには、時系列の実績(Timephased Data)という重厚な履歴が刻まれている。

未熟なエンジニアが犯す典型的なミスは以下の3点だ。

1. 既存のAssignmentをDeleteする: これにより、そのリソースがこれまで積み上げてきた「実績工数」がタスクから完全に消滅する。
2. Resourceオブジェクトだけを差し替える: `Assignment.ResourceID` を強引に書き換えようとしても、Projectのエンジンはそれを許容しないか、予期せぬ再計算を引き起こす。
3. 計算モードを無視する: `pjAutomatic` 計算が走る状態で実績をいじると、依存関係にある後続タスクの開始日が勝手に動き、PMの意図しない「スケジュール汚染」を招く。

プロの設計は、「実績の継承」と「残存作業の再割り当て」を、トランザクション的に分離して実行することにある。

—

2. 実装すべき「安全な置換」のアーキテクチャ

リソースを置換する際、我々が守るべき手順は以下の通りだ。

1. 現状のキャプチャ: 置換対象リソースの「実績工数(ActualWork)」と「残存工数(RemainingWork)」を正確に取得する。
2. 実績の固定: 旧リソースの「残存工数」をゼロに設定し、そのリソースの役割を「過去」のものとして封印する。
3. 新リソースの割当: 新リソースを同一タスクに割り当て、旧リソースから引き継いだ「残存工数」を流し込む。
4. 整合性の検証: タスク全体の総工数(Work)が、置換前後で変化していないかをチェックする。

—

3. プロダクション・グレードのVBAコード例

このコードは、単に動くだけではない。エラーハンドリング、計算モードの制御、そして「実績の整合性」を最優先に設計された、現場投入用のプロトタイプだ。

‘
‘ 概要: 指定したタスクのリソースを、実績工数を維持したまま安全に置換する
‘ 引数: tsk – 対象Taskオブジェクト
‘ oldResName – 置換前のリソース名
‘ newResName – 置換後のリソース名
‘
Public Sub SecureReplaceResource(ByRef tsk As Task, oldResName As String, newResName As String)
Dim asgn As Assignment
Dim oldAsgn As Assignment
Dim newAsgn As Assignment
Dim actWork As Double
Dim remWork As Double
Dim calcMode As Long

‘ 1. 事前チェック
If tsk Is Nothing Then Exit Sub
If tsk.Summary Then Exit Sub ‘ サマリータスクには直接割り当てない

On Error GoTo ErrorHandler

‘ 2. パフォーマンスと整合性のための計算停止
calcMode = Application.Calculation
Application.Calculation = pjManual

‘ 3. 旧リソースの割り当てを特定
For Each asgn In tsk.Assignments
If asgn.ResourceName = oldResName Then
Set oldAsgn = asgn
Exit For
End If
Next asgn

If oldAsgn Is Nothing Then
Debug.Print “Error: 指定された旧リソースが見つかりません。 [” & oldResName & “]”
GoTo CleanUp
End If

‘ 4. 工数情報の退避
‘ ※Projectは内部的に「分」単位で管理しているため、そのまま扱うのが安全
actWork = oldAsgn.ActualWork
remWork = oldAsgn.RemainingWork

‘ 5. 旧リソースのクローズ(残存作業をゼロにする)
‘ これにより、旧リソースは「これまでの実績分だけ働いた」という状態で固定される
oldAsgn.RemainingWork = 0

‘ 6. 新リソースの割り当てと工数の注入
‘ すでに割り当てられていないか確認し、なければ追加
Set newAsgn = Nothing
For Each asgn In tsk.Assignments
If asgn.ResourceName = newResName Then
Set newAsgn = asgn
Exit For
End If
Next asgn

If newAsgn Is Nothing Then
Set newAsgn = tsk.Assignments.Add(ResourceID:=GetResourceIDByName(newResName))
End If

‘ 新リソースに残存工数を引き継ぐ
newAsgn.RemainingWork = remWork

‘ 7. (オプション) 履歴管理のためにノートに記録
tsk.Notes = tsk.Notes & vbCrLf & Now & “: リソース置換 [” & oldResName & ” -> ” & newResName & “]”

‘ 再計算を実行
Application.Calculation = pjAutomatic
Application.CalculateAll

MsgBox “リソースの置換が正常に完了しました。”, vbInformation

CleanUp:
Application.Calculation = calcMode
Exit Sub

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

‘ — ヘルパー関数: 名前からリソースIDを取得 —
Private Function GetResourceIDByName(resName As String) As Long
Dim r As Resource
For Each r In ActiveProject.Resources
If r.Name = resName Then
GetResourceIDByName = r.ID
Exit Function
End If
Next r
‘ 見つからない場合は新規作成するなどのロジックをここに
Err.Raise 9999, , “リソース ‘” & resName & “‘ がプロジェクト内に存在しません。”
End Function

—

4. チーフアーキテクトによる深層解説

① `Assignment.Delete` を使わない理由

上記のコードでは、`oldAsgn.Delete` を呼んでいない。これが重要だ。
`Delete` を呼ぶと、そのタスクにおける旧担当者の実績データが消え、エンタープライズ・プロジェクト管理(EPM)において「誰がいつまで働いたか」という監査証跡が失われる。`RemainingWork = 0` とすることで、「過去の事実は残し、未来の予定だけを消す」という状態を作り出している。

② 計算モード `pjManual` の重要性

MS Projectの計算エンジンは、1つのプロパティ変更に対して数千行の再計算を走らせることがある。ループの中でこれを許可すると、処理が劇的に遅くなるだけでなく、一時的な不整合状態をエンジンが勝手に「補正」しようとして、工数データが微妙にズレる現象(丸め誤差やカレンダーの競合)が発生する。一連の処理が終わるまで、エンジンは黙らせておくのが鉄則だ。

③ データベース・外部ファイル連携への布石

実務では、この置換リストをExcelや基幹システム(SAP等)から読み込むことが多いだろう。その際は、以下の点に注意せよ。

  • リソース名の名寄せ: 外部データの “田中 太郎” と Project内の “田中太郎”(空白なし)は別人と判定される。必ず `Trim` や正規化関数を挟むこと。
  • 一括処理のバッチ化: 1タスクごとに `CalculateAll` を呼ぶのではなく、全タスクの置換が終わった後に1回だけ呼ぶ設計にすれば、パフォーマンスは10倍以上に跳ね上がる。

—

5. 結論:プロジェクトの「記憶」を守れ

MS Projectの自動化において、最も価値があるのは「綺麗なコード」ではなく、「データの完全性(Integrity)を維持する設計」だ。

今回紹介した手法は、単なるリソースの入れ替えではない。プロジェクトの履歴という「記憶」を守りつつ、スムーズに未来の計画へバトンを渡すための儀式である。この思想を理解すれば、あなたの作成するツールは、単なる便利道具を超えて、PMOが全幅の信頼を寄せる「プロジェクトの守護神」へと昇華するだろう。

このコードをベースに、自社の運用ルール(コスト単価の変動対応、カレンダーの考慮など)を組み込み、最強の自動化工機を構築してほしい。

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