皆さん、こんにちは!チーフアーキテクトの〇〇です。
プロジェクト管理の現場で、皆さんが日々奮闘されていることは、私も痛いほど理解しています。特に、コスト管理はプロジェクトの成否を分ける重要な要素ですよね。その中でも、リソースの「単価レートテーブル」は、プロジェクトの予算にダイレクトに影響を与える、非常に強力な機能です。
しかし、プロジェクトのフェーズが変わるたびに、あるいは契約条件の見直しがあった際に、手作業で何十、何百というリソースの単価レートテーブルを切り替えるのは、時間と手間がかかるだけでなく、ヒューマンエラーの温床にもなりかねません。
「もっとスマートに、もっと正確に、そしてもっと迅速に、コストシミュレーションを行いたい!」
そう願う皆さん、今日からその願いをProject VBAで叶えましょう。この記事では、リソースの「単価レートテーブルA〜E」をVBAで一括切り替えし、即座に予算への影響を算出するプロ向けのツールを、皆さんと一緒に作り上げていきます。
マクロの記録から一歩踏み出し、Project VBAの本質を掴むための、まさに「極限の知見」を魂を込めてお伝えしますので、ぜひ最後までお付き合いください。ここをクリアすれば、Project VBAの基本はバッチリですよ!
—
なぜProject VBAで単価レートテーブルを操作するのか?
まず、なぜ私たちはこの自動化に時間と労力をかけるべきなのでしょうか?
1. 圧倒的な効率化と時間短縮
手動で単価レートテーブルを切り替える場合、Projectのリソースシートを開き、各リソースの情報を編集し、ドロップダウンから選択…と、気の遠くなるような作業が続きます。VBAを使えば、この一連の作業はほんの一瞬で完了します。
2. コストシミュレーションの即時性
プロジェクトには様々なシナリオが考えられます。例えば、「もし、すべてのリソースを契約Bの単価で雇ったら、総コストはどうなるだろう?」「フェーズ2以降は、熟練度の高いリソースを投入するから、単価テーブルCを適用してみよう。」このような問いに対し、VBAは瞬時に答えを提示してくれます。
3. ヒューマンエラーの排除
手作業は、どうしても入力ミスや選択ミスがつきものです。VBAは一度正しくコードを書けば、何度実行しても同じ正確な結果をもたらします。これにより、信頼性の高い予算計画を立てることが可能になります。
Projectのリソースとコスト、そして単価レートテーブルの基礎知識
VBAコードを書き始める前に、Projectがリソースのコストをどのように管理しているのか、その基本的な概念を理解しておきましょう。
リソース(Resource)オブジェクトとは?
Project VBAにおいて、プロジェクト内の各リソースは `Resource` オブジェクトとして扱われます。このオブジェクトは、リソース名、種類(作業、資材、コスト)、標準単価、超過単価、そして今日の主役である「単価レートテーブル」といった、様々なプロパティを持っています。
単価レートテーブル(CostRateTable)の奥深さ
ProjectのUI上では「リソース情報」ダイアログの「コスト」タブに「単価レートテーブル」としてAからEまでの選択肢がありますよね。これは単なる選択肢ではなく、Projectが持つ非常に強力なコスト管理機能の中核をなすものです。

図:Projectのリソース情報ダイアログにある単価レートテーブルの選択肢
実は、このA〜Eの各テーブルは、それぞれ独立した「有効日」「標準単価」「超過単価」「単価」といったレートの組み合わせを持つことができます。例えば、テーブルAでは基本レート、テーブルBでは緊急時レート、テーブルCでは特定のパートナー企業との契約レート…といった使い分けが可能です。
VBAの世界では、各 `Resource` オブジェクトは `CostRateTables` というコレクションを持っています。このコレクションには一つ以上の `CostRateTable` オブジェクトが含まれており、それぞれの `CostRateTable` オブジェクトが特定の単価レートテーブル(A〜Eのどれか)の定義と、その定義がいつから有効になるか(`EffectiveDate`)を保持しています。
今回のテーマである「単価レートテーブルA〜Eを切り替える」とは、具体的には、リソースに割り当てられている `CostRateTable` オブジェクトの `RateTable` プロパティを変更することを意味します。これにより、そのリソースがProject全体のコスト計算でどのレートテーブルを参照するかが決まります。
VBAでリソースと単価レートテーブルを操作する基本
それでは、いよいよVBAの世界へ飛び込んでみましょう。
1. Projectオブジェクトモデルの入り口
VBAからProjectを操作する際の起点となるのは、常に `Application` オブジェクトです。そこから現在開いているプロジェクト (`ActiveProject`) へとアクセスします。
Dim app As MSProject.Application ‘ Projectアプリケーションオブジェクト
Dim prj As MSProject.Project ‘ アクティブなプロジェクトオブジェクト
Set app = Application ‘ 現在のProjectアプリケーションを取得
Set prj = app.ActiveProject ‘ 現在開いているプロジェクトを取得
2. リソースコレクションへのアクセス
プロジェクト内の全てのリソースは、`Project` オブジェクトの `Resources` コレクションに格納されています。
Dim res As MSProject.Resource ‘ 個々のリソースオブジェクト
‘ プロジェクト内の全てのリソースをループ処理する例
For Each res In prj.Resources
‘ ここで各リソースに対する処理を記述
Debug.Print res.Name ‘ リソース名を表示
Next res
3. 単価レートテーブルオブジェクトへのアクセス
ここが本日の核心です。各 `Resource` オブジェクトは、`CostRateTables` というコレクションを持っています。このコレクションの中には、実際に単価レートの情報を定義する `CostRateTable` オブジェクトが存在します。
通常、リソースには一つ(有効日の指定がない場合)または複数(有効日を設定してレートが変更される場合)の `CostRateTable` オブジェクトが存在します。今回の「単価レートテーブルA〜Eの切り替え」は、この `CostRateTable` オブジェクトの `RateTable` プロパティを操作することで実現します。
Dim res As MSProject.Resource
Dim crt As MSProject.CostRateTable ‘ コストレートテーブルオブジェクト
For Each res In prj.Resources
‘ 作業リソースのみが単価レートテーブルを持ちます
If res.Type = pjResourceTypeWork Then
For Each crt In res.CostRateTables
‘ ここで crt オブジェクトのプロパティを操作します
‘ crt.RateTable プロパティが、A〜Eのどのテーブルを指すかを決定します
Debug.Print “リソース: ” & res.Name & “, 現在のレートテーブル: ” & crt.RateTable
‘ 例えば、レートテーブルをBに設定する場合
‘ crt.RateTable = pjRateTableB
Exit For ‘ 通常、最初のCostRateTableオブジェクトを操作すれば十分
Next crt
End If
Next res
`pjRateTableA` から `pjRateTableE` は、Project VBAが提供する定数(列挙体 `PjRateTable` のメンバー)です。これらを使うことで、コードの可読性が向上し、入力ミスを防ぐことができます。
単価レートテーブルを一括切り替えするVBAコードの実装
それでは、いよいよ本命のコードです。ユーザーが指定した単価レートテーブルに、プロジェクト内の全作業リソースを一括で切り替えるVBAマクロを作成しましょう。
コードの全体像
‘—————————————————————————————————-
‘ マクロ名: SwitchResourceCostRateTable
‘ 目的: プロジェクト内の全作業リソースに対し、ユーザーが選択した単価レートテーブル(A〜E)を
‘ 一括で適用し、適用後のプロジェクト総コストを表示するコストシミュレーションツール。
‘ 開発者: チーフアーキテクト
‘ 最終更新日: 2023-10-27
‘—————————————————————————————————-
Sub SwitchResourceCostRateTable()
‘ — オブジェクト変数の宣言 —
Dim prj As Project ‘ アクティブなProjectオブジェクト
Dim res As Resource ‘ 個々のリソースオブジェクト
Dim crt As CostRateTable ‘ リソースのコストレートテーブルオブジェクト
‘ — 変数の宣言 —
Dim targetRateTable As PjRateTable ‘ 適用する単価レートテーブル(PjRateTable列挙体)
Dim strInput As String ‘ ユーザーからの入力文字列
Dim bSuccess As Boolean ‘ 処理が成功したかを示すフラグ
‘ — エラーハンドリングの開始 —
On Error GoTo ErrorHandler
‘ — 処理の準備 —
‘ 現在開いているプロジェクトを取得
Set prj = ActiveProject
‘ — ユーザーからの入力受付 —
‘ ユーザーに適用したい単価レートテーブルを選択させるためのInputBoxを表示
strInput = InputBox(“適用したい単価レートテーブルを選択してください。” & vbCrLf & _
“A, B, C, D, E のいずれかを入力してください。”, _
“単価レートテーブルの選択”, “A”) ‘ デフォルト値は”A”
‘ 入力値に基づいて、適用するPjRateTableの定数を決定
Select Case UCase(strInput) ‘ 大文字に変換して比較(Aとaを区別しない)
Case “A”: targetRateTable = pjRateTableA
Case “B”: targetRateTable = pjRateTableB
Case “C”: targetRateTable = pjRateTableC
Case “D”: targetRateTable = pjRateTableD
Case “E”: targetRateTable = pjRateTableE
Case Else
‘ 無効な入力の場合、エラーメッセージを表示して処理を中断
MsgBox “無効な入力です。「A」〜「E」のいずれかを入力してください。”, vbCritical, “入力エラー”
Exit Sub ‘ サブプロシージャを終了
End Select
‘ — パフォーマンスとユーザー体験の向上 —
‘ 画面更新を一時停止し、処理の高速化とチラつき防止を図る
Application.ScreenUpdating = False
‘ Projectの自動計算を一時的に手動に設定(大規模プロジェクトで効果的)
‘ これにより、各リソースの変更ごとに再計算が走るのを防ぎ、処理速度を向上させる
Application.Calculation = pjManual
‘ — 全リソースの単価レートテーブル一括切り替え処理 —
‘ プロジェクト内の全てのリソースをループ処理
For Each res In prj.Resources
‘ リソースオブジェクトが有効な場合のみ処理を続行(念のため)
If Not res Is Nothing Then
‘ 作業リソース(pjResourceTypeWork)のみを対象とする
‘ 材料リソースやコストリソースは単価レートテーブルを持たないため、ここでは除外
If res.Type = pjResourceTypeWork Then
‘ 各リソースにはCostRateTablesコレクションがある
‘ 通常、デフォルトでは一つ以上のCostRateTableオブジェクトがコレクションに含まれる
‘ そのCostRateTableオブジェクトのRateTableプロパティを変更することで、
‘ どの単価レートテーブル(A〜E)をアクティブにするかを制御する
For Each crt In res.CostRateTables
‘ CostRateTableオブジェクトのRateTableプロパティを変更
‘ これにより、そのリソースのコスト計算に使用される単価レートテーブルが切り替わる
crt.RateTable = targetRateTable
‘ 今回の目的はA〜Eの切り替えなので、一つのCostRateTableオブジェクトを操作すれば十分
Exit For ‘ 最初のCostRateTableオブジェクトのみを対象とする
Next crt
End If
End If
Next res
‘ 全ての変更が完了したら、Projectに再計算を強制する
‘ これにより、単価レートテーブルの変更がプロジェクト全体のコストに正確に反映される
Application.CalculateAll
bSuccess = True ‘ 処理が成功したことを示すフラグを立てる
‘ — エラーハンドリング —
ErrorHandler:
‘ 画面更新と計算モードを元に戻す
Application.ScreenUpdating = True
Application.Calculation = pjAutomatic
‘ 処理結果のメッセージ表示
If bSuccess Then
‘ 成功した場合、適用されたテーブルと現在のプロジェクト総コストを表示
MsgBox “全てのリソースの単価レートテーブルを「” & UCase(strInput) & “」に切り替えました。” & vbCrLf & _
“現在のプロジェクトの総コスト: ” & Format(prj.Cost, “Currency”), _
vbInformation, “処理完了”
Else
‘ エラーが発生した場合、エラーメッセージを表示
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical, “エラー”
End If
‘ — オブジェクト変数の解放(メモリリーク防止のため) —
Set crt = Nothing
Set res = Nothing
Set prj = Nothing
End Sub
コードの解説
1. オブジェクトと変数の宣言:
- `Project`, `Resource`, `CostRateTable` といったProject固有のオブジェクト型を宣言します。
- `PjRateTable` はProject VBAが提供する列挙型で、`pjRateTableA` から `pjRateTableE` までの定数を持ちます。これを使うことで、A〜Eを数値ではなく意味のある名前で扱えます。
- `strInput` でユーザーの入力を受け取ります。
2. ユーザーからの入力:
- `InputBox` を使って、ユーザーにどの単価レートテーブルに切り替えたいかを選択してもらいます。
- `Select Case UCase(strInput)` で、入力された文字列を大文字に変換し、対応する `PjRateTable` 定数に変換します。無効な入力があった場合は処理を中断します。
3. パフォーマンスの最適化:
- `Application.ScreenUpdating = False`: マクロ実行中のProject画面の更新を一時的に停止します。これにより、処理速度が大幅に向上し、ユーザーは画面のチラつきに悩まされることがありません。処理の最後に `True` に戻すことを忘れないでください。
- `Application.Calculation = pjManual`: 大規模なプロジェクトでは、リソースの単価変更一つ一つでProjectが自動的に再計算を行うと、非常に時間がかかります。これを手動モードに一時的に切り替え、全ての変更後に一括で `Application.CalculateAll` を実行することで、パフォーマンスを向上させます。これも最後に `pjAutomatic` に戻します。
4. リソースのループと単価レートテーブルの変更:
- `For Each res In prj.Resources`: プロジェクト内の全てのリソースを一つずつ処理します。
- `If res.Type = pjResourceTypeWork Then`: ここが重要です。 「単価レートテーブル」は、主に「作業」リソースに適用される概念です。資材リソースやコストリソースは単価レートテーブルを持たないため、誤ってアクセスしようとするとエラーになります。この条件で作業リソースのみを対象とします。
- `For Each crt In res.CostRateTables`: 各作業リソースが持つ `CostRateTables` コレクションをループします。通常、このコレクションには一つ(有効日未指定)または複数(有効日指定でレート変更)の `CostRateTable` オブジェクトが含まれます。今回の目的は「A〜Eの切り替え」なので、最初の `CostRateTable` オブジェクトの `RateTable` プロパティを変更すれば十分です。
- `crt.RateTable = targetRateTable`: ここで、ユーザーが選択した単価レートテーブル(`pjRateTableA` など)を `RateTable` プロパティに設定します。
5. コストの再計算と結果表示:
- `Application.CalculateAll`: 全てのリソースの単価レートテーブルを変更した後、Project全体のコストを強制的に再計算させます。これにより、変更がプロジェクトの総コストに正確に反映されます。
- `MsgBox … Format(prj.Cost, “Currency”)`: 処理が成功したら、どのテーブルが適用されたかと、変更後のプロジェクト総コストをメッセージボックスで表示します。`Format` 関数を使うことで、通貨形式で分かりやすく表示できます。
6. エラーハンドリングとオブジェクト解放:
- `On Error GoTo ErrorHandler`: エラーが発生した場合に指定した `ErrorHandler` へジャンプします。
- `ErrorHandler:` ラベル以下で、画面更新や計算モードを元に戻し、エラーメッセージを表示します。
- `Set crt = Nothing`, `Set res = Nothing`, `Set prj = Nothing`: メモリリークを防ぐため、使用したオブジェクト変数を解放します。これはVBAの堅牢なコードを書く上で非常に重要な習慣です。
コストシミュレーションへの発展と「極限の知見」
このマクロは、単価レートテーブルの切り替えだけでなく、即座にプロジェクトの総コストを確認できるため、強力なコストシミュレーションツールとして機能します。
さらなる応用例:
- 特定のタスクやフェーズに限定したシミュレーション:
現在のコードはプロジェクト全体のリソースを対象としていますが、特定のタスクに割り当てられたリソースのみを対象にする、といった応用も可能です。
‘ 特定のタスクに割り当てられたリソースを対象にする場合のヒント
‘ Dim tsk As Task
‘ Set tsk = prj.Tasks(“特定のタスク名”) ‘ または prj.Tasks(インデックス)
‘ For Each assign In tsk.Assignments
‘ Set res = assign.Resource
‘ ‘ res.CostRateTables を操作…
‘ Next assign
- 単価レートテーブル定義の変更:
今回の記事では、既存のA〜Eのテーブルを「切り替える」ことに焦点を当てましたが、VBAを使えば、各 `CostRateTable` オブジェクトの `StandardRate` や `OvertimeRate` プロパティを直接変更し、テーブル自体の定義を動的に調整することも可能です。これにより、より詳細な「もしも」のシナリオをシミュレーションできます。ただし、その場合は変更前のレートを保存しておくなどの工夫が必要になります。
- レポート出力の自動化:
`MsgBox` だけでなく、Projectの標準レポート機能や、Excelへのデータエクスポート機能をVBAで呼び出すことで、より詳細なコストレポートを自動生成できます。
ここが「極限の知見」:オブジェクトのライフサイクルとProjectの計算エンジン
`CostRateTable` オブジェクトは、単なるUI上の選択肢の裏側にある、具体的な「レートと有効日」の組み合わせを管理するエンティティです。このオブジェクトの `RateTable` プロパティを変更するということは、Projectの内部的なコスト計算エンジンに対し、「このリソースのこの期間の作業は、今後はこの定義のレートで計算しなさい」と指示していることになります。
- 即時性の裏側: `Application.CalculateAll` を実行することで、Projectは割り当てられた作業の期間、リソースの単価レートテーブル、有効日などを総合的に考慮し、プロジェクト全体のコストを再計算します。この一連のプロセスは、Projectの強力な計算エンジンによって瞬時に行われます。VBAで自動化することで、このエンジンのパワーを最大限に引き出すことができます。
- 永続化: VBAで `RateTable` プロパティを変更し、ファイルを保存すれば、その変更はProjectファイルに永続化されます。これは単なる一時的な表示の変更ではなく、プロジェクトのデータそのものへの変更です。
陥りやすいエラーとその対策
どんなに完璧なコードでも、予期せぬ状況でエラーが発生することはあります。特に初学者が陥りやすいポイントと対策を解説します。
1. オブジェクト参照エラー(「オブジェクトが必要です」「オブジェクトが設定されていません」)
- 原因: `Set` ステートメントでオブジェクト変数に何も割り当てられていないのに、そのプロパティやメソッドにアクセスしようとした場合。例えば、`ActiveProject` が開かれていないのに `Set prj = ActiveProject` とすると `prj` が `Nothing` になります。
- 対策: `If Not obj Is Nothing Then` のように、オブジェクトが有効かどうかを常にチェックする習慣をつけましょう。
2. 型の不一致エラー
- 原因: 想定されるデータ型と異なる値を代入しようとした場合。例えば、`InputBox` から文字列を受け取ったのに、数値型変数に直接代入しようとした場合など。
- 対策: `Select Case` や `CInt()`, `CDbl()` のような型変換関数を適切に利用しましょう。今回のコードでは `Select Case UCase(strInput)` で文字列を定数に変換しています。
3. リソースタイプによるアクセス制限
- 原因: 作業リソースのみが持つプロパティ(例: `StandardRate` や `CostRateTables`)を、資材リソースやコストリソースに適用しようとした場合。
- 対策: `If res.Type = pjResourceTypeWork Then` のように、リソースのタイプを確認してから処理を行うようにしましょう。これは今回のコードで既に対策済みです。
4. 画面更新忘れによるフリーズ感
- 原因: `Application.ScreenUpdating = False` にしたまま、`True` に戻し忘れると、Projectの画面が更新されなくなり、マクロが終了してもフリーズしたように見えます。
- 対策: `On Error GoTo ErrorHandler` でエラー発生時にも必ず `Application.ScreenUpdating = True` を実行するようにし、正常終了時も `End Sub` の直前で必ず元に戻しましょう。
これらの対策を講じることで、あなたのVBAコードはより堅牢で、ユーザーフレンドリーなものになります。
まとめと次のステップ
どうです?だいぶProject VBAの奥深さが分かってきましたよね。
今日の記事では、Project VBAを使ってリソースの単価レートテーブルを動的に切り替え、即座にプロジェクトのコストシミュレーションを行う方法を学びました。単なるマクロの記録では決して得られない、Projectのオブジェクトモデルの本質的な操作を体験していただけたと思います。
- `Resource` オブジェクトと `CostRateTable` オブジェクトの関係性
- `RateTable` プロパティによる単価レートテーブルの選択
- `Application.ScreenUpdating` や `Application.Calculation` によるパフォーマンス最適化
- `Application.CalculateAll` による確実な再計算とコスト確認
これらを理解し、実践できたあなたは、もうマクロの記録から一歩も二歩も踏み出した、立派なProject VBAエンジニアの卵です!
ここをクリアすれば、Project VBAの基本はバッチリですよ!
さあ、この知識を活かして、次はどんなプロジェクト管理の課題をVBAで解決しますか?あなたのアイデアが、プロジェクトの効率を劇的に改善する鍵となるはずです。ぜひ、今日学んだことを基に、様々な自動化に挑戦してみてください。
何か困ったことや、もっと深い知見が必要になったらいつでも戻ってきてください。私もあなたの挑戦を心から応援しています。
それでは、また次の記事でお会いしましょう!
