【テクニカル・上級編】リソースの「ベースカレンダー」をVBAで動的に生成・適用する自動化スクリプト – Project VBA解析バイブル

スポンサーリンク

幾何学的なカレンダー汚染からの脱却:Project VBAによる動的ベースカレンダー生成とリソース調停の極意

シニアアーキテクト諸君。日々のプロジェクトマネジメントにおいて、MS Projectの「カレンダー仕様の不統一」に頭を悩ませていないか。

グローバル展開するプロジェクト、あるいは協力会社が入り乱れる現場では、国籍、契約形態、ローカルの祝祭日、さらには特別稼働日といった「変異する時間軸」が無数に存在する。これをGUIで一つひとつ手動ポチポチと設定するなど、エンジニアとしての尊厳を捨てるに等しい。標準ベースカレンダー(「標準」「夜勤」「24時間」)の複製とカスタマイズをGUIで行うアプローチは、大規模プロジェクトにおいて必ず破綻する。オブジェクトの不整合、グローバルカレンダーの汚染、そして何より、メンテナンス性の完全な喪失だ。

我々はコードで支配する。今回は、Project VBAのオブジェクトモデルの深層に潜り込み、「動的にベースカレンダーを生成し、例外日(非稼働日・特別稼働日)を注入した上で、対象リソース群へミリ秒単位のトランザクションで一括適用する」ための極限のアーキテクチャをここに開示する。

—

1. Projectカレンダーオブジェクトの暗部:ライフサイクルとメモリの罠

まず、Projectにおける `Calendar` オブジェクトの挙動を理解しなければならない。
MS Projectの裏側では、すべてのカレンダーは `Project.Calendars` コレクションによって管理されている。ここで注意すべきは、ベースカレンダー(Base Calendar)とリソース個別のタスクカレンダー(Resource Calendar)の境界線だ。

VBAからカレンダーを生成する際、以下の致命的な罠が存在する。
1. グローバル汚染: `ActiveProject.Calendars.Add` を軽率に実行すると、プロジェクトファイル内に永続的なカレンダーが生成される。処理失敗時のロールバック機構を実装しないと、ゴミオブジェクトがプロジェクトファイルを肥大化させる。
2. メモリリークの温床: `Calendar` オブジェクトや `Exception` コレクションを操作する際、変数の解放を怠ると、COMコンテキストが参照を保持し続け、VBAの実行終了後もメモリが解放されない。
3. 例外日の排他制御: カレンダーの例外日(Exceptions)コレクションは、日付の順序がソートされていない状態で追加すると、内部のC++コアエンジンがパニックを起こし、予期せぬスケジュール計算のズレ(負の工数バグ)を引き起こす。

これらを完全に制御するためには、「トランザクション的な生成」「明示的なオブジェクト参照の切断」「日付の昇順ソート済み例外日のバルクインジェクション」の3原則を遵守する必要がある。

—

2. 実装アーキテクチャ:動的ベースカレンダー生成エンジンの全貌

以下のコードは、外部データ(JSONやDB、あるいはExcel定義シート)から取得した勤務体系データを想定し、VBA上で完全に独立したベースカレンダーを動的に構築し、指定されたリソース群へアタッチする実用スクリプトだ。

レガシーなVBAの限界を超えるため、エラーハンドリング、オブジェクトの即時解放、およびパフォーマンスを意識した構造化を行っている。

Option Explicit

‘ ==============================================================================
‘ 処理名: 動的ベースカレンダー生成・リソース一括適用エンジン
‘ 著作権: チーフアーキテクト室
‘ 概要: 指定された例外日を持つベースカレンダーを動的に生成し、対象リソース群へ割り当てる
‘ ==============================================================================
Public Sub DeployDynamicBaseCalendar()
Dim prj As Project
Set prj = ActiveProject

Dim targetCalendarName As String
targetCalendarName = “【自動生成】プロジェクト特化カレンダー_202X”

‘ 1. 既存の同名ベースカレンダーが存在する場合は安全に削除(冪等性の担保)
Call PurgeExistingCalendar(prj, targetCalendarName)

‘ 2. 新規ベースカレンダーの生成
Dim baseCal As Calendar
On Error GoTo ErrorHandler
Set baseCal = prj.Calendars.Add(targetCalendarName)

‘ 3. 基本週休日の設定(例: 土日を非稼働、月~金を稼働に強制設定)
Call ConfigureBaseWeekDays(baseCal)

‘ 4. 例外日(祝日・特別稼働日)のバルクインジェクション
Call InjectExceptions(baseCal)

‘ 5. 対象リソース群への一括アサイン(リソースプールまたはアクティブプロジェクト)
Call AssignCalendarToResources(prj, baseCal.Name)

MsgBox “ベースカレンダーの動的生成およびリソースへの適用が正常に完了しました。”, vbInformation, “アーキテクチャ・インスペクター”
GoTo CleanUp

ErrorHandler:
MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical, “Execution Halt”
‘ 異常終了時のロールバック処理
If Not baseCal Is Nothing Then
On Error Resume Next
baseCal.Delete
End If

CleanUp:
‘ オブジェクトの明示的解放によるメモリ最適化
Set baseCal = Nothing
Set prj = Nothing
End Sub

‘ ——————————————————————————
‘ 既存カレンダーの安全な削除(冪等性の確保)
‘ ——————————————————————————
Private Sub PurgeExistingCalendar(ByRef prj As Project, ByVal calName As String)
Dim cal As Calendar
For Each cal In prj.Calendars
‘ ベースカレンダーであり、かつ名前が一致する場合のみ削除
If Not cal.BaseCalendar = False And cal.Name = calName Then
‘ リソースにアタッチされている場合はデフォルト(標準)に戻すなどのハンドリングが必要
Dim r As Resource
For Each r In prj.Resources
If Not r Is Nothing Then
If r.Calendar.Name = calName Then
Set r.Calendar = prj.Calendars(“標準”)
End If
End If
Next r
cal.Delete
Exit For
End If
Next cal
Set cal = Nothing
End Sub

‘ ——————————————————————————
‘ 基本週休日の設定
‘ ——————————————————————————
Private Sub ConfigureBaseWeekDays(ByRef cal As Calendar)
Dim wDay As WeekDay
Dim i As Integer

‘ Projectの曜日インデックス: 1=日曜日, 2=月曜日, …, 7=土曜日
For i = 1 To 7
Set wDay = cal.WeekDays(i)
Select Case i
Case 1, 7 ‘ 日曜・土曜は非稼働日
wDay.Working = False
Case Else ‘ 月~金は標準稼働時間設定 (08:00 – 17:00, 休憩 12:00 – 13:00)
wDay.Working = True
With wDay.Shift1
.Start = “08:00”
.Finish = “12:00”
End With
With wDay.Shift2
.Start = “13:00”
.Finish = “17:00″
End With
End Select
Next i
Set wDay = Nothing
End Sub

‘ ——————————————————————————
‘ 例外日の注入(実運用では外部DBやExcelレンジから動的に配列を取得することを推奨)
‘ ——————————————————————————
Private Sub InjectExceptions(ByRef cal As Calendar)
Dim excs As Exceptions
Set excs = cal.Exceptions

‘ 【重要】例外日を追加する際は、時系列順(昇順)であることがC++コアの安定動作に不可欠
‘ ここではハードコードだが、実際はSortedなデータ構造から流し込むこと

‘ 例外1: 年末年始休暇の一括定義
excs.Add Type:=pjExceptionTypeByDate, Start:=”202X/12/29″, Finish:=”202X/1/3″

‘ 例外2: 特別稼働日(土曜日だが稼働日に変更するケース)
Dim specialWorkDay As Exception
Set specialWorkDay = excs.Add(Type:=pjExceptionTypeByDate, Start:=”202X/10/15″, Finish:=”202X/10/15”)
specialWorkDay.Working = True
With specialWorkDay.Shift1
.Start = “09:00”
.Finish = “13:00”
End With

Set specialWorkDay = Nothing
Set excs = Nothing
End Sub

‘ ——————————————————————————
‘ リソースへのカレンダー一括アサイン
‘ ——————————————————————————
Private Sub AssignCalendarToResources(ByRef prj As Project, ByVal calName As String)
Dim r As Resource
Dim targetCal As Calendar
Set targetCal = prj.Calendars(calName)

‘ 画面描画を停止し、COM通信のオーバーヘッドを極限まで削減(パフォーマンス最適化)
Application.ScreenUpdating = False

For Each r In prj.Resources
If Not r Is Nothing Then
‘ 特定のグループや条件に合致するリソースのみに適用する場合のフィルタリング
‘ 例: リソースのテキストフィールド1に “DynamicTarget” と入っている場合のみ
If r.Text1 = “DynamicTarget” Then
Set r.Calendar = targetCal
End If
End If
Next r

Application.ScreenUpdating = True
Set targetCal = Nothing
Set r = Nothing
End Sub

—

3. チーフアーキテクトからの実践的警鐘:パフォーマンスとレガシー保守

上記のコードを実装・運用するにあたり、現場のエンジニアが直面するであろう「限界点」と「その回避策」を記す。

① 画面描画の排除 (`ScreenUpdating = False`) の絶対性

MS ProjectのVBAは、オブジェクトプロパティ(特に `Resource.Calendar` や `Task.Start` など)を書き換えるたびに、GUI側のガントチャートやリソースシートの再描画を裏で試みようとする。これが数千件規模のリソース・タスクが存在するプロジェクトで発生すると、O(N)の計算量が数倍に膨れ上がり、処理が数分間フリーズする。
前述のコードの通り、バルク処理の前には必ず `Application.ScreenUpdating = False` を挟み、処理終了後に明示的に `True` に戻すこと。これはパフォーマンスチューニングの基本中の基本だ。

② COMインターフェースのメモリ管理の闇

VBAはガベージコレクションを持たない。`Set r = Nothing` や `Set baseCal = Nothing` を記述していないコードは、VBAランタイムがスコープを抜けるまでCOMポインタを握り続ける。特にループ内でオブジェクトを変数に格納し続ける構造を書くと、メモリリークを起こし、最悪の場合MS Projectそのものがクラッシュする。
オブジェクト変数は使い終わったら即座に `Nothing` を代入して解放する。これがシニアの流儀である。

③ システム間連携(外部API / DB連携)への拡張

このスクリプトはスタンドアロンで動くだけではない。前半の `InjectExceptions` やリソースの絞り込み条件(`r.Text1`)部分を、REST API経由で取得したJSONデータ(企業カレンダーAPIなど)、あるいはActive Directoryの勤務情報と非同期でバインドするように拡張せよ。

社内システム管理者が手動でカレンダーをメンテナンスする時代は終わった。
Project VBAを極限まで使い倒し、スケジュール管理のインフラストラクチャをコードによって完全自動化することこそが、真のモダン・エンジニアリングである。

実装に妥協するな。コードでプロジェクトを支配せよ。

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