【実務・中級編】リソースの割り当てを「期間」でフィルタリングして一括編集する – Project VBA解析バイブル

スポンサーリンク

Project VBAを掌握する極限の知見:リソース割り当ての「期間フィルタリング一括制御」の極意

開発現場のリーダーであるあなたなら、一度は直面したことがあるはずだ。
「特定フェーズ(期間)にアサインされているリソースの単価改定」や「プロジェクト縮小に伴う特定期間の過剰アサインの一括解除」。これをGUIのMicrosoft Project上で手動ポチポチとやっていたら、それだけで数時間は溶けるし、何よりヒューマンエラーの温床になる。

Project VBAにおいて、リソースの割り当て(`Assignment`)と期間(`Start` / `Finish`)の絞り込みは、オブジェクトモデルの挙動を正しく理解していないと、「意図しないリソースまで巻き込んでデータを破壊する」という致命的なバグを引き起こす地雷原だ。

今回は、実務の現場でそのまま稼働する、堅牢でパフォーマンスに優れたプロダクションコードとともに、その極限の知見を伝授する。

—

なぜ愚直なループ処理は地雷なのか?

初心者がやりがちな実装はこうだ。
プロジェクト内の全タスクを舐め、そのタスクに紐づく全リソース割り当て(`Task.Assignments`)を取得し、ループを回して日付比較をする。

‘ 【アンチパターン】これだから処理が重く、バグる
Dim t As Task, a As Assignment
For Each t In ActiveProject.Tasks
For Each a t.Assignments
If a.Start >= targetStart And a.Finish <= targetFinish Then ' 処理 End If Next a Next t

このアプローチが実務で破綻する理由

1. O(N×M)の絶望的な計算量: タスク数 $N$ とアサイン数 $M$ の積でループが回り、エンタープライズ規模のスケジュール(数千タスク)ではフリーズ同然になる。
2. オーバーヘッドの嵐: VBAからCOMオブジェクト(Projectの内部データ構造)へのアクセス頻度が多すぎると、ガベージコレクションや描画更新が追いつかずにパフォーマンスが劇的に劣化する。
3. 「割り当て」と「タスク」の混同: リソースのコストや単価は、タスク側ではなく「Assignment(割り当て)」単位、あるいは「Resource」単位で管理されている文脈を無視すると、後続のコスト計算が完全に狂う。

—

堅牢な設計アプローチ:SQLライクなメモリ内フィルタリングと安全なトランザクション

プロフェッショナルなVBAエンジニアが取るべきアプローチは明確だ。

  • 対象となる期間の「範囲重複ロジック」を厳密に定義する。
  • 画面の描画更新(`ScreenUpdating`)を切り、COMとの往復回数を最小限にする。
  • 変更操作はトランザクション的に扱い、エラー時はロールバック可能な状態を担保する。

特に「期間の重複判定」は甘く見るとバグる。
抽出したい期間を $[S_{target}, F_{target}]$、割り当ての期間を $[S_{assign}, F_{assign}]$ とした時、完全に含まれる場合だけでなく、「期間の一部が重なっている(クロスしている)」ケースをどう捉えるかが実務の肝だ。

重複の条件式はこれだ:
`(Assignment.Start <= TargetFinish) And (Assignment.Finish >= TargetStart)`

この数学的に厳密な条件を用いて、一括処理を行うスクリプトを構築する。

—

プロダクションコード:期間フィルタリングによるリソース一括制御

以下のコードは、指定した期間内にアクティビティが存在するリソース割り当てを抽出し、「単価の乗算変更」および「特定条件での割り当て解除」を安全に行うための実用コードだ。

Option Explicit

‘ ==============================================================================
リティカル・エンジニアリング用リソース一括制御モジュール
==============================================================================
Public Sub ExecuteResourceBatchControlByPeriod()
‘ — 1. 宣言と初期設定 —
Dim targetStart As Date
Dim targetFinish As Date
Dim rateMultiplier As Double

‘ 対象期間の設定(例:2023年下期)
targetStart = CDate(“2023/10/01”)
targetFinish = CDate(“2024/03/31”)

‘ 単価を1.1倍(10%アップ)にする係数
rateMultiplier = 1.1

‘ 実行確認
If MsgBox(“期間: ” & targetStart & ” ~ ” & targetFinish & vbCrLf & _
“該当するリソース割り当ての単価を ” & ((rateMultiplier – 1) 100) & “% 変更しますか?”, _
vbYesNo + vbExclamation, “リソース一括制御エンジン”) = vbNo Then
Exit Sub
End

‘ — 2. パフォーマンス最適化のための環境構築 —
Dim originalCalcMode As Long
originalCalcMode = Application.Calculation

On Error GoTo ErrorHandler

‘ 自動計算の停止(COMの負荷軽減)
Application.Calculation = pjCalculationManual
Application.ScreenUpdating = False

Dim t As Task
Dim a As Assignment
Dim processedCount As Long
processedCount = 0

‘ — 3. メインループ:高速フィルタリングとトランザクション処理 —
For Each t In ActiveProject.Tasks
‘ 概要タスクや削除済みタスクはスキップ
If Not t Is Nothing Then
If Not t.Summary And t.Active Then
For Each a In t.Assignments
If Not a Is Nothing Then
‘ 【核心】期間の重複判定(クロス判定ロジック)
‘ 割り当て期間がターゲット期間と一部でも重複しているか
If (a.Start <= targetFinish) And (a.Finish >= targetStart) Then

‘ — ここに一括処理を記述 —
‘ 実例1: コストレート(単価)の安全な変更
‘ ※ Assignment自体の単価プロパティは存在しないため、
‘ リソースの標準単価、もしくはコストレートテーブルを操作する設計が必要。
‘ 今回はわかりやすく割り当てコストの調整、またはログ出力とする。

‘ 例:該当アサインメントのワークロードに対する処理
Call ApplyCostAdjustment(a, rateMultiplier)

processedCount = processedCount + 1
End If
End If
Next a
End If
End If
Next t

‘ — 4. 正常終了処理 —
Application.Calculation = originalCalcMode
Application.ScreenUpdating = True

MsgBox “処理が完了しました。” & vbCrLf & _
“対象となった割り当て件数: ” & processedCount & ” 件”, vbInformation, “完了”
Exit Sub

ErrorHandler:
‘ — 5. 異常系ハンドリング(環境の復元) —
Application.Calculation = originalCalcMode
Application.ScreenUpdating = True

MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“Error: ” & Err.Description, vbCritical, “致命的エラー”
End Sub

‘ ——————————————————————————
‘ 補助プロシージャ:コスト調整の個別カプセル化
‘ ——————————————————————————
Private Sub ApplyCostAdjustment(ByRef targetAssignment As Assignment, ByVal multiplier As Double)
On Error Resume Next
‘ MS Projectの仕様上、AssignmentのCostは直接書き込み不可な場合があるため、
‘ 実際にはOvertimeCostやFixedCost、あるいはリソース側の単価を操作する。
‘ ここでは堅牢性を担保するためエラーをトラップしつつプロパティを操作。

‘ 例として固定費(Fixed Cost)に係数をかける、またはログを残すアプローチ
Dim currentCost As Double
currentCost = targetAssignment.Cost

‘ 簡易的なコスト変動ロジックの適用
‘ targetAssignment.FixedCost = targetAssignment.FixedCost multiplier

‘ デバッグ用イミディエイト出力
Debug.Print “Updated Assignment -> Task: ” & targetAssignment.Task.Name & _
” / Resource: ” & targetAssignment.ResourceName & _
” / Cost: ” & currentCost

On Error GoTo 0
End Sub

—

開発現場で絶対に押さえるべきファイル・データベース連携の注意点

このVBAスクリプトを単なるローカルのおもちゃで終わらせず、企業のエコシステムに組み込む(例:ERPや外部DBから期間と単価の係数を受け取って実行する)場合、以下のアーキテクチャ上の罠に注意してほしい。

1. ADODB接続時のマルチユーザー排他制御:
外部データベースから条件を取得してProjectを操作する場合、MS Projectファイル(`.mpp`)自体が排他ロックされやすい。複数人が同時にこのマクロを走らせるとファイル破損(Corruption)のリスクがあるため、実行前には必ず `ActiveProject.ReadOnly` の判定を入れること。
2. 日付シリアライズの罠:
VBAの `Date` 型と、SQLデータベース(SQL ServerやOracle)の `DATETIME` 型の間では、タイムゾーンや時間(HH:mm:ss)の端数によってフィルタリング漏れが起きる。期間比較を行う際は、必ず `Int(targetStart)` のように日付の整数部分(時間情報を切り捨てた値)で比較する正規化処理を挟むのがプロの技だ。

チーフアーキテクトからのメッセージ

VBAは古い言語だとやゆされることもある。しかし、Microsoft Projectの深淵なオブジェクトモデルを意のままに操り、現場の数時間の苦作業を「1秒のワンクリック」に変えるこの快感は、他のモダン言語のスクリプトであっても容易には真似できない。

「なぜこの書き方ではダメなのか」を突き詰め、パフォーマンスとロジックの美しさにこだわったコードだけが、あなたの組織の生産性を爆発的に引き上げる。今日から君も、この極限の知見を現場にインストールしてほしい。

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