ルモーリン
ホーム更新サービス雑談ランドナーコースガイド鉄ゲタ自転車Linuxリンク連絡先

クレジットの決済結果をエクセルに反映

背景

毎月出てくるクレジットの決済結果をエクセルの家計簿にコピーするのが手作業なので面倒くさくなってきた。 せっかくだからPerlでアップデータを作り自動化しよう。

概要

  1. カード会社はライフカード
  2. 決済結果をサイトから手動ダウンロード(自動ログイン/ダウンロードはクラックを疑われる可能性あり)
  3. ファイル形式は、なんちゃってCSV(行毎にカラム数が異なる)
  4. 家計簿はエクセルで、科目設定シート、1~12月の各シート、集計シート、他
  5. 決済結果と家計簿をデスクトップに置く
  6. アップデータはGUI(Tkx)を使う
  7. エクセル操作はOLEを使う

できた

こんな感じです。 なんちゃってCSVファイルを読み込むと、決済件数と総額がメッセージで報告されます。 それから家計簿に追加すると決済日の月のシートの末尾に追加します。 クレジット会社のサイトから決済結果をオンラインで入手できるAPIとか公開されないかなあ。 そのうち手動ダウンロードが面倒くさくなる(笑)。

コード

#!/usr/bin/env perl -w

use utf8;
use strict;
use warnings;
use open IO => ":utf8";

use Data::Dumper;
use DateTime;
use Encode::Argv;
use Encode::Locale;
use Spreadsheet::WriteExcel;
use Tkx;
use Win32::OLE;
use Win32::OLE::Const;
use Win32::OLE "CP_UTF8";

binmode STDIN, ":encoding(console_in)";
binmode STDOUT, ":encoding(console_out)";

$| = 1;
$Win32::OLE::CP = CP_UTF8;

# リストボックス内の文字を等幅にするため一律でフォントを指定
Tkx::option_add("*Listbox.font", "System");

my $userprofile = $ENV{USERPROFILE} =~ s#\\#/#gr;
my $desktop_dir = "$userprofile/Desktop/";

my $now_dt = DateTime->now(time_zone => "local");
my $default_csv = $now_dt->strftime("lifecard_meisai_%Y%m.csv");

my $mw = Tkx::widget->new(".");
menu_build($mw, [
	[ "ファイル", "F", [
		[ "決済データを読み込む", "O", \&file_open, ],
		[ "家計簿に追加", "A", \&file_append, ],
		[ "終了", "X", \&wm_delete_window, ],
	], ],
]);

$mw->g_wm_title("クレジット決済アップデータ");
$mw->g_wm_minsize(300, 0);
$mw->g_wm_protocol(WM_DELETE_WINDOW => \&wm_delete_window);
$mw->g_wm_resizable(0, 0);

my @meisai;

Tkx::MainLoop();

exit;

sub my_msg($$) {
	my ($msg, $parent) = @_;
	$parent = $parent // $mw;

	Tkx::tk___messageBox(
		-parent => $parent,
		-title => "メッセージ",
		-type => "ok",
		-icon => "info",
		-message => "$msg",
	);
}

sub file_open {
	my $csv_file = Tkx::tk___getOpenFile(
		-defaultextension => "*.csv",
		-filetypes => [
			["CSVファイル", [".csv",]],
			["すべてのファイル", [".*",]],
		],
		-initialdir => $desktop_dir,
		-initialfile => $default_csv,
		-title => "クレジット決済CSVファイルを選択してください",
	);
	if ($csv_file) {
		load_csv($csv_file);
	}
}

sub load_csv {
	my ($csv_file) = @_;
	if (open my $csv_fh, "<:encoding(cp932)", $csv_file) {
		my $meisai_in = 0;
		my @line;
		my @header;
		while (<$csv_fh>) {
			chomp;
			if (!$meisai_in && /^明細No\.,/) {
				$meisai_in = 1;
				@header = split /,/;
			} elsif ($meisai_in && /^$/) {
				undef $meisai_in;
				last;
			} elsif ($meisai_in) {
				push @line, $_;
			}
		}
		close $csv_fh;

		my %field_idx = (
			date => "利用日",
			shop => "利用先",
			money => "利用金額",
		);
		for (my $column = 0; $column < @header; $column++) {
			for (keys %field_idx) {
				if ($header[$column] eq $field_idx{$_}) {
					$field_idx{$_} = $column;
				}
			}
		}

		undef @meisai;
		for (@line) {
			my @field = split /,/;
			push @meisai, {
				date => $field[$field_idx{date}],
				shop => $field[$field_idx{shop}],
				money => $field[$field_idx{money}],
			};
		}

		@meisai = sort {$a->{date} cmp $b->{date}} @meisai;

		my $total = 0;
		$total += $_->{money} for @meisai;
		my_msg "@{[scalar @meisai]}件、${total}円", $mw;
	}
}

sub file_append {
	my $excel = Win32::OLE->new("Excel.Application", "Quit");
	my $wb = $excel->Workbooks->Open(Encode::encode locale => "${desktop_dir}家計簿.xlsx");

	my @month_name = qw(1 2 3 4 5 6 7 8 9 10 11 12);
	for (@meisai) {
		$_->{date} =~ m#/(\d\d)/#;
		my $ws = $wb->Worksheets($month_name[$1 - 1] . "月");

		my $row;
		for ($row = 5; $row <= 104; $row++) {
			my $v = $ws->Cells($row, 3)->{Value};
			last if !defined $v;
		}

		if ($row <= 104) {
			$ws->Cells($row, 3)->{Value} = $_->{date};
			$ws->Cells($row, 4)->{Value} = $_->{shop};
			$ws->Cells($row, 5)->{Value} = $_->{money};
		}
	}

	$wb->Save();
	$wb->Close();
	$excel->Quit();
}

sub wm_delete_window {
	$mw->g_destroy;
}

sub menu_build {
	my ($mainwindow, $tree) = @_;
	my $top = $mainwindow->new_menu;

	for (@$tree) {
		my $second = $top->new_menu( -tearoff => 0, );
		$top->add_cascade(
			-label => "${$_}[0](${$_}[1])",
			-underline => 1 + length ${$_}[0],
			-menu => $second,
		);
		for (@{${$_}[2]}) {
			my $label = ${$_}[0];
			my $label_after = "";
			my $underline = 1 + length ${$_}[0];
			if ($label =~ /\.\.\.$/) {
				$label =~ s/\.\.\.$//;
				$label_after = "...";
				$underline -= 3;
			}
			$label .= "(${$_}[1])$label_after";
			if ("CODE" eq ref ${$_}[2]) {
				$second->add_command(
					-label => $label,
					-underline => $underline,
					-command => ${$_}[2],
 				);
			} elsif ("SCALAR" eq ref ${$_}[2]) {
				$second->add_checkbutton(
					-label => $label,
					-underline => $underline,
					-variable => ${$_}[2],
					-offvalue => 0,
					-onvalue => 1,
 				);
			}
		}
	}

	$mainwindow->configure(-menu => $top);
}