マクロ的思考のすすめ。

VBA作業用のstaticサイト。

前回までのあらすじ


前回は、請求書出力自動化の案件をなんとかもぎ取り、僕はその仕事を受けることになった。テンプレートファイルの作成と環境整備までが終わったところである。
今回は、おおまかなスケジュールの目安と作業の方向性を確認する。前準備として、本ページに予定やこれまでの作業を記録する。

このサイト普通に公開しちゃっていいのか??そんなのは知らん。というか後で消します。

とりあえずスケジュール案


全9回中のこり6回

割と適当。適宜修正したまえ。僕の性質上締め切りには結構ギリギリになりがちなので、早めを意識している。
また、チェックとかテストとかはよく分からんけど書いてみただけ。

プログラムの方向性


大体のフローを箇条書きで書いていくよ。適宜修正。

前提として とりあえずtemplateはローカルに置いておく。TEMPLATEディレクトリに入れるのは分かりやすくするため。
ディレクトリ内のファイルが名前で並ぶならlistもディレクトリに入れた方が良いかもしれない。
使いにくそうなら変更するが、考えたディレクトリ構造は以下の通り。
|
|
|-- OUTPUT
| |--PDF
| | |--aaa請求書.pdf
| | |--bbb請求書.pdf
| | |--ccc請求書.pdf
| | |--ddd請求書.pdf
| |
| |--EXCEL
| | |--aaa請求書.xlsx
| | |--bbb請求書.xlsx
| | |--ccc請求書.xlsx
| | |--ddd請求書.xlsx
|
|-- TEMPLATE
| |--template.xlsx
|
|-- program.xlsm(出力自動化ツール.xlsm)
|-- list1.xlsx
|-- list2.xlsx
|-- list3.xlsx

以下フロー。出来るだけ細かく書いてみる。

1. ファイルを用意する。構成は上の通り。
2. program.xlsxのボタンを押す。
3. list1.xlsxを読み込む。
4. template.xlsxを複製する。
5. list1.xlsxの内容を複製したファイルに書き込んで保存。
6. excelからpdfを出力し、OUTPUT/PDFに保存。
  1. 保存してから書き込むか、書き込んでから保存するか。

変数の命名規則とか


使う変数とか決めとく

  1. 読み込む行と列、書き込む行と列は変数にしておく。
  2. list中の位置は^[a-zA-Z]*ColFrom(int型)で、書き込む先は^[a-zA-Z]*CellTo(string型)とする。^[a-zA-Z]*ColFromは数字で指定するが、^[a-zA-Z]*CellToはstringで"B2"のように指定する。
  3. string型でセルを指定しようとしたけどCellsが使えなかったからRangeを使った。よく分からん。
  4. program.xlsm中のstatusなどの一覧はlistRow,listColで書き込む位置を指定。
  5. list.xlsx内の読み込みはtargetRowと各コンテンツの行を表す変数dateColFromなどで指定。
  6. 8行あるcontentsの読み込み元の行はcontent[2][8]の二次元配列でとっておいた。
  7. 出力先のファイル名はoutputNameという変数を使う。これに拡張子を足す。outputNameは今のところ$(会社名)+"請求書"でやってる。
  8. 変数名はいずれもキャメルケースとする。(一部の一時変数を除く)

イメージ的には


なんとなくのイメージ図を載せておく。

本番ファイルがどんな感じだったかほとんど覚えていなかったため、僕の曖昧な記憶にあったなんとなくの構成で表を作る。

本番はもっと複雑な感じがあったので、まぁざっくりとしたイメージとしての図と思え。

fig1
メイン画面。ボタンがある。
fig2
ファイル選択画面。複数選択可能。
ここでTEMPLATEがないとエラーになる。
fig3
何も選択しないとエラーを出して終わり。
こういう感じのリストから読み込む
date company name content1-1 content2-1 content3-1 content1-2 content2-2 content3-2 statement
2022/11/11 company1 name1 たわし メロン 合計 250 2000 10,000 輝く夜へようこそ!
2024/7/19 company2 name2 すいか 蛇口 合計 150 2400 10,000 それでも地球は回っている。
2028/4/24 company3 name3 とけい ストロー 合計 1050 1300 300,000 お支払いはキャッシュで。
fig4
読み込みと書き込みが終わるとリストが表示されてメッセージが出現。
fig5
OUTPUTディレクトリが作成されている。
fig6
OUTPUTディレクトリの中はEXCELとPDFディレクトリが作成されている。
fig7
EXCELディレクトリの中は一時的なエクセルファイルが保存されている。一応。
fig8
PDFディレクトリの中はPDFファイルが保存されている。これが最終的な結果になるはず。
fig9
PDFファイルを開いてみた。ゴミみたいなファイルが生成されてるね!!
また、dateがおかしくなってることにも注目!

一回コード書いてみたよ


とりあえず書いてみた奴だけ。

Function selectfiles() As Variantファイルを選択させる関数
	Dim flist As Variant
	flist = Application.GetOpenFilename(filefilter:="Excel Files, *.xlsx;*.xls;*.xlsm", MultiSelect:=True)
	If IsArray(flist) Then
		selectfiles = flist 選択されたファイルを配列で返す
	Else
		MsgBox "Error: Nothing Selected"なにも選択されなかったらエラー
		End
	End If
End Function
Sub main() メイン関数
    Dim fileList As Variant
    Dim sheetName As String
    Dim listRow As Integer, listCol As Integer
    Dim contentColFrom(2, 8) As Integer
    Dim targetRow As Integer
    Dim FSO As Object
    
    Dim companyCellTo As String
    Dim dateCellTo As String
    Dim nameCellTo As String
    Dim priceCellTo As String
    Dim contentCellTo(2, 8) As String
    Dim statementCellTo As String
    
    sheetName = "Sheet1"
    listCol = 6
    listRow = 1
    dateColFrom = 1
    companyColFrom = 2
    nameColFrom = 3
    
	contentColFrom(1, 1) = 4 リストファイル中の項目の位置を指定
    contentColFrom(1, 2) = 5
    contentColFrom(1, 3) = 6
    contentColFrom(1, 4) = 7
    contentColFrom(1, 5) = 8
    contentColFrom(1, 6) = 9
    contentColFrom(1, 7) = 10
    contentColFrom(1, 8) = 11
    contentColFrom(2, 1) = 12
    contentColFrom(2, 2) = 13
    contentColFrom(2, 3) = 14
    contentColFrom(2, 4) = 15
    contentColFrom(2, 5) = 16
    contentColFrom(2, 6) = 17
    contentColFrom(2, 7) = 18
    contentColFrom(2, 8) = 19
    
    statementColFrom = 20
    
    companyCellTo = "B4"書き込む先の位置を指定
    dateCellTo = "G2"
    nameCellTo = "C7"
    priceCellTo = "C8"
    statementCellTo = "B22"
    
    contentCellTo(1, 1) = "B12"
    contentCellTo(1, 2) = "B13"
    contentCellTo(1, 3) = "B14"
    contentCellTo(1, 4) = "B15"
    contentCellTo(1, 5) = "B16"
    contentCellTo(1, 6) = "B17"
    contentCellTo(1, 7) = "B18"
    contentCellTo(1, 8) = "B19"
    contentCellTo(2, 1) = "F12"
    contentCellTo(2, 2) = "F13"
    contentCellTo(2, 3) = "F14"
    contentCellTo(2, 4) = "F15"
    contentCellTo(2, 5) = "F16"
    contentCellTo(2, 6) = "F17"
    contentCellTo(2, 7) = "F18"
    contentCellTo(2, 8) = "F19"
    
    
    Set FSO = CreateObject("Scripting.FileSystemObject")
    
    fileList = selectfiles()各フォルダが存在していなければ作成
    If Not FSO.folderexists("OUTPUT") Then
        MkDir ("OUTPUT")
    End If
    If Not FSO.folderexists("OUTPUT/EXCEL") Then
        MkDir ("OUTPUT/EXCEL")
    End If
    If Not FSO.folderexists("OUTPUT/PDF") Then
        MkDir ("OUTPUT/PDF")
    End If
    If Not FSO.FileExists("TEMPLATE/template.xlsx") Thentemplateが存在していなければエラー終了
        MsgBox "template.xlsx doesn't exist."
        End
    End If
    
    
    While Cells(listRow, listCol) <> ""program.xlsx中の一覧の書き込み開始位置を決める
        listRow = listRow + 1
    Wend
    
    For Each filePath In fileList選択されたファイルそれぞれに対して実行
        Dim fn As String
        Dim wbFrom As Workbook
        Dim wbTo As Workbook
        Dim outputName As String
        fn = Dir(filePath)
        Set wbFrom = Workbooks.Open(filePath)
        Set wsFrom = wbFrom.Worksheets(sheetName)
        
        targetRow = 2
        While wsFrom.Cells(targetRow, 1).Value <> ""
            outputName = wsFrom.Cells(targetRow, companyColFrom) & "請求書"生成ファイルの名前を決定
            FileCopy "TEMPLATE/template.xlsx", "OUTPUT/EXCEL/" & outputName & ".xlsx"テンプレートを複製してOUTPUTに保存
            
            Set wbTo = Workbooks.Open("OUTPUT/EXCEL/" & outputName & ".xlsx")書き込み先を指定
            
            ThisWorkbook.Worksheets(1).Cells(listRow, listCol - 1) = wsFrom.Cells(targetRow, companyColFrom)program.xlsx内の一覧に書き込む
            ThisWorkbook.Worksheets(1).Cells(listRow, listCol - 2) = "listed"
            ThisWorkbook.Worksheets(1).Cells(listRow, listCol) = fn
            ThisWorkbook.Worksheets(1).Cells(listRow, listCol + 1) = outputName
            
            wbTo.Worksheets(1).Range(dateCellTo) = wsFrom.Cells(targetRow, dateColFrom)書き込む
            wbTo.Worksheets(1).Range(companyCellTo) = wsFrom.Cells(targetRow, companyColFrom)
            wbTo.Worksheets(1).Range(nameCellTo) = wsFrom.Cells(targetRow, nameColFrom)
            wbTo.Worksheets(1).Range(priceCellTo) = wsFrom.Cells(targetRow, contentColFrom(2, 8))
            wbTo.Worksheets(1).Range(statementCellTo) = wsFrom.Cells(targetRow, statementColFrom)
            wbTo.Worksheets(1).Range(contentCellTo(1, 1)) = wsFrom.Cells(targetRow, contentColFrom(1, 1))
            wbTo.Worksheets(1).Range(contentCellTo(1, 2)) = wsFrom.Cells(targetRow, contentColFrom(1, 2))
            wbTo.Worksheets(1).Range(contentCellTo(1, 3)) = wsFrom.Cells(targetRow, contentColFrom(1, 3))
            wbTo.Worksheets(1).Range(contentCellTo(1, 4)) = wsFrom.Cells(targetRow, contentColFrom(1, 4))
            wbTo.Worksheets(1).Range(contentCellTo(1, 5)) = wsFrom.Cells(targetRow, contentColFrom(1, 5))
            wbTo.Worksheets(1).Range(contentCellTo(1, 6)) = wsFrom.Cells(targetRow, contentColFrom(1, 6))
            wbTo.Worksheets(1).Range(contentCellTo(1, 7)) = wsFrom.Cells(targetRow, contentColFrom(1, 7))
            wbTo.Worksheets(1).Range(contentCellTo(1, 8)) = wsFrom.Cells(targetRow, contentColFrom(1, 8))
            wbTo.Worksheets(1).Range(contentCellTo(2, 1)) = wsFrom.Cells(targetRow, contentColFrom(2, 1))
            wbTo.Worksheets(1).Range(contentCellTo(2, 2)) = wsFrom.Cells(targetRow, contentColFrom(2, 2))
            wbTo.Worksheets(1).Range(contentCellTo(2, 3)) = wsFrom.Cells(targetRow, contentColFrom(2, 3))
            wbTo.Worksheets(1).Range(contentCellTo(2, 4)) = wsFrom.Cells(targetRow, contentColFrom(2, 4))
            wbTo.Worksheets(1).Range(contentCellTo(2, 5)) = wsFrom.Cells(targetRow, contentColFrom(2, 5))
            wbTo.Worksheets(1).Range(contentCellTo(2, 6)) = wsFrom.Cells(targetRow, contentColFrom(2, 6))
            wbTo.Worksheets(1).Range(contentCellTo(2, 7)) = wsFrom.Cells(targetRow, contentColFrom(2, 7))
            wbTo.Worksheets(1).Range(contentCellTo(2, 8)) = wsFrom.Cells(targetRow, contentColFrom(2, 8))
            
            wbTo.ExportAsFixedFormat Type:=xlTypePDF, Filename:="OUTPUT/PDF/" & outputName & ".pdf"PDFで保存する
            
            
            targetRow = targetRow + 1
            ThisWorkbook.Worksheets(1).Cells(listRow, listCol) = fn
            Call wbTo.Close(savechanges:=True)
            ThisWorkbook.Worksheets(1).Cells(listRow, listCol - 2) = "done"
            listRow = listRow + 1
        Wend
        Call wbFrom.Close(savechanges:=False)
    Next
    
    Set wsFrom = Nothing
    Set wbFrom = Nothing
    MsgBox "ALL DONE. HAVE A GOOD DAY!!"
End Sub
			

参考文献

VBAについて調べたときのサイトをメモ程度に書いておくよ


考えるべきこと


次回の予定


特に考えてない。なんか思いついたら書いとく。

続きです。9/7

】の後に改行を入れる。変数などの定義は適宜調整。

Dim insertIndex As Integer
Dim result As String
Dim inputString As String
insertIndex = InStr(wsFrom.Cells(targetRow, nameColFrom), "】")
inputString = wsFrom.Cells(targetRow, nameColFrom)
result = Left(inputString, insertIndex) & vbLf & Right(inputString, Len(inputString) - insertIndex)
wbTo.Worksheets(1).Range(nameCellTo) = result
↑のコードは、】の後に改行を入れるコード。】がない場合はエラーになるので、エラー処理を入れる。入れなくてもいい
改行はvbLfを使う。他にもvbCr、vbCrLfがある。vbLfは改行、vbCrはキャリッジリターン、vbCrLfは改行とキャリッジリターンの組み合わせ。