【実務中級】MS Project VBAでリソース個別のカレンダー(例外日)を動的制御する極限の知見
プロジェクトマネジメントの現場において、リソースの稼働管理はスケジュール精度の命運を握る。汎用的なプロジェクトカレンダーとは別に、「特定のメンバーが急遽取得した有給休暇」や「突発的な出張による稼働除外日」を、何百人規模のリソースプールに対して手動でポチポチ設定していく……。そんな泥臭い作業を続けていないだろうか?
もしあなたが業務効率化ツールの開発者であれば、その手作業こそが「システムによる自動化」の最大のターゲットであると気づいているはずだ。
今回は、MS Project VBAにおける `Resource` オブジェクト、`Calendar` オブジェクト、そして例外日を司る `Exception` オブジェクトのライフサイクルと癖を徹底的に解説し、実務で絶対に破綻しない堅牢なカレンダー上書きロジックを伝授する。
—
1. なぜ「標準機能の手動設定」では実務で破綻するのか?
MS ProjectのGUI上でリソースの稼働日を変更する場合、[リソース情報]ダイアログを開き、[稼働時間]タブの変更を行う。しかし、これが数人ではなく「全社規模の300名」となるとどうなるか。
- ヒューマンエラーの温床: 日付の入力ミス、期間の設定ミスがスケジュール全体を狂わせる。
- メンテナンス性の欠如: 人事システムや勤怠管理システム(CSV/DB)からの変更差分が発生するたびに、手動では追従しきれなくなる。
- 「ベースカレンダー」と「リソース個別カレンダー」の混同: ここがProject VBAの最大の罠である。リソースのカレンダーをいじる際、ベースカレンダー自体を書き換えてしまうと、他のリソースのスケジュールまで破壊される。「特定のリソースだけが持つ固有のカレンダー(またはベースのオーバーライド)」を正確にターゲットにしなければならない。
プロフェッショナルなエンジニアであれば、外部データ(CSVやExcel)をソースとして、VBAからAPI経由でアトミックに例外日を流し込む仕組みを構築すべきだ。
—
2. Projectオブジェクトモデルの急所:ResourceとCalendarの構造
VBAからリソースの例外日を操作する際、以下のオブジェクト階層を正確に把握しておく必要がある。
Application
└── ActiveProject
└── Resources (Collection)
└── Resource (Object)
└── BaseCalendar (Property) / Calendar (Object)
└── Exceptions (Collection)
└── Exception (Object)
押さえておくべき技術的ポイント
1. リソースには固有の「ベースカレンダー」が割り当てられている:
通常、リソース(`Resource.BaseCalendar`)は「標準」などのベースカレンダーを継承している。
2. 個別の例外を追加するアプローチ:
リソース個別に例外(休暇など)を持たせる場合、そのリソースが参照しているカレンダーオブジェクト(またはリソース自体のカレンダーコンテキスト)に対して `Exceptions.Add` メソッドを実行する。
3. カレンダー名の衝突回避:
MS Projectでは、プロジェクト全体でカレンダー名が一意である必要がある。リソース固有のカレンダーをプログラムで動的生成・操作する場合、名前の重複によるランタイムエラー(Error 1100など)を完全にガードするロジックが不可欠である。
—
3. 【プロダクションコード】特定リソースの休暇を動的に上書きする堅牢なVBAプロシージャ
以下のコードは、外部のデータソース(今回はシミュレーションとしてハードコーディングまたは別シートを想定)から取得した「リソース名」と「休暇日(単日または期間)」をもとに、該当リソースのカレンダーに例外日を安全に追加する実務レベルのモジュールである。
エラーハンドリング、トランザクション的な視点、そして既存例外との重複チェック(または上書き)を考慮した実装になっている。
Option Explicit
‘ ==============================================================================
‘ モジュール名: ModResourceCalendarManager
‘ 概要 : 指定されたリソースのカレンダーに特定の休暇(例外日)を動的に追加する
‘ 著者 : 首席VBAアーキテクト
‘ ==============================================================================
Public Sub ApplyResourceExceptionExample()
Dim targetResourceName As String
targetResourceName = “開発 太郎” ‘ 対象となるリソース名
Dim exceptionStart As Date
Dim exceptionFinish As Date
exceptionStart = #10/15/2023# ‘ 休暇開始日
exceptionFinish = #10/17/2023# ‘ 休暇終了日(単日の場合は同日を指定)
Dim exceptionName As String
exceptionName = “特別夏季休暇(自動設定)”
‘ メイン処理の実行
Call UpdateResourceCalendarException(targetResourceName, exceptionStart, exceptionFinish, exceptionName)
End Sub
”’
”’
”’ リソース名 ”’ 開始日 ”’ 終了日 ”’ 例外の名称 Public Sub UpdateResourceCalendarException(ByVal resName As String, ByVal startDate As Date, ByVal finishDate As Date, ByVal excName As String)
‘ エラーハンドリングの有効化(MS ProjectのCOM例外をトラップ)
On Error GoTo ErrorHandler
Dim tgtResource As Resource
Set tgtResource = GetResourceByName(resName)
If tgtResource Is Nothing Then
MsgBox “指定されたリソースが見つかりません: ” & resName, vbCritical, “リソースエラー”
Exit Sub
End If
‘ リソースに紐付くカレンダーオブジェクトを取得
‘ 注意: Resourceオブジェクト自体は直接Exceptionsを持たないため、BaseCalendarプロパティを経由するか、
‘ プロジェクト全体のCalendarsコレクションからリソース名に紐づくものを特定する。
‘ ※MS Projectでは通常、リソース名と同名の個別カレンダーを持つか、ベースカレンダーを共有する。
‘ ここでは「リソース固有のベースカレンダー」を取得・操作するアプローチをとる。
Dim targetCal As Calendar
Dim calName As String
calName = tgtResource.BaseCalendar
‘ もしリソース専用のカレンダーが存在しない(標準を共有している等)場合、
‘ リソース名ベースのカスタムカレンダーを新規作成してアサインし直す設計が安全。
‘ 今回はシンプルに既存の割り当て先カレンダー(BaseCalendar)に対して例外を追加する実装とする。
‘ (※共有カレンダーの場合、他のメンバーにも影響するため実務では専用カレンダーの分離を推奨)
Set targetCal = ActiveProject.Calendars(calName)
‘ 既に同じ期間・名称の例外が存在するかチェックし、重複登録を防ぐ(冪等性の担保)
If ExistsException(targetCal, startDate, finishDate) Then
MsgBox “指定された期間の例外は既に登録されています。”, vbInformation, “スキップ”
Exit Sub
End If
‘ 例外日の追加
‘ 構文: Calendar.Exceptions.Add(Type, Start, Finish, Name, [Shift1Start], [Shift1Finish], …)
‘ pjExceptionNonWorking は非稼働日を指定する列挙体
targetCal.Exceptions.Add _
Type:=pjExceptionNonWorking, _
Start:=startDate, _
Finish:=finishDate, _
Name:=excName
MsgBox “[” & resName & “]” & ” のカレンダーに例外日を正常に設定しました。” & vbCrLf & _
“期間: ” & Format(startDate, “yyyy/mm/dd”) & ” ~ ” & Format(finishDate, “yyyy/mm/dd”), _
vbInformation, “処理成功”
Exit Sub
ErrorHandler:
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“詳細: ” & Err.Description, vbCritical, “致命的エラー”
End Sub
”’
”’
Private Function GetResourceByName(ByVal resName As String) As Resource
Dim r As Resource
For Each r In ActiveProject.Resources
If Not r Is Nothing Then
If r.Name = resName Then
Set GetResourceByName = r
Exit Function
End If
End If
Next r
Set GetResourceByName = Nothing
End Function
”’
”’
Private Function ExistsException(ByVal cal As Calendar, ByVal targetStart As Date, ByVal targetFinish As Date) As Boolean
Dim exc As Exception
Dim exists As Boolean
exists = False
On Error Resume Next
For Each exc In cal.Exceptions
If Not exc Is Nothing Then
‘ 日付の重複判定(簡易ロジック)
If exc.Start <= targetFinish And exc.Finish >= targetStart Then
exists = True
Exit For
End If
End If
Next exc
On Error GoTo 0
ExistsException = exists
End Function
—
4. チーフアーキテクトが教える「現場で絶対にハマる罠」と回避策
上記のコードを実務の現場(特に数千行のWBSを持つ大規模プロジェクト)に導入する際、以下の「現場の泥臭い罠」に直面することがある。プロとしてこれらをどう回避すべきかを伝授しよう。
罠1:共有ベースカレンダーを書き換えてしまうことの恐怖
先ほどのコードで `tgtResource.BaseCalendar` を取得したが、もしそのリソースが「標準」という名前のベースカレンダーを他の50人のメンバーと共有していた場合、そのカレンダーに例外を追加するとプロジェクト全体の「標準」カレンダーが汚染され、関係のないメンバーまで休暇扱いになってしまう。
【解決策】
実務において個別の休暇を反映させる場合、以下の設計アプローチをとるのが鉄則である。
1. リソース専用のベースカレンダー(例: `Res_開発太郎`)が存在するか確認する。
2. 存在しない場合は、標準カレンダーを元にして新規にベースカレンダーを作成する(`ActiveProject.Calendars.Add “Res_開発太郎”, “標準”`)。
3. 該当リソースの `BaseCalendar` プロパティをその専用カレンダーに書き換える。
4. その専用カレンダーに対して `Exceptions.Add` を実行する。
このひと手間を加えることで、他のリソースへの影響を完全に遮断し、クリーンなリソース管理が実現できる。
罠2:パフォーマンスの劣化(ループ処理の重み)
数百人分の休暇データをデータベースやExcelから一括読み込みし、毎回のループで `ActiveProject.Resources` や `Calendars` を全件走査(O(N^2)の計算量)すると、処理が数分間フリーズすることがある。
【解決策】
- 検索対象のリソースやカレンダーの参照を一度Dictionaryオブジェクト(`Scripting.Dictionary`)にキャッシュし、メモリ上でO(1)の高速ルックアップを実現する。
- 画面描画の更新を一時停止する(`Application.ScreenUpdating = False` ――もっとも、MS ProjectのVBAではExcelほど効果が顕著ではないが、イベントや不要な再計算を抑制する配慮は有効)。
—
5. おわりに
Project VBAは、正しく飼い慣らせば手作業による数時間の苦行を「0.5秒のワンクリック」に変える強力な武器となる。
今回解説したカレンダーと例外オブジェクトの制御は、単なるAPIのリファレンス書き写しではなく、「他のリソースへの波及を防ぐスコープ管理」と「二重登録を防ぐ冪等性」という実務に耐えうる堅牢な設計思想に基づいている。
あなたの開発するツールにこの知見を組み込み、プロジェクトマネージャーたちを「手動メンテ地獄」から解放してやってほしい。それこそが、真の業務自動化エンジニアの仕事である。
