和風スパゲティのレシピ

日本語でコーディングするExcelVBA

VBA課題006「注文書をデータ取込」回答コード

Excel・VBA総合コミュニティ「Excel-Fun.xls*」にて毎月出題される、
VBAの問題に対する私の回答ソースコードを掲載いたします。


今回掲載するコードは本ブログの基本問題集#2としても出題していた、
注文書をデータ取込」問題です。
VBA課題006「注文書をデータ取込」

今回も読みやすさ重視で書いてみましたので、
よろしければご参考ください(´∀`)

課題の内容

今回は注文書ファイルの内容をデータシートへ取り込むシンプルなお題です。
取込帳票
取込先データ


詳細は下記リンク内にある「課題説明」シートをご確認ください。


せっかくなので回答を見る前に皆さんも挑戦してみましょう。

一度自分が作ってから他の人のコードを見ることで、
学習効果が何倍にも高まります。

完成させなくても、出来る範囲で解くだけでも十分効果がありますので、
是非ともトライしてみてください!


◇ 「領収書PDFを出力」の課題ファイルはこちら
Discord:★出題【VBA】#006 注文書をデータ取込

◇ 「領収書PDFを出力」への回答集はこちら
Discord:☆回答【VBA】#006 注文書をデータ取込

※上記はDiscord参加済みの方用のURLです
 未参加の方は下記からご参加ください!
www.limecode.jp

課題の回答ファイル

実際の回答ファイルはこちらをご覧ください。
(和風スパ)【VBA】006_注文書をデータ取込.xlsm

ソースコード

モジュール全体像

標準モジュール:Df_シートレイアウト

Option Explicit

' 販売データ
Public Const Adrs販売データ_年度 = "F2"
Public Const R1st販売データ = 5
Public Const C1st販売データ = 2
Public Enum CNo販売データ
    No = C1st販売データ
    購入者
    月
    日
    品物
    価格
    個数
    売上
    取込日
End Enum
Public Const CLast販売データ = CNo販売データ.取込日

' 注文書
Public Const Adrs注文書_購入者 = "C5"
Public Const Adrs注文書_注文日 = "C7"
Public Const Adrs注文書_合計税別 = "F22"
Public Const Adrs注文書_消費税 = "F23"
Public Const Adrs注文書_合計税込 = "F24"
Public Const R1st注文書 = 12
Public Const RLast注文書 = 21
Public Const C1st注文書 = 2
Public Enum CNo注文書
    No = C1st注文書
    品物
    価格
    個数
    金額
End Enum
Public Const CLast注文書 = CNo注文書.金額

' 実行シート
Public Const Adrs実行シート_ファイルパス = "C4"
Public Const Adrs実行シート_フォルダパス = "C9"
Public Const R1st取込ログ = 12
Public Const C1st取込ログ = 2
Public Enum CNo取込ログ
    No = C1st取込ログ
    取込ファイル名
    結果
End Enum
Public Const CLast取込ログ = CNo取込ログ.結果


Function Is注文書シート(判定シート As Worksheet) As Boolean
    Is注文書シート = 判定シート.Range("B2") = "注  文  書" _
                 And 判定シート.Range("D24") = "合計(税込)"
End Function

標準モジュール:Pr0_注文書取込_共通コード

Option Explicit

' 注文書 → 販売データ
Sub 指定の注文書シートを販売データに取り込む(ws注文書 As Worksheet)

    ' 出力第1行(最終行+1)と出力No(最大値+1)を取得
    Dim R_出力 As Long: R_出力 = Get最終行(WS販売データ) + 1
    Dim 出力No As Long: 出力No = WorksheetFunction.Max(WS販売データ.Columns(CNo販売データ.No)) + 1
    
    ' 注文書の表部分をループ
    Dim R_取込 As Long
    For R_取込 = R1st注文書 To RLast注文書
    
        ' 金額が0でない行を取込
        If ws注文書.Cells(R_取込, CNo注文書.金額) <> 0 Then

            WS販売データ.Cells(R_出力, CNo販売データ.No) = 出力No
            WS販売データ.Cells(R_出力, CNo販売データ.購入者) = ws注文書.Range(Adrs注文書_購入者)
            WS販売データ.Cells(R_出力, CNo販売データ.) = Month(ws注文書.Range(Adrs注文書_注文日))
            WS販売データ.Cells(R_出力, CNo販売データ.) = Day(ws注文書.Range(Adrs注文書_注文日))
            WS販売データ.Cells(R_出力, CNo販売データ.品物) = ws注文書.Cells(R_取込, CNo注文書.品物)
            WS販売データ.Cells(R_出力, CNo販売データ.価格) = ws注文書.Cells(R_取込, CNo注文書.価格)
            WS販売データ.Cells(R_出力, CNo販売データ.個数) = ws注文書.Cells(R_取込, CNo注文書.個数)
            WS販売データ.Cells(R_出力, CNo販売データ.売上) = ws注文書.Cells(R_取込, CNo注文書.金額)
            WS販売データ.Cells(R_出力, CNo販売データ.取込日) = Date
            
            ' 出力行とNoを更新
            R_出力 = R_出力 + 1
            出力No = 出力No + 1
            
        End If
    
    Next ' 注文書の表部分をループ


End Sub

' 取込済判定(同日同購入者で判定)
Function Is指定注文書が販売データに取込済(ws注文書 As Worksheet) As Boolean
    
    ' そもそも年度が異なればExit
    If WS販売データ.Range(Adrs販売データ_年度) <> Get年度(ws注文書.Range(Adrs注文書_注文日)) Then Exit Function
    
    ' 判定キーの取得
    Dim key月 As Long: key月 = Month(ws注文書.Range(Adrs注文書_注文日))
    Dim key日 As Long: key日 = Day(ws注文書.Range(Adrs注文書_注文日))
    Dim key購入者 As String: key購入者 = ws注文書.Range(Adrs注文書_購入者)
    
    ' 全キーが一致するデータがあるか判定
    Dim R As Long
    For R = R1st販売データ To Get最終行(WS販売データ)
    
        If WS販売データ.Cells(R, CNo販売データ.) = key月 _
        And WS販売データ.Cells(R, CNo販売データ.) = key日 _
        And WS販売データ.Cells(R, CNo販売データ.購入者) = key購入者 Then
            Is指定注文書が販売データに取込済 = True
            Exit Function
        End If
    
    Next
    
End Function

標準モジュール:Pr1_注文書取込_ファイル指定

Option Explicit

' マクロ① 指定パスの注文書を取込
Sub 指定した注文書ファイルを販売データに取り込む()

    ' 取込ファイルの存在・ファイル形式チェック
    Dim path取込ファイル As String: path取込ファイル = WS実行シート.Range(Adrs実行シート_ファイルパス)
    Dim 取込ファイル名 As String: 取込ファイル名 = FSO.GetFileName(path取込ファイル)
    
    If 取込ファイル名 = "" Then MsgBox "指定したファイルが存在しません。": Exit Sub
    If Right(取込ファイル名, 5) <> ".xlsx" Then MsgBox "指定ファイルがxlsxファイルではありません。": Exit Sub
    
    ' 対象ブックを開く
    Dim path保存フォルダ As String: path保存フォルダ = FSO.GetParentFolderName(path取込ファイル)
    Dim wb取込ファイル As Workbook
    Set wb取込ファイル = ブックを開いていなければ開く(path保存フォルダ, 取込ファイル名)
    
    ' シートの存在チェック
    If Isシートが存在する("注文書", wb取込ファイル) = False Then MsgBox "注文書シートがないファイルです。": Exit Sub
    Dim ws注文書 As Worksheet: Set ws注文書 = wb取込ファイル.Worksheets("注文書")
    
    ' シートのレイアウトチェック
    If Is注文書シート(ws注文書) = False Then MsgBox "注文書シートのレイアウトが異なります。": Exit Sub

    ' 対象年度が異なれば取込を停止
    If WS販売データ.Range(Adrs販売データ_年度) <> Get年度(ws注文書.Range(Adrs注文書_注文日)) Then MsgBox "注文書の年度が異なります。": Exit Sub
        
    ' 取込済判定
    If Is指定注文書が販売データに取込済(ws注文書) Then MsgBox "同日同購入者のデータが既に存在します。": Exit Sub

    Dim R_第1出力行 As Long: R_第1出力行 = Get最終行(WS販売データ) + 1
        
    ' ■ 取込メインプロシージャを実行
    Call 指定の注文書シートを販売データに取り込む(ws注文書)
    
    Dim R_最終出力行 As Long: R_最終出力行 = Get最終行(WS販売データ)
            
    WS販売データ.Activate
    WS販売データ.Range(WS販売データ.Rows(R_第1出力行) _
                     , WS販売データ.Rows(R_最終出力行)).Select
    MsgBox "データの取込を完了しました。"
    
End Sub

標準モジュール:Pr2_注文書取込_フォルダ一括

Option Explicit

' マクロ② 指定フォルダ内の全注文書を取込
Sub 指定フォルダ内にあるすべての注文書ファイルを販売データに取り込む()

    ' 取込ログをクリア
    WS実行シート.Range(WS実行シート.Rows(R1st取込ログ), WS実行シート.Rows(Get最終行(WS実行シート))).Value = ""

    ' 取込フォルダの存在チェック
    Dim Path取込フォルダ As String: Path取込フォルダ = WS実行シート.Range(Adrs実行シート_フォルダパス)
    If FSO.FolderExists(Path取込フォルダ) = False Then MsgBox "指定したフォルダが存在しません。": Exit Sub
    
    ' 処理後の移動先フォルダを作成
    Dim Path完了フォルダ As String: Path完了フォルダ = Path取込フォルダ & "\" & Format(Date, "yyyymmdd") & "取込済"
    Dim Path除外フォルダ As String: Path除外フォルダ = Path取込フォルダ & "\除外ファイル"
    If FSO.FolderExists(Path完了フォルダ) = False Then FSO.CreateFolder Path完了フォルダ
    If FSO.FolderExists(Path除外フォルダ) = False Then FSO.CreateFolder Path除外フォルダ
    
    ' 第1出力列を記憶
    Dim R_第1出力行 As Long: R_第1出力行 = Get最終行(WS販売データ) + 1
    
    ' フォルダ内のすべてのxlsxファイルを取得
    Dim 取込ファイルNo As Long: 取込ファイルNo = 1
    Dim R_ログ As Long: R_ログ = R1st取込ログ
    Dim 取込ファイル As File
    For Each 取込ファイル In FSO.GetFolder(Path取込フォルダ).Files
    
        ' ファイル名とNoをログに記録
        WS実行シート.Cells(R_ログ, CNo取込ログ.No) = 取込ファイルNo
        WS実行シート.Cells(R_ログ, CNo取込ログ.取込ファイル名) = 取込ファイル.Name
        
        ' ファイルがxlsxでない
        If FSO.GetExtensionName(取込ファイル.Path) <> "xlsx" Then
            WS実行シート.Cells(R_ログ, CNo取込ログ.結果) = "xlsxでないファイル"
        Else
        
            ' 注文書シートの取得
            On Error Resume Next
                Dim wb取込ファイル As Workbook
                Set wb取込ファイル = ブックを開いていなければ開く(Path取込フォルダ, 取込ファイル.Name)
                Dim ws注文書 As Worksheet: Set ws注文書 = wb取込ファイル.Worksheets("注文書")
            On Error GoTo 0
            
            ' 注文書シートがない
            If ws注文書 Is Nothing Then
                WS実行シート.Cells(R_ログ, CNo取込ログ.結果) = "注文書シートなし"
            
            ' 注文書のレイアウトが不一致
            ElseIf Is注文書シート(ws注文書) = False Then
                WS実行シート.Cells(R_ログ, CNo取込ログ.結果) = "シートレイアウト不一致"
            
            ' 対象年度でない
            ElseIf WS販売データ.Range(Adrs販売データ_年度) <> Get年度(ws注文書.Range(Adrs注文書_注文日)) Then
                WS実行シート.Cells(R_ログ, CNo取込ログ.結果) = "データ年度不一致"
                
            ' 取込済
            ElseIf Is指定注文書が販売データに取込済(ws注文書) Then
                WS実行シート.Cells(R_ログ, CNo取込ログ.結果) = "取込済の注文書"
            
            ' ■ 取込を実行
            Else
                Call 指定の注文書シートを販売データに取り込む(ws注文書)
                WS実行シート.Cells(R_ログ, CNo取込ログ.結果) = "完了"
            End If
            
            ' ファイルを開いていたら閉じる
            If Not wb取込ファイル Is Nothing Then wb取込ファイル.Close False
            
        End If
    
        ' 結果が「完了」かどうかで判定
        If WS実行シート.Cells(R_ログ, CNo取込ログ.結果) = "完了" Then
            取込ファイル.Move Path完了フォルダ & "\"
        Else
            取込ファイル.Move Path除外フォルダ & "\"
        End If
    
        ' 次のファイルへ
        取込ファイルNo = 取込ファイルNo + 1
        R_ログ = R_ログ + 1
    Next ' フォルダ内のすべてのファイルを走査
    
    Dim R_最終出力行 As Long: R_最終出力行 = Get最終行(WS販売データ)
    
    If R_第1出力行 = R_最終出力行 Then
        MsgBox "取込可能なファイルがひとつもありませんでした。"
    Else
        WS販売データ.Activate
        WS販売データ.Range(WS販売データ.Rows(R_第1出力行) _
                         , WS販売データ.Rows(R_最終出力行)).Select
        MsgBox "データの取込を完了しました。"
    End If

End Sub

' フォルダのダイアログ選択
Sub フォルダを選択する()
    
    WS実行シート.Range(Adrs実行シート_フォルダパス) = GetPathダイアログボックスでフォルダを選択する(ThisWorkbook.Path)

End Sub

Function GetPathダイアログボックスでフォルダを選択する(Optional Path初期表示フォルダ As String = "") As String
    With Application.FileDialog(msoFileDialogFolderPicker)

        If Path初期表示フォルダ <> "" Then .InitialFileName = Path初期表示フォルダ

        If .Show Then: GetPathダイアログボックスでフォルダを選択する = .SelectedItems(1)
    
    End With
End Function

標準モジュール:Ut_汎用関数

Option Explicit

' FileSystemObjectのオブジェクト
Public FSO As New FileSystemObject

' 最終行の取得
Function Get最終行(指定シート As Worksheet, Optional 列番号 As Long = -1) As Long
    With 指定シート
    
        ' 列番号を省略時はシートの最終行
        If 列番号 = -1 Then
            Get最終行 = .UsedRange.Rows.Count + .UsedRange.Row - 1
    
        ' 列番号を指定時は列の最終行
        Else
            Get最終行 = .Cells(.Rows.Count, 列番号).End(xlUp).Row
        End If
    
    End With
End Function

' ブックを開いていなければ開く
Function ブックを開いていなければ開く(フォルダパス As String, ブック名 As String) As Workbook

    If Isブックを開いている(ブック名) Then
        Set ブックを開いていなければ開く = Workbooks(ブック名)
    Else
        On Error Resume Next
        Set ブックを開いていなければ開く = Workbooks.Open(フォルダパス & "\" & ブック名)
        On Error GoTo 0
    End If
    
End Function

' ブックのOpenチェック
Function Isブックを開いている(判定ブック名 As String) As Boolean

    ' 開いているすべてのブックを走査
    Dim wb As Workbook
    For Each wb In Workbooks
    
        ' ブック名が一致したらTrueを返してExit
        If wb.Name = 判定ブック名 Then
            Isブックを開いている = True
            Exit Function
        End If
        
    Next
    
End Function

' シートの存在チェック
Function Isシートが存在する(判定シート名 As String, 指定ブック As Workbook) As Boolean

    ' ブック内の全シートを走査
    Dim ws As Worksheet
    For Each ws In 指定ブック.Worksheets

        ' シート名が一致したらTrueを返してExit
        If ws.Name = 判定シート名 Then
            Isシートが存在する = True
            Exit Function
        End If

    Next

End Function

' 年度の取得
Function Get年度(ByVal 日付 As Date) As Long
    If Month(日付) >= 4 Then
        Get年度 = Year(日付)
    Else
        Get年度 = Year(日付) - 1
    End If
End Function

コードの解説

今回の課題は本ブログの基本問題集#2でも出題しており、
解答コードと解説ページも以下に公開しています。
VBA問題#2「注文書をデータ取込」マクロ①解答
VBA問題#2「注文書をデータ取込」マクロ②解答
VBA問題#2「注文書をデータ取込」完成版解答(挑戦ポイント制覇)

各コードの細かい解説は上記の記事をご参考ください。


今回の課題はシンプルでしたので、
「問題集の解答として用意した上記リンクにあるコード」と、
「実際に自分が作る想定で書いた本記事コード」で、
ロジック上は違いはありませんでした。


ただしコードの中身としては、
こちらはすべてFileSystemObjectを使用したコードになっています。

以下の完成版解答のコードと比べていただき、Dirシリーズで書いた場合と、
FileSystemObjectを使って書いた場合のコードの違いを参考にしてみて下さい。

www.limecode.jp


それ以外のロジックの解説や挑戦ポイントへの対応方法も、
上記の完成版解答で詳しく解説しています。

よろしければご参考ください。