MS Project VBAを極める:Assignmentオブジェクトを操り、リソース工数配分の自動化を実装せよ
開発プロジェクトの現場において、リソースの割り当てと工数配分(Work Breakdown Structure との紐付け)は、プロジェクトマネージャーの最も神経を使うコア業務の一つだ。
「誰にどのタスクをアサインし、何時間(何人工)投入させるか」
これをExcelや手動で管理しているうちは、プロジェクト規模が中規模を超えた瞬間に破綻する。MS Projectの標準機能やマニュアル通りの操作に頼るのもいいが、数百のタスクを持つスケジュールにおいて、スキルセットに応じた動的なアサインメントを人力で行うのはもはや罪悪と言っていい。
今回は、MS Project VBAの真髄である `Assignment` オブジェクト を完全に手なづけ、スキルセットに基づくリソースの自動選定から工数配分、そして多重アサイン時の破綻を防ぐ堅牢な自動化ロジックの構築法を伝授する。
—
1. なぜ「手動アサイン」や「安易なVBAコード」は破綻するのか?
多くのVBA初心者が陥る罠が、`Task.Resources.Add` メソッドをただ闇雲に叩くだけのコードだ。
【やってはいけないアンチパターン】
・タスクの既存アサインメントをクリアせずに重複アサインする
・Task.Work に対して Resource.Units を考慮せずに工数を直打ちし、スケジュールの整合性を破壊する
・Assignment オブジェクトのライフサイクルを無視し、存在しないインデックスやリソース名でエラーを吐かせる
MS Projectのオブジェクトモデルにおいて、タスク(Task)とリソース(Resource)の多対多の関係を繋ぐ実体が `Assignment`(ResourceAssignment)オブジェクト である。
タスクにリソースを割り当てるということは、「誰が・どのタスクに・どれだけの工数(Work)を・どの稼働率(Units)で割り振られているか」というステート(状態)をコードから厳密にコントロールすることを意味する。
ここを理解していないと、稼働時間がオーバーフローする「オーバーアロケーション(過負荷)」の検知すらできず、絵に描いた餅のスケジュールが生成されることになる。
—
2. 堅牢なアサインメント自動化のアーキテクチャ
今回構築する自動化エンジンの設計思想は以下の通りだ。
1. 前提データのクレンジング: 対象タスクの既存アサインメントを安全にクリアする。
2. スキルマッチング: タスクのテキストフィールド(例: `Text1` に「VBA」「Python」などの必要スキルを定義)を読み取り、リソースの持つスキル(例: `Notes` やカスタムフィールド)と照合する。
3. 工数・稼働率の適正配分: `Assignment.Work` および `Assignment.Units` に対し、プロジェクトのカレンダーや制約を考慮した数値を流し込む。
4. エラーハンドリング: リソースが存在しない、あるいは既にリタイアしている場合のフォールバック。
—
3. 【プロダクションコード】スキル自動判定&工数配分エンジン
以下のVBAコードは、実務の現場でそのまま組み込めるプロダクションクオリティのモジュールだ。
プロジェクト内の未アサインタスクに対し、タスクの「必要スキル」とリソースの「保有スキル」を突合し、最適なリソースを自動選定してアサインと工数配分を行う。
Option Explicit
‘ ==============================================================================
‘ 処理名 : AutoAssignResourcesBySkill
‘ 概要 : タスクのText1フィールドに設定された必要スキルを読み取り、
‘ 合致するスキルを持つリソースを自動選定してAssignmentを生成・工数配分する。
‘ ==============================================================================
Public Sub AutoAssignResourcesBySkill()
Dim tsk As Task
Dim res As Resource
Dim tgtAssignment as Assignment
Dim requiredSkill As String
Dim resourceSkill As String
Dim isAssigned As Boolean
‘ エラーハンドリングの有効化
On Error GoTo ErrorHandler
‘ アプリケーションの画面描画を停止し、処理速度を劇的に向上させる
App.ScreenUpdating = False
‘ プロジェクト内の全タスクを走査
For Each tsk ActiveProject.Tasks
‘ サマリータスク(親タスク)やマイルストーン、完了済みタスクは除外
If Not tsk Is Nothing Then
If Not tsk.Summary And tsk.Milestone = False And tsk.PercentComplete < 100 Then
' タスクのText1に定義された必要スキルを取得
requiredSkill = Trim(CStr(tsk.Text1))
If requiredSkill <> “” Then
‘ 既にアサインが存在する場合は一旦クリアする場合のロジック(必要に応じて制御)
‘ ここでは安全のため、未アサインのタスク、または特定の条件のタスクを対象とする
If tsk.ResourceNames = “” Then
isAssigned = False
‘ リソースプールから最適なスキルを持つリソースを探す
For Each res In ActiveProject.Resources
If Not res Is Nothing Then
‘ リソースのNotes(備考)欄をスキルの保持場所として判定(実運用ではCustomField推奨)
resourceSkill = Trim(CStr(res.Notes))
If InStr(1, resourceSkill, requiredSkill, vbTextCompare) > 0 Then
‘ ——————————————————————
‘ アサインメントの実行
‘ Task.Assignments.Add を使用することで、安全にAssignmentを生成
‘ ——————————————————————
Set tgtAssignment = tsk.Assignments.Add(TaskID:=tsk.ID, ResourceID:=res.ID)
‘ 稼働率を100%(1.0)に設定
tgtAssignment.Units = 1.0
‘ タスクの標準工数(Work)をアサインメント側にも明示的に同期させる
‘ ※ MS Projectの仕様上、Workの配分はタスクのDurationとUnitsに依存するが、
‘ 正確な工数を入れたい場合はここでAssignment.Workを制御する。
‘ tgtAssignment.Work = tsk.Work
isAssigned = True
‘ デバッグ出力
Debug.Print “アサイン成功: タスク[” & tsk.Name & “] -> リソース[” & res.Name & “] (スキル: ” & requiredSkill & “)”
‘ 最初に見つかったリソースをアサインしてループを抜ける(必要に応じて負荷平準化ロジックに拡張可能)
Exit For
End If
End If
Next res
If Not isAssigned Then
Debug.Print “警告: タスク[” & tsk.Name & “]に適合するスキル(” & requiredSkill & “)を持つリソースが見つかりませんでした。”
End If
End If
End If
End If
End If
Next tsk
App.ScreenUpdating = True
MsgBox “リソースの自動アサインと工数配分が完了しました。”, vbInformation, “処理完了”
Exit Sub
ErrorHandler:
App.ScreenUpdating = True
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“エラー内容: ” & Err.Description, vbCritical, “エラー”
End Sub
—
4. プロダクションコードの急所:アーキテクトの解説
上記のコードを実務で運用するにあたり、プロとして押さえておくべき「設計の急所」を解説する。
① `ScreenUpdating = False` によるパフォーマンス最適化
MS ProjectのVBAは、オブジェクトを操作するたびにガントチャートやリソースプールの再計算(スケジュールエンジンの走査)が走る。数千行規模のプロジェクトでこれを無防備に実行すると、処理が数十分単位でフリーズする。
必ず処理の冒頭で画面描画と再計算を抑制し、最後に一括して更新させよ。
② `Task.Assignments.Add` の優位性
リソースを割り当てる際、`Task.ResourceNames = “山田 太郎”` のように文字列で代入するコードを見かけるが、これは百害あって一利なしだ。同姓同名の問題、リソースIDのミスマッチ、スペルミスによるランタイムエラーの温床となる。
必ず `Task.Assignments.Add(TaskID, ResourceID)` を使用し、一意なIDベースでオブジェクトをバインドせよ。
③ アサインメントのライフサイクル管理と工数(Work)の整合性
タスクにリソースがアサインされた瞬間、MS Projectは自動的にタスクの `Work`(総労働時間)をカレンダーと稼働率(Units)から逆算する。
しかし、トップダウンで「このタスクには40時間の工数が必要だ」と決まっている場合は、`Assignment.Units` を設定した後に `Assignment.Work` を明示的に代立するか、タスク側の `Work` プロパティをコントロールする必要がある。
ここを怠ると、「アサインされたが工数が0時間になっている」「期間(Duration)が勝手に変動した」というバグに直面することになる。
—
5. データベースや外部ファイル連携への拡張アプローチ
この自動化スクリプトは、単にMS Project内で完結させるだけでなく、外部データベース(SQL ServerやOracle)や人事評価マスター(Excel/CSV)との連携によって真価を発揮する。
- 外部マスタ連携: 毎朝、人事DBから各社員の「最新の保有スキル」「稼働可能ステータス」をCSV経由でリソースプールのカスタムフィールド(`Resource.Text2`など)に自動同期させる。
- 動的アサインメント: 上記のVBAスクリプトをタスクのインポート処理の直後にフックさせ、完全ノーコード(全自動)でプロジェクトの初期スケジューリングを構築する。
プログラミングとは、人が行うべきではない退屈でエラーの起きやすい作業を、コードの厳密性と再現性によって駆逐する行為だ。
Assignmentオブジェクトを完全に掌握し、あなたのプロジェクトマネジメントを次のステージへと引き上げてもらいたい。
