Option Explicit
Option Base 1
'-----------------------------------------------
' プロシージャ名 : exec工数算出
' 機能概要 : ガントシートを走査し、工数データを営業日数込みで収集する
' 留意点 : 担当者が未入力の行や日付不整合はスキップ/終了する
'-----------------------------------------------
Public Sub exec工数算出()
Dim wsガント As Worksheet
Dim lngRowEnd As Long
Dim lngRow As Long
Dim lng工程Idx As Long
Dim lngCol担当 As Long
Dim lng開始日列 As Long, lng終了日列 As Long
Dim lng工数列 As Long
Dim cls工数データ As cls工数データ
Dim clt工程一覧 As Collection
Dim dic休業日 As Scripting.Dictionary
Dim typ列情報 As typガント列定義
Dim dat処理開始 As Date, dat処理終了 As Date
Dim dic集計レコード As Scripting.Dictionary
On Error GoTo ERR_HANDLER
dat処理開始 = Now
' 必須シートの存在確認
If Not fncBlnシート存在確認() Then Exit Sub
' シート取得
Set wsガント = ThisWorkbook.Worksheets(STR_WSNAME_ガント)
' 列定義初期化
typ列情報 = fncガント列定義取得()
' 工数の数値形式チェック(数値以外があれば終了)
If Not fncBlnCheck工数値(wsガント, typ列情報) Then Exit Sub
Set clt工程一覧 = New Collection
Set dic休業日 = fnc休業日Dictionary取得()
With wsガント
lngRowEnd = .Cells(.Rows.Count, typ列情報.ツール名).End(xlUp).Row
For lng工程Idx = C_LNG_工程START To C_INT_工程END
' 列名を列番号に変換
Call prc列番号変換(typ列情報, lng工程Idx, lng工数列, lngCol担当, lng開始日列, lng終了日列)
' ガントチャートの開始行 → 終了行までループ
For lngRow = LNG_ROWSTART_WSガント To lngRowEnd
' 担当者が未入力の場合は次の行へ
If .Cells(lngRow, lngCol担当).Value = "" Then GoTo SkipRow
' 開始日・終了日が正常取得できない場合はエラー終了
If Not fncBln日付妥当性チェック( _
.Cells(lngRow, lng開始日列).Value, .Cells(lngRow, lng終了日列).Value, _
wsガント, lngRow, _
lng開始日列, lng終了日列, _
dic休業日) Then
Exit Sub
End If
' 対象行のセル情報から工数データクラスを生成
Set cls工数データ = fncCls工数データ生成(wsガント, lngRow, lngCol担当, lng工数列, lng開始日列, lng終了日列)
cls工数データ.工数セルアドレス = wsガント.Cells(lngRow, lng工数列).Address(False, False)
' ツール名を格納
cls工数データ.ツール名 = .Cells(lngRow, fncColumnLetterToNumber(typ列情報.ツール名)).Value
' 休業日ディクショナリを工数データへ連携
Set cls工数データ.dic休業日 = dic休業日
' 営業日数計算 → 工数データに格納
cls工数データ.日数 = cls工数データ.fncLng営業日数取得()
' 工数が未入力なら営業日数で補完
Call cls工数データ.prc工数自動補完
' 1営業日あたりの平均工数を算出してプロパティに格納
cls工数データ.平均工数1営業日 = cls工数データ.fncDbl平均工数1営業日()
' 工数データをコレクションに追加
clt工程一覧.Add cls工数データ
'確認出力
Debug.Print cls工数データ.fncToDebugString()
SkipRow:
Next lngRow
Next lng工程Idx
End With
' 営業日ゼロチェック(工数あり) → 処理を中断
If Not fncBln営業日ゼロ異常確認(clt工程一覧) Then Exit Sub
' 実工数異常チェック(異常があれば処理終了)
If Not fncBln1日平均工数確認(clt工程一覧) Then Exit Sub
' 集計レコード一覧を生成(Dictionary)
Set dic集計レコード = create集計レコード一覧(clt工程一覧)
' 工数明細シートに出力
Call output工数明細(dic集計レコード)
' 集計シートに出力
Call output集計表(dic集計レコード)
dat処理終了 = Now
Debug.Print Format(dat処理終了, "hh:nn:ss") & ":終了(処理時間: " & Format(dat処理終了 - dat処理開始, "hh:nn:ss") & ")"
Exit Sub
ERR_HANDLER:
MsgBox "処理中にエラーが発生しました。" & vbCrLf & _
"内容: " & Err.Description, vbCritical
Set cls工数データ = Nothing
Set dic休業日 = Nothing
Set clt工程一覧 = Nothing
Set wsガント = Nothing
Exit Sub
End Sub
'--------------------------------------------------
' プロシージャ名 : output集計表
' 概要 : cls集計レコードのDictionaryから「集計」シートへ氏名×日付の工数を集計出力
' 引数 : dic集計レコード - Dictionary(cls集計レコードを格納)
'--------------------------------------------------
Public Sub output集計表(ByVal dic集計レコード As Scripting.Dictionary)
Const COL_氏名 As Long = 1 ' 氏名列の列番号(A列=1)
Const ROW_氏名開始 As Long = 2 ' 氏名一覧の開始行(データ出力対象)
Const COL_日付開始 As Long = 2 ' 日付が始まる列番号(B列=2)
Const ROW_日付ヘッダー As Long = 1 ' 日付ヘッダーが記載されている行番号
Dim ws出力 As Worksheet
Dim dic氏名行 As Scripting.Dictionary
Dim dic対象氏名 As Scripting.Dictionary
Dim arr日付 As Variant
Dim arr出力() As Double
Dim varKey As Variant
Dim cls明細 As cls集計レコード
Dim lng行数 As Long, lng列数 As Long
Dim lng氏名行Idx As Long, lng日付列Idx As Long
Dim str氏名 As String
Dim dtm対象日 As Date
Dim arr氏名 As Variant
Dim rng氏名範囲 As Range
' 出力対象のシートを参照
Set ws出力 = ThisWorkbook.Worksheets(STR_WSNAME_集計)
' ヘッダーから日付を取得(1行目 B列~右端)
lng列数 = ws出力.Cells(ROW_日付ヘッダー, ws出力.Columns.Count).End(xlToLeft).Column - (COL_日付開始 - 1)
Const LNG_DATE_IDX_START As Long = 1
ReDim arr日付(LNG_DATE_IDX_START To lng列数)
Dim lng日付Idx As Long
For lng日付Idx = 1 To lng列数
arr日付(lng日付Idx) = ws出力.Cells(ROW_日付ヘッダー, lng日付Idx + (COL_日付開始 - 1)).Value
Next lng日付Idx
'--------------------------------------------------
' 対象となる氏名一覧を取得(cls集計レコードに登場する氏名のみを対象)
' → シートのA列にある全氏名のうち、出力すべき対象を限定するための前処理
'--------------------------------------------------
Set dic対象氏名 = New Scripting.Dictionary
' cls集計レコードの中から登場する氏名のみを対象として、辞書に登録する
' ※同一氏名が複数レコードに登場する可能性があるため、既存チェックを行う
For Each varKey In dic集計レコード.Keys
Set cls明細 = dic集計レコード(varKey)
' 対象氏名が未登録であれば、辞書に追加(重複回避)
If Not dic対象氏名.Exists(cls明細.str氏名) Then
dic対象氏名.Add cls明細.str氏名, True
End If
Next varKey
' シートのA列から氏名リストを取得(下端まで)
Set dic氏名行 = New Scripting.Dictionary
' 氏名列の有効範囲(最終行)を取得
Dim lng最終行_氏名 As Long
lng最終行_氏名 = ws出力.Cells(ws出力.Rows.Count, COL_氏名).End(xlUp).Row
' 氏名列の範囲オブジェクトを作成(開始行 ~ 最終行)
Set rng氏名範囲 = ws出力.Range(ws出力.Cells(ROW_氏名開始, COL_氏名), ws出力.Cells(lng最終行_氏名, COL_氏名))
arr氏名 = rng氏名範囲.Value
' 配列形式で取得できなかった場合(1セルのみ)、2次元配列に変換して扱えるようにする
If Not IsArray(arr氏名) Then
ReDim arr氏名(1 To 1, 1 To 1)
arr氏名(1, 1) = rng氏名範囲.Value
End If
' 集計対象の氏名のみを辞書に格納(行番号と対応)
lng行数 = UBound(arr氏名, 1)
Dim lng氏名Idx As Long
For lng氏名Idx = 1 To lng行数
str氏名 = Trim(arr氏名(lng氏名Idx, 1))
' 空白行はスキップ
If str氏名 = "" Then GoTo SkipRow
' 対象氏名のみに限定(集計レコードに含まれていない行は除外)
If Not dic対象氏名.Exists(str氏名) Then GoTo SkipRow
' 有効な氏名として辞書に登録
dic氏名行(str氏名) = lng氏名Idx
SkipRow:
Next lng氏名Idx
' 出力用2次元配列を初期化(氏名行数×日付列数)
ReDim arr出力(1 To lng行数, 1 To lng列数)
' 集計データの反映処理(氏名×日付ごとに工数を加算)
For Each varKey In dic集計レコード.Keys
Set cls明細 = dic集計レコード(varKey)
str氏名 = cls明細.str氏名
dtm対象日 = cls明細.dtm対象日
' 対象の氏名が存在する行を特定(行番号を取得)
If dic氏名行.Exists(str氏名) Then
lng氏名行Idx = dic氏名行(str氏名)
' 日付列を順に走査し、対象日と一致する列を見つけて工数を加算
' ※一致が見つかれば Exit For でループ終了(効率化)
For lng日付列Idx = 1 To lng列数
If arr日付(lng日付列Idx) = dtm対象日 Then
' 対象日の位置が一致した場合は、工数を加算してループ終了
arr出力(lng氏名行Idx, lng日付列Idx) = arr出力(lng氏名行Idx, lng日付列Idx) + cls明細.dbl工数
Exit For
End If
Next lng日付列Idx
End If
Next varKey
' シートに2次元配列で一括出力
ws出力.Range(ws出力.Cells(ROW_氏名開始, COL_日付開始), _
ws出力.Cells(ROW_氏名開始 + lng行数 - 1, COL_日付開始 + lng列数 - 1)).Value = arr出力
End Sub
コメント