【入門編】制約条件の変更履歴をVBAでログ出力し、スケジュール変動の責任所在を明確化する – Project VBA解析バイブル

スポンサーリンク

こんにちは。Project VBAの深淵へようこそ。

Microsoft Projectというツールは、単なる「ガントチャート作成ソフト」ではありません。その実体は、数万のタスクが複雑に絡み合う巨大な計算エンジンです。

特に「制約条件(ASAP: できるだけ早く、Must Finish On: 必終、など)」の設定は、スケジュールという名のドミノを倒す最初の1枚になります。誰かが良かれと思って「必終」を設定した瞬間、プロジェクト全体のバッファが消滅する……そんな現場を私は何度も見てきました。

「誰が、いつ、なぜ変えたのか?」

これをブラックボックスにしないことが、PMO(プロジェクトマネジメントオフィス)やリードエンジニアがProject VBAを学ぶ真の目的です。今回は、マクロの記録を卒業し、「イベントハンドリング」というプロの領域へ一歩踏み出してみましょう。

—

1. なぜ「制約条件」のログが重要なのか?

Microsoft Projectの計算エンジンは、以下の優先順位で動いています。
1. カレンダー(稼働時間)
2. 依存関係(リンク)
3. 制約条件(ASAP/ALAP/MSOなど)

ここで恐ろしいのは、制約条件は依存関係よりも優先されることが多いという点です。
例えば、先行タスクが遅れているのに、後続タスクに「指定日に開始(SNET)」がついていると、スケジュールは「不自然に固定」され、真のリスクが見えなくなります。

この「犯人捜し」ではなく「原因究明」のために、VBAで自動的に足跡を残す仕組みを構築しましょう。

—

2. 実装のキモ:TaskChangeイベントを使いこなす

一般的なExcel VBAでは「ボタンを押して実行」するマクロが多いですが、ログ出力においては「ユーザーが値を書き換えた瞬間に動く」イベント駆動型のコードが必要です。

今回使用するのは、`Project_BeforeTaskChange` というイベントです。

準備:カスタムフィールドをログ置き場にする

まず、Project側で「テキスト1(Text1)」などのカスタムフィールドを「変更履歴」という名前に変えておきましょう。ここに「2023/10/27: ASAP -> MSO (By User)」といった文字列を流し込みます。

—

3. 極限の知見を込めたログ出力コード

このコードは、`ThisProject` モジュールに記述します。標準モジュールではない点に注意してください。

‘
‘ Project VBA: Constraint Change Auditor
‘ 役割: 制約条件の変更を検知し、カスタムフィールドに変更履歴を刻む
‘
Option Explicit

‘ タスクの変更前に実行されるイベント
Private Sub Project_BeforeTaskChange(ByVal tsk As Task, ByVal Field As PjField, ByVal NewVal As Variant, Cancel As Boolean)

‘ 監視対象が「制約条件の種類 (pjTaskConstraintType)」であるか判定
If Field = pjTaskConstraintType Then

‘ 現在の(変更前の)値を取得
Dim oldConstraint As String
oldConstraint = GetConstraintName(tsk.ConstraintType)

‘ 新しく設定されようとしている値を取得
Dim newConstraint As String
newConstraint = GetConstraintName(CInt(NewVal))

‘ 値が実際に異なる場合のみログを記録(パフォーマンス対策)
If oldConstraint <> newConstraint Then
Call WriteAuditLog(tsk, oldConstraint, newConstraint)
End If

End If
End Sub

‘ 制約条件の列挙型を分かりやすい文字列に変換するヘルパー関数
Private Function GetConstraintName(ByVal cType As Long) As String
Select Case cType
Case pjAsap: GetConstraintName = “ASAP (できるだけ早く)”
Case pjAlap: GetConstraintName = “ALAP (できるだけ遅く)”
Case pjFnet: GetConstraintName = “FNET (指定日以降に終了)”
Case pjFnlt: GetConstraintName = “FNLT (指定日以前に終了)”
Case pjMfo: GetConstraintName = “MSO (必終)”
Case pjMso: GetConstraintName = “MSO (必始)”
Case pjSnet: GetConstraintName = “SNET (指定日以降に開始)”
Case pjSnlt: GetConstraintName = “SNLT (指定日以前に開始)”
Case Else: GetConstraintName = “Unknown”
End Select
End Function

‘ カスタムフィールドに変更履歴を追記する
Private Sub WriteAuditLog(ByRef tsk As Task, ByVal oldVal As String, ByVal newVal As String)
Dim logEntry As String
Dim currentLog As String

‘ ログの書式: [日時] ユーザー名: 旧 -> 新
logEntry = “[” & Format(Now, “yyyy/mm/dd HH:mm”) & “] ” & _
Environ(“USERNAME”) & “: ” & oldVal & ” -> ” & newVal

‘ Text1(テキスト1)フィールドをログ格納先として使用
‘ 既存のログがある場合は、改行して先頭に追加(最新が上)
currentLog = tsk.Text1
If currentLog <> “” Then
tsk.Text1 = logEntry & vbCrLf & currentLog
Else
tsk.Text1 = logEntry
End If

‘ ヒント: ここで Debug.Print を使うとイミディエイトウィンドウでも確認できます
Debug.Print “Task: ” & tsk.Name & ” | ” & logEntry
End Sub

—

4. コードの解説と「初心者がハマるポイント」

① `BeforeTaskChange` の魔力

`TaskChange`(変更後)ではなく `BeforeTaskChange`(変更前)を使うのがプロの技です。なぜなら、変更前であれば 「変わる前の値」と「これから変わる値」の両方にアクセスできるから です。これにより、「何から何へ変わったのか」という正確な対比が可能になります。

② `Field` 引数の正体

Projectには数百のフィールドがあります。このイベントは、名前を変えても、期間を変えても発生します。
`If Field = pjTaskConstraintType` という一文を入れないと、何か操作するたびにマクロが走り、動作が非常に重くなってしまいます。「必要な時だけ動く」。これが大規模プロジェクトを扱う際の鉄則です。

③ カスタムフィールドの限界

今回は `Text1` フィールドに書き込んでいますが、Projectのテキストフィールドには文字数制限(255文字)があります。変更が多すぎる場合は、古いログが切れてしまう可能性があります。
真に堅牢なシステムにするなら、外部のテキストファイルやSQLiteなどのデータベースへ書き出すように拡張するのがチーフアーキテクト流です。

—

5. スケジュールを掌握するあなたへ

このマクロを導入すると、チームメンバーは「あ、制約条件を変えたら履歴が残るんだ」と意識するようになります。実は、これが最大の効果です。

ツールによる自動化は、単に作業を楽にするだけではありません。「データの整合性に対する規律」を生むためにあるのです。

VBAの基本をマスターすれば、Projectはただのガントチャートから、プロジェクトの「航海日誌」へと進化します。今回紹介したイベントハンドリングを入り口に、オブジェクトブラウザを覗き、Projectの内部構造をさらに探求してみてください。

「ここをクリアすれば、Project VBAの基本はバッチリですよ。次は、リソースの過負荷を検知するエンジンの作成に挑戦してみましょうか。」

あなたのエンジニアリングが、プロジェクトを成功へ導くことを願っています。

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