中小底辺事務員が自動化を頑張る（給与明細書の電子化）

# いきさつ
中小企業というと、昭和から業務のやり方が変わってないというのも、そう珍しい事ではありません。
とてもめんどくさがり屋の私は「日々の単純作業をパソコンに任せて楽したい！」と思いつつ、

**技術も（ネットサービスやシステム導入にお金を使う）裁量もない！**

という現実がありました。
それでも、素人なりにちょっと頑張ってみたら少しは楽になりました、というお話です。

同じような境遇の方がもし見ていたら、参考になれば良いなという気持ちと、普段Qiitaを見ておられるようなエンジニアの方には「クソみたいな内容とコード書きやがって！こうしろ！もっと違うもん使え！」と、叱咤激励して頂ければと思っております。

# 背景とか
紙の給与明細書をドットインパクトプリンターで打ち出し、それを一枚一枚ちぎっては仕分けをして、従業員全員に配るというのはなかなか骨が折れます。どうにかしたいと思いつつ、どんな方法があるか考えてみました。

> オンラインサービスを使う。

1人月額いくらのサービスが多いですね、従業員がWebにアクセスするタイプでしょうか。皆さんの会社もきっとこれじゃないでしょうか。明細書だけでなく労務管理、給与計算や社会保険手続等と統合されたサービスもあり、とても便利だと思いますし今現在ベストチョイスだと思います。でも（裁量がないので）お金かかるのはダメです。

> 既存の給与計算ソフトに機能を追加してもらう。

給与計算ソフトを開発した会社に依頼すれば機能追加できそうですけど、お金かかります。

# 良い方法がないので作ってみる
よくあるオンラインサービスと同様のものを作ろうとすると。
> - Webサーバーとデータベースの構築
- Webの開発スキル
- 外部から接続するにはネットワーク、セキュリティの設定

考える事はこれくらいでしょうか、なかなか大変そうです。

ただ、給与明細書を見てもらう（送る）だけのシステムなら、メールで送ってしまっていいのでは？
と思い、それなら一人でも頑張れそうと作ってみました。

## 明細書（PDF）の作る
後出しの情報で申し訳ないのですが、給与のデータというのが社内のSQLサーバーにあります。そして使う道具はAccessです。
AccessはSQL言語の知識がなかったり、帳票ツールの使い方が難しくてわからないような私でも、簡単にクエリや帳票を作れたりして、非エンジニアの事務員になかなか良いソフトだと思ってます。けっこう致命的な仕様（バグ）があったりしますけど、それでも非エンジニアな方にはお勧めできるのではないでしょうか。

Accessのレポート作成から給与明細書を作成しておきます、
そのままPDF出力をすると単一のファイルになってしまいますが、従業員ごとにPDFファイルを分けたいので、一つ一つ出力します。

~~~visualbasic:「給与明細」フォルダの「社員コード」フォルダに「yyyymmdd(支給日)_社員コード.pdf」を作成
't_meisai_data → 給与のデータテーブル名
'r_meisai → 明細書のレポート名
'f_shain_code → 社員番号フィールド名
'f_sikyuubi → 支給日フィールド名
Const SAV_PATH = CurrentProject.Path & "\給与明細" '保存先を直下の給与明細フォルダへ
Const MEISAI_DATA = "t_meisai_data"
Const MEISAI_FORM = "r_meisai"

Dim RS As ADODB.Recordset
Set RS = New ADODB.Recordset
RS.Open MEISAI_DATA, CurrentProject.Connection, adOpenStatic, adLockReadOnly

Const PDF_DATE = Replace(RS!f_sikyuubi, "/", "")

Do While Not RS.EOF
  PER_PATH = SAV_PATH & "\" & RS!f_shain_code
  PDF_NAME = PDF_DATE & "_" & RS!f_shain_code

  If Dir(PER_PATH, vbDirectory) = "" Then
    MkDir PER_PATH
  End If
        
  DoCmd.OpenReport MEISAI_FORM, acViewPreview, , "f_shain_code =" & """" & RS!f_shain_code & """"
  DoCmd.OutputTo acOutputReport, MEISAI_FORM, acFormatPDF, PER_PATH & "\" & PDF_NAME & ".pdf"
  DoCmd.Close
		
  RS.MoveNext
Loop
~~~

## PDFにパスワードをかける
PDFを書庫に圧縮してパスワードを設定するのと、PDFにパスワードを設定するのと迷いましたが、PDFにパスワードを設定して送る方がメリットが多いと思い、そちらにしました。

パスワードを個別に設定するため、コマンドラインで使える[QPDF](http://qpdf.sourceforge.net/)を使ってみます。[こちら](http://pdf-file.nnn2.com/?p=865)のサイトを参考にさせて頂きました。なにやら難しいですが、こんな感じにしとけば良さそうです。

~~~shell:PDFにパスワードをかけて出力
qpdf --encrypt [ユーザーパス] [オーナーパス] 40 --print=y --modify=n --extract=y --annotate=n -- [入力元ファイル名] [出力先ファイル名]
~~~

出力し終わったすべてのPDFを指定するのは大変なので、PDFを1つ出力したらそのPDFに対してコマンドを実行するように、「RS.MoveNext」の上に挿入します。
従業員ごとにパスワードを設定するために、[社員番号][パスワード]の2つのフィールドを持ったテーブルを作っておきます。
（とりあえず問題なさそうなので、ユーザーパスとオーナーパスは同一にします。）
最初に出力したPDFはパスなしですが、以下でパスありに出力し、ファイル名の末尾になんとなく「p」を付けてます。

~~~visualbasic:「sendpdf」フォルダにパス付きのPDFを出力
't_passwords → [社員番号][パスワード]のフィールドのあるテーブル
'f_pass → パスワード
Dim PDF_PASS As Variant

PDF_PASS = DLookup("[f_pass]", "[t_passwords]", "[f_shain_code] =" & """" & RS!f_shain_code  & """")

cmd = "qpdf --encrypt " & PDF_PASS & " " & PDF_PASS & " 40 --print=y --modify=n --extract=y --annotate=n -- " & PER_PATH & "\" & PDF_NAME & ".pdf" & " " & SAV_PATH & "\sendpdf\" & PDF_NAME & "p.pdf"
    If Dir(SAV_PATH & "\sendpdf", vbDirectory) = "" Then
        MkDir SAV_PATH & "\sendpdf"
    End If

Set ex = sh.Exec("cmd.exe /c " & cmd)

If (ex.Status = WshFailed) Then
    Exit Sub
End If

Do While (ex.Status = WshRunning)
    DoEvents
Loop
~~~

## PDFをメールで送信する

~~~visualbasic:「sendpdf」のフォルダからPDFをメールで送信
't_mail → [職員コード][メールアドレス]のフィールドのあるテーブル
'f_mail →　メールアドレス
'f_name → 氏名
PDF_DATE = "YYYY/MM/DD" '本来は直打ちではなくて、フォーム等から取得する

Dim FILE_PATH As Variant

Dim RS As ADODB.Recordset
Set RS = New ADODB.Recordset
RS.Open "t_mail", CurrentProject.Connection, adOpenStatic, adLockReadOnly

Do While Not RS.EOF
	FILE_PATH = CurrentProject.Path & "\給与明細\sendpdf\" & Replace(PDF_DATE, "/", "") & "_" & RS!f_shain_code & "p.pdf"
	
	Set objMail = CreateObject("CDO.Message")
	objMail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
	objMail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = "smtp.mail.serv"
	objMail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 587
	objMail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/smtpusessl") = False
	objMail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate") = 1
	objMail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/sendusername") = "user@mail.serv"
	objMail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/sendpassword") = "password"
	objMail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/smtpconnectiontimeout") = 30
	objMail.From = "user@mail.serv <user@mail.serv>"
	objMail.Subject = "給与明細書（" & PDF_DATE & "支給分）"
	objMail.To = RS!f_mail
	objMail.TextBody = RS!f_name & "　様"
	objMail.AddAttachment FILE_PATH
	objMail.Configuration.Fields.Update
	objMail.Send
	
	Application.Wait Now() + TimeValue("00:00:02") '送信間隔が短いとメールサーバーから怒られるのでWait
	Set objMail = Nothing
	RS.MoveNext
Loop
~~~

## 指定の時間に実行する
ショートカットの起動引数を以下のようにすると、Accessのマクロを実行できます。

~~~
/x マクロ名
~~~
プロシージャの起動はできない？ので、プロシージャを実行して終了するだけのマクロを作成します。

あとは、Windowsのタスクスケジューラを実行したいスケジュールを設定して完了です。

# 終わりに
エラー処理やログ出力部分は省きました。
コードもほぼほぼ他所からの引用みたいなものですが、どこから引っ張ってきたのか忘れてしまいリンクは貼れませんでしたが、感謝しております。