Annotation of pta/pta_import.pl, Revision 1.12
1.1 schwarze 1: #!/usr/bin/perl
2:
3: # Copyright (c) 2020 Freda Bundchen
4:
5: # Permission to use, copy, modify, and distribute this software for any
6: # purpose with or without fee is hereby granted, provided that the above
7: # copyright notice and this permission notice appear in all copies.
8: #
9: # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10: # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11: # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12: # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13: # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14: # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15: # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16:
17: use warnings;
18: use strict;
19:
1.9 schwarze 20: use Getopt::Std qw(getopts);
21:
22: our ($opt_I);
23:
1.1 schwarze 24: # === SUBROUTINES =====================================================
25:
1.12 ! schwarze 26: sub import_account ($$$$) {
! 27: my ($account_name, $compiled_ref, $delim, $csv_account) = @_;
1.10 schwarze 28: my ($account, $amount, $booking, $credit, $date, $debit,
29: $description);
1.5 schwarze 30: chomp(my $header = <>);
1.2 schwarze 31: while (<>) {
1.5 schwarze 32: next if (/^$/);
1.1 schwarze 33: chomp;
1.3 schwarze 34: my $line = $_;
1.12 ! schwarze 35: my @fields = split /$delim/, $line;
! 36: my $matches = 0;
! 37: foreach my $selector (@$compiled_ref) {
! 38: $matches = 1;
! 39: for (my $i = 0; $i <= $#{$selector->{re}}; $i++) {
! 40: next if $fields[$i] =~ $selector->{re}[$i];
! 41: $matches = 0;
! 42: last;
! 43: }
! 44: if ($matches) {
! 45: $account = $selector->{ac};
! 46: $booking = $selector->{id};
1.4 schwarze 47: last;
1.3 schwarze 48: }
49: }
1.12 ! schwarze 50: $matches or die "unmatched CSV line: $line";
1.10 schwarze 51: if ($account_name eq "chase_credit") {
52: ($date, $amount, $debit, $credit,
53: $description) =
54: import_chase_credit($account,
55: $csv_account, $line);
56: } elsif ($account_name eq "capital_one_credit") {
57: ($date, $amount, $debit, $credit,
58: $description) =
59: import_capital_one_credit($account,
60: $csv_account, $line);
61: } elsif ($account_name eq "optum_hsa") {
62: ($date, $amount, $debit, $credit,
63: $description) =
64: import_optum_hsa($account,
65: $csv_account, $line);
1.11 schwarze 66: } elsif ($account_name eq "sparkasse_camt") {
67: ($date, $amount, $debit, $credit,
68: $description) =
69: import_sparkasse_camt($account,
70: $csv_account, $line);
1.10 schwarze 71: } else {
72: die "undefined format: $account_name";
73: }
74: unless ($date && $booking && $debit && $credit &&
75: $amount && $description) {
1.5 schwarze 76: die "import parse error: $line";
1.3 schwarze 77: }
1.10 schwarze 78: $description =~ s/#//g;
79: print "$date $booking $debit $credit $amount " .
1.1 schwarze 80: "$description\n";
81: }
82: }
83:
1.10 schwarze 84: sub import_chase_credit ($$$) {
85: my ($account, $csv_account, $line) = @_;
86: my ($trans_date, $post_date, $description, $category,
87: $type, $amount) = split /,/, $line;
88: my ($debit, $credit);
89: $post_date =~ s#(\d+)/(\d+)/(\d+)#$3$1$2#;
90: ($amount, $debit, $credit) =
91: get_accounts_by_amount_sign($amount, $account,
92: $csv_account);
93: return ($post_date, $amount, $debit, $credit, $description);
94: }
95:
96: sub import_capital_one_credit ($$$) {
97: my ($account, $csv_account, $line) = @_;
98: my ($trans_date, $post_date, $card_num,
99: $description, $category, $csv_debit,
100: $csv_credit) = split /,/, $line;
101: $post_date =~ s/(\d+)-(\d+)-(\d+)/$1$2$3/;
102: my ($amount, $debit, $credit) =
103: get_accounts_by_csv_col($account, $csv_account,
104: $csv_debit, $csv_credit);
105: return ($post_date, $amount, $debit, $credit, $description);
106: }
107:
108: sub import_optum_hsa ($$$) {
109: my ($account, $csv_account, $line) = @_;
110: my ($date, $description, $amount,
111: $type) = split /,/, $line;
112: my ($debit, $credit);
113: $date =~ s/(\d+)-(\d+)-(\d+)/$1$2$3/;
114: $amount =~ s/\$//;
115: ($amount, $debit, $credit) =
116: get_accounts_by_amount_sign($amount, $account,
117: $csv_account);
118: return ($date, $amount, $debit, $credit, $description);
119: }
120:
1.11 schwarze 121: sub import_sparkasse_camt ($$$) {
122: my ($account, $csv_account, $line) = @_;
123: my @fields;
124: $_ = $line;
125: push @fields, $1 while s/"([^"]*)";?//;
126: $_ eq "" or die "CAMT parse error before $_ in $line";
127: @fields == 17 or die "not 17 but @fields fields: $line";
128: $fields[1] =~ s/^(\d\d)\.(\d\d)\.(\d\d)$/20$3$2$1/
129: or die "date parse error: $line";
130: $fields[14] =~ s/,/./;
131: return $fields[1],
132: get_accounts_by_amount_sign($fields[14], $account, $csv_account),
133: (join ' ', $fields[11], $fields[4]);
134: }
135:
1.10 schwarze 136: sub get_accounts_by_amount_sign ($$$) {
137: my ($amount, $account, $csv_account) = @_;
138: my ($debit, $credit);
139: if ($amount <= 0) {
140: $amount = substr $amount, 1;
141: $credit = $csv_account;
142: $debit = $account;
143: } else {
144: $debit = $csv_account;
145: $credit = $account;
1.5 schwarze 146: }
1.10 schwarze 147: return ($amount, $debit, $credit);
1.5 schwarze 148: }
1.9 schwarze 149:
1.10 schwarze 150: sub get_accounts_by_csv_col ($$$$) {
151: my ($account, $csv_account, $csv_debit, $csv_credit) = @_;
152: my ($amount, $debit, $credit);
153: if ($csv_debit eq "") {
154: $amount = $csv_credit;
155: $credit = $account;
156: $debit = $csv_account;
157: } else {
158: $amount = $csv_debit;
159: $credit = $csv_account;
160: $debit = $account;
161: }
162: return ($amount, $debit, $credit);
1.9 schwarze 163: }
164:
165: sub usage () {
1.10 schwarze 166: printf STDERR "usage: %s -I accountname csvfilename\n", $0;
1.9 schwarze 167: exit 1;
168: }
169:
1.1 schwarze 170: # === MAIN PROGRAM =====================================================
171:
1.12 ! schwarze 172: my ($csv_account, $fn, $in, $account_name, $delim, @compiled);
1.9 schwarze 173: getopts 'I:' or usage;
174: if ($opt_I) {
1.10 schwarze 175: $account_name = $opt_I;
176: $fn = "import_" . $account_name . ".txt";
1.9 schwarze 177: open $in, $fn or die "$fn: $!";
1.1 schwarze 178: } else {
1.9 schwarze 179: usage;
180: }
181: while (<$in>) {
182: chomp;
183: next if /^(?:#|$)/;
184: my $line = $_;
1.12 ! schwarze 185: if (/^ACCOUNT\s+(\S+)$/) {
1.9 schwarze 186: $csv_account and die "duplicate ACCOUNT line: $1";
187: $csv_account = $1;
188: next;
189: }
1.12 ! schwarze 190: if (/^DELIM\s+(\S)$/) {
! 191: $delim and die "duplicate DELIM line: $1";
! 192: $delim = $1;
! 193: next;
! 194: }
! 195: $delim or die "no DELIM line in $fn";
! 196: s/^(.*)$delim\s+(\d+)\s+(\S+)// or
1.9 schwarze 197: die "$fn import parse error: $line";
1.12 ! schwarze 198: push @compiled, {
! 199: re => [map { qr/$_/ } split /$delim/, $1],
! 200: ac => $2,
! 201: id => $3,
! 202: };
1.1 schwarze 203: }
1.9 schwarze 204: close $in;
205: $csv_account or die "no ACCOUNT line in $fn";
1.12 ! schwarze 206: import_account($account_name, \@compiled, $delim, $csv_account);
CVSweb