5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

某小売業で教育を担当しているにゃろめです。

私は社内資格者育成のための研修運営を担当しています。2月の年度末が近づくと年間や半期を通じて実施してきた研修も終盤となり、最後の「認定試験」を迎えます。

そして「認定試験」が無事終わると、合否結果を店舗に連絡するお仕事が待っています。

合否発表の流れをざくっと言うと、こんな流れ。
合否を出す➡
Excelでまとめる➡
Excelで結果通知書を作成➡
PDF化➡メールに添付

➡店舗の店長へ送付

10人、20人くらいならそんな苦ではないのですが、
ぼんやり来年1月~2月のスケジュールを考えると、、、

あれ?
短期間に100名くらいの作らなあかんやん…( ゚Д゚)

これは何とかしなければ!!!

ということで、今年私が手に入れた武器「VBA」で自動化すべく、
相棒チャッピー(ChatGPT)に相談。
業務の合間にさくっとマクロを作成してみました🙌

以下のことをマクロで自動!!
・「合否結果」から自動判別、「認定」「未認定」シートの特定セルにNo.を自動入力
(自動入力されたらExcel関数でシートの中身が変わります)
全員分の通知書をPDFで保存
Excelシートに入力しているアドレス宛てのメールを自動作成+PDFも自動添付

💻作成開始!!!

先にこのようなExcelシートを準備(以前から使用していたものを少し加工)

image.png

チャッピーへのプロンプト

(相変わらず指示が下手だ…)

excel VBAを教えて下さい。以下が実施したいことです。

・「【】●●認定試験結果 通知書」というExcelファイルがあります。
・ファイル内には「結果」「認定」「未認定」のシートがあります。
「結果」シートは1行目が見出し、2行目以下データが入っています。
見出しの「合否結果」のデータを見て「認定」であれば「認定」シートのA13セルに、
見出し「No.」 列にある(A列)番号を入れてください。
「未認定」であれば「未認定」シートのA13セルに「No.」列にある番号を入力
・1行目が終わったら「認定」シート「未認定」シートどちらか(番号を入力した方)を保存

下手なプロンプトでもチャッピーはなんとか理解してくれました🥲

チャッピーが教えてくれたコード

Sub 結果を認定未認定シートへ転記して保存()

Dim wb As Workbook
Dim wsResult As Worksheet
Dim wsNintei As Worksheet
Dim wsMiNintei As Worksheet

Dim lastRow As Long
Dim i As Long
Dim colGohi As Long  '「合否結果」列
Dim colNo As Long    '「No.」列

Dim nextRowNintei As Long
Dim nextRowMiNintei As Long

Dim valGohi As String
Dim valNo As Variant

Dim haveNintei As Boolean
Dim haveMiNintei As Boolean

Dim basePath As String
Dim baseName As String

Set wb = ThisWorkbook
Set wsResult = wb.Worksheets("結果")
Set wsNintei = wb.Worksheets("認定")
Set wsMiNintei = wb.Worksheets("未認定")

'--- 見出し行(1行目)から「合否結果」「No.」の列番号を探す ---
colGohi = 0
colNo = 0

For i = 1 To wsResult.Cells(1, wsResult.Columns.Count).End(xlToLeft).Column
    Select Case wsResult.Cells(1, i).Value
        Case "合否結果"
            colGohi = i
        Case "No."
            colNo = i
    End Select
Next i

If colGohi = 0 Or colNo = 0 Then
    MsgBox "「結果」シートの1行目に「合否結果」または「No.」の見出しが見つかりません。", vbExclamation
    Exit Sub
End If

'--- 「結果」シートの最終行 ---
lastRow = wsResult.Cells(wsResult.Rows.Count, colNo).End(xlUp).Row
If lastRow < 2 Then
    MsgBox "「結果」シートにデータがありません。", vbExclamation
    Exit Sub
End If

'--- 「認定」「未認定」シートの書き込み開始行(A13から) ---
nextRowNintei = wsNintei.Cells(wsNintei.Rows.Count, "A").End(xlUp).Row
If nextRowNintei < 13 Then
    nextRowNintei = 13
Else
    nextRowNintei = nextRowNintei + 1
End If

nextRowMiNintei = wsMiNintei.Cells(wsMiNintei.Rows.Count, "A").End(xlUp).Row
If nextRowMiNintei < 13 Then
    nextRowMiNintei = 13
Else
    nextRowMiNintei = nextRowMiNintei + 1
End If

haveNintei = False
haveMiNintei = False

'--- 「結果」シートを2行目からループ ---
For i = 2 To lastRow
    valGohi = CStr(wsResult.Cells(i, colGohi).Value)
    valNo = wsResult.Cells(i, colNo).Value
    
    If valGohi = "認定" Then
        wsNintei.Cells(nextRowNintei, "A").Value = valNo
        nextRowNintei = nextRowNintei + 1
        haveNintei = True
    
    ElseIf valGohi = "未認定" Then
        wsMiNintei.Cells(nextRowMiNintei, "A").Value = valNo
        nextRowMiNintei = nextRowMiNintei + 1
        haveMiNintei = True
    End If
Next i

'--- 保存処理(シートを別ブックとして保存)---
basePath = wb.Path
If basePath = "" Then
    ' まだ保存していないブックの場合
    MsgBox "まず、このブックを一度保存してください。", vbExclamation
    Exit Sub
End If

baseName = Left(wb.Name, InStrRev(wb.Name, ".") - 1)

'「認定」シートに番号を入力した場合
If haveNintei Then
    wsNintei.Copy
    With ActiveWorkbook
        .SaveAs Filename:=basePath & "\" & baseName & "_認定.xlsx", _
                FileFormat:=xlOpenXMLWorkbook
        .Close SaveChanges:=False
    End With
End If

'「未認定」シートに番号を入力した場合
If haveMiNintei Then
    wsMiNintei.Copy
    With ActiveWorkbook
        .SaveAs Filename:=basePath & "\" & baseName & "_未認定.xlsx", _
                FileFormat:=xlOpenXMLWorkbook
        .Close SaveChanges:=False
    End With
End If

MsgBox "処理が完了しました。", vbInformation

End Sub

このあとに、以下の指示を追加

・Excelは上書き保存ではなく、PDFで1つ1つ保存
・PDFのシート名は【】に「店舗名+苗字+さん」を入れる
・PDF作成後、outlookを起動しPDFを自動添付
・その際、同じ店舗の受講生は1つのメールに添付するようにする
・メールアドレス①の番号を宛先に入力、メールアドレス②③はCCに入力
・メール作成するかどうか選択式(作成しない場合はPDF保存で終わる)

最終できたコード

Option Explicit

' メイン処理
Sub MakePdfAndMail()

Dim wsRes As Worksheet   ' 結果
Dim wsPass As Worksheet  ' 認定
Dim wsFail As Worksheet  ' 未認定
Dim lastRow As Long
Dim i As Long

Dim colNo As Long, colResult As Long, colShop As Long
Dim colName As Long, colMail1 As Long, colMail2 As Long, colMail3 As Long

Dim dict As Object                ' 店舗ごとの情報を格納する Dictionary
Dim info As Object                ' 店舗ごとの情報(さらに Dictionary)
Dim shop As String, result As String
Dim fullName As String, lastName As String
Dim toAddr As String, cc2 As String, cc3 As String
Dim targetWS As Worksheet
Dim pdfName As String, pdfPath As String
Dim dispName As String
Dim spacePos As Long
Dim ans As VbMsgBoxResult         ' メール起動するかどうかの確認用

On Error GoTo ErrHandler

Set wsRes = ThisWorkbook.Worksheets("結果")
Set wsPass = ThisWorkbook.Worksheets("認定")
Set wsFail = ThisWorkbook.Worksheets("未認定")

' 見出し行から列番号を取得
colNo = GetColumnByHeader(wsRes, "No.")
colResult = GetColumnByHeader(wsRes, "合否結果")
colShop = GetColumnByHeader(wsRes, "店舗")
colName = GetColumnByHeader(wsRes, "氏名")
colMail1 = GetColumnByHeader(wsRes, "メールアドレス①")
colMail2 = GetColumnByHeader(wsRes, "メールアドレス②")
colMail3 = GetColumnByHeader(wsRes, "メールアドレス③")

lastRow = wsRes.Cells(wsRes.Rows.Count, colNo).End(xlUp).Row

Set dict = CreateObject("Scripting.Dictionary")

For i = 2 To lastRow
    
    If wsRes.Cells(i, colNo).value <> "" Then
        
        result = Trim$(CStr(wsRes.Cells(i, colResult).value))
        shop = CStr(wsRes.Cells(i, colShop).value)
        fullName = CStr(wsRes.Cells(i, colName).value)
        
        ' 合否で使うシートを決定
        If result = "認定" Then
            Set targetWS = wsPass
        ElseIf result = "未認定" Then
            Set targetWS = wsFail
        Else
            ' それ以外はスキップ
            GoTo NextRow
        End If
        
        ' ---- No. を A13 にセット ----
        targetWS.Range("A13").value = wsRes.Cells(i, colNo).value
        
        ' ---- ファイル名の【 ~ 】部分を作成(苗字だけ)----
        lastName = fullName
        
        ' 半角スペースで区切りを探す
        spacePos = InStr(lastName, " ")
        If spacePos = 0 Then
            ' 見つからなければ全角スペースを探す
            spacePos = InStr(lastName, " ")
        End If
        
        If spacePos > 0 Then
            lastName = Left$(lastName, spacePos - 1)
        End If
        
        dispName = shop & " " & lastName & "さん"
        
        pdfName = "【" & dispName & "】●●認定試験結果 通知書.pdf"
        pdfPath = ThisWorkbook.Path & "\" & pdfName
        
        ' ---- PDF 出力 ----
        targetWS.ExportAsFixedFormat _
            Type:=xlTypePDF, _
            Filename:=pdfPath, _
            Quality:=xlQualityStandard, _
            IncludeDocProperties:=True, _
            IgnorePrintAreas:=False, _
            OpenAfterPublish:=False
        
        ' ==== 店舗ごとの情報を Dictionary に格納 ====
        If Not dict.Exists(shop) Then
            ' 店舗ごとの情報用 Dictionary を作成
            Set info = CreateObject("Scripting.Dictionary")
            info.Add "To", ""          ' To 用文字列
            info.Add "CC", ""          ' CC 用文字列(; 区切り)
            Set info("Files") = New Collection  ' 添付ファイル一覧
            dict.Add shop, info
        End If
        
        Set info = dict(shop)
        
        ' メールアドレス(数字でもOK)
        toAddr = Trim$(CStr(wsRes.Cells(i, colMail1).Text))
        cc2 = Trim$(CStr(wsRes.Cells(i, colMail2).Text))
        cc3 = Trim$(CStr(wsRes.Cells(i, colMail3).Text))
        
        ' To(同じアドレスが重複しないように)
        Call AddAddressUnique(info("To"), toAddr)
        ' CC(②③ をまとめて重複なしに)
        Call AddAddressUnique(info("CC"), cc2)
        Call AddAddressUnique(info("CC"), cc3)
        
        ' PDF ファイルパスを追加
        info("Files").Add pdfPath
        
    End If

NextRow:
Next i

' ===== メールを起動するかどうか確認 =====
ans = MsgBox("Outlook のメールを起動して、店舗ごとのメールを作成しますか?", _
             vbYesNo + vbQuestion, "メール作成の確認")

If ans = vbYes Then
    ' ===== Outlook メール作成 =====
    Call CreateOutlookMailByShop(dict)
End If

MsgBox "処理が完了しました。", vbInformation

Exit Sub

ErrHandler:
MsgBox "エラーが発生しました : " & Err.Number & " - " & Err.Description, vbCritical

End Sub

'------------------------------------------------------------
' 見出し行(1行目)から指定した文字の列番号を取得
'------------------------------------------------------------
Private Function GetColumnByHeader(ws As Worksheet, headerText As String) As Long
Dim rng As Range
Set rng = ws.Rows(1).Find(What:=headerText, LookAt:=xlWhole)

If rng Is Nothing Then
    Err.Raise vbObjectError + 100, , _
              "見出し """ & headerText & """ が見つかりません。"
End If

GetColumnByHeader = rng.Column

End Function

'------------------------------------------------------------
' 文字列で保持しているアドレスリストに、重複が無ければ追加
' strList : "a@a.com;b@b.com" のような文字列(ByRef)
' newAddr : 追加したいアドレス
'------------------------------------------------------------
Private Sub AddAddressUnique(ByRef strList As String, ByVal newAddr As String)
Dim tmp As String

If newAddr = "" Then Exit Sub

' 先頭・末尾にも ; を付けて「;xxx;」の形で検索し、重複を防ぐ
tmp = ";" & strList & ";"
If InStr(1, tmp, ";" & newAddr & ";", vbTextCompare) = 0 Then
    If strList = "" Then
        strList = newAddr
    Else
        strList = strList & ";" & newAddr
    End If
End If

End Sub

'------------------------------------------------------------
' 店舗ごと(dict)に Outlook メールを作成
' dict(shop)("To") : To アドレス文字列
' dict(shop)("CC") : CC アドレス文字列
' dict(shop)("Files"): 添付ファイルの Collection
'------------------------------------------------------------
Private Sub CreateOutlookMailByShop(ByVal dict As Object)

Dim olApp As Object
Dim olMail As Object
Dim shopKey As Variant
Dim info As Object
Dim f As Variant

' Outlook 起動(既に起動していればそのインスタンスを取得)
Set olApp = CreateObject("Outlook.Application")

For Each shopKey In dict.Keys
    
    Set info = dict(shopKey)
    
    Set olMail = olApp.CreateItem(0)  ' 0 = olMailItem
    
    With olMail
        .To = info("To")
        .cc = info("CC")
        .Subject = "●●認定試験結果 通知書"
        
        .Body = shopKey & " ご担当者様" & vbCrLf & vbCrLf & _
                "●●認定試験結果の通知書をお送りいたします。" & vbCrLf & _
                "添付ファイルをご確認ください。" & vbCrLf & vbCrLf & _
                "※本メールは自動送信です。"
        
        ' 添付ファイル追加
        For Each f In info("Files")
            .Attachments.Add CStr(f)
        Next f
        
        ' すぐ送信したい場合は .Send に変更
        .Display
    End With
    
Next shopKey

End Sub

VBAの中の文面を少し変えれば、担当している研修ごとに対応可能。
これで年明けの仕事が少し楽になるはず~

💬余談(PADで初めての業務改善Level.1)

本当はPowerAutomateDesktop(PAD)を使って何かしたくて、
本読んだり動画見たりしてみたけど
ちゃんと腰を据えてやらないと全然理解が追い付かない…

今回の取り組みもVBAとPADで組み合わせてやってみよう!と思ったが
結局VBAで全部完結できちゃうじゃん。
とのことでPAD断念。

うーん、何かPADでしたい!と思って、
便利な「録画機能」を使って
日常の些細な手間をフロー化してみた。

「社内システム接続の自動化フロー」

社内システムに繋げるには、事務所であれば会社のLAN線をPCに繋げるだけで完了。

しかし、私は出張がほとんどなので会社スマホでデザリングして以下の手順が必要。
1. 1つのアプリを立ち上げる
2. そこに入力するワンタイムパスワード作成のため、専用サイトに入りパス入力➡ログイン
3. 生成されたワンタイムパスワードをアプリに入力する。

時間は15秒ほどのものなのですが、地味に面倒なので1と2のフロー作成。

image.png

これでワンタイムパスワード作成までを自動化。
(約10秒)

手作業でするのと、PAD立ち上げて実行するのとそんな時間的に変わらない。
むしろ面倒な気もする…
(PC起動後自動的に立ち上げるようにしてるんだけど、立ち上がらない…)

でも!自分がPAD使っている嬉しさと、
その数秒何もせず違う作業ができるので、
有意義な自動化だと思っている!!(自己満)

来年も頑張って仕事を楽にしよ~

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?