| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package GnuCash::SQLite; |
|
2
|
|
|
|
|
|
|
|
|
3
|
2
|
|
|
2
|
|
645316
|
use strict; |
|
|
2
|
|
|
|
|
12
|
|
|
|
2
|
|
|
|
|
53
|
|
|
4
|
2
|
|
|
2
|
|
8
|
use warnings; |
|
|
2
|
|
|
|
|
2
|
|
|
|
2
|
|
|
|
|
46
|
|
|
5
|
2
|
|
|
2
|
|
25
|
use 5.10.0; |
|
|
2
|
|
|
|
|
6
|
|
|
6
|
2
|
|
|
2
|
|
919
|
use UUID::Tiny ':std'; |
|
|
2
|
|
|
|
|
29272
|
|
|
|
2
|
|
|
|
|
380
|
|
|
7
|
2
|
|
|
2
|
|
2705
|
use DBI; |
|
|
2
|
|
|
|
|
32324
|
|
|
|
2
|
|
|
|
|
149
|
|
|
8
|
2
|
|
|
2
|
|
868
|
use DateTime; |
|
|
2
|
|
|
|
|
419072
|
|
|
|
2
|
|
|
|
|
63
|
|
|
9
|
2
|
|
|
2
|
|
13
|
use Carp; |
|
|
2
|
|
|
|
|
3
|
|
|
|
2
|
|
|
|
|
122
|
|
|
10
|
2
|
|
|
2
|
|
1638
|
use Path::Tiny; |
|
|
2
|
|
|
|
|
19703
|
|
|
|
2
|
|
|
|
|
4004
|
|
|
11
|
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=head1 NAME |
|
13
|
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
GnuCash::SQLite - A module to access GnuCash SQLite files |
|
15
|
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
=head1 VERSION |
|
17
|
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
version 0.09 |
|
19
|
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
=cut |
|
21
|
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
our $VERSION = '0.09'; |
|
23
|
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
sub new { |
|
25
|
5
|
|
|
5
|
0
|
1303
|
my $class = shift; |
|
26
|
5
|
|
|
|
|
17
|
my %attr = @_; |
|
27
|
5
|
|
|
|
|
10
|
my $self = {}; |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
croak 'No GnuCash file defined.' |
|
30
|
5
|
100
|
|
|
|
218
|
unless defined($attr{db}); |
|
31
|
|
|
|
|
|
|
croak "File: $attr{db} does not exist." |
|
32
|
4
|
100
|
|
|
|
27
|
unless path($attr{db})->is_file; |
|
33
|
|
|
|
|
|
|
|
|
34
|
3
|
|
|
|
|
311
|
$self->{db} = $attr{db}; |
|
35
|
3
|
|
|
|
|
33
|
$self->{dbh} = DBI->connect("dbi:SQLite:dbname=$self->{db}","",""); |
|
36
|
|
|
|
|
|
|
|
|
37
|
3
|
|
|
|
|
21297
|
bless $self, $class; |
|
38
|
3
|
|
|
|
|
14
|
return $self; |
|
39
|
|
|
|
|
|
|
} |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
# Create a 32-character UUID |
|
42
|
|
|
|
|
|
|
sub create_guid { |
|
43
|
7
|
|
|
7
|
0
|
847
|
my $uuid = create_uuid_as_string(UUID_V1); |
|
44
|
7
|
|
|
|
|
1146
|
$uuid =~ s/-//g; |
|
45
|
7
|
|
|
|
|
37
|
return $uuid; |
|
46
|
|
|
|
|
|
|
} |
|
47
|
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
# Given an account name, return the GUID of the currency (aka commodity) |
|
49
|
|
|
|
|
|
|
# associated with that account |
|
50
|
|
|
|
|
|
|
sub commodity_guid { |
|
51
|
3
|
|
|
3
|
0
|
634
|
my $self = shift; |
|
52
|
3
|
|
|
|
|
5
|
my $account_name = shift; |
|
53
|
|
|
|
|
|
|
|
|
54
|
3
|
|
|
|
|
14
|
my $sql = "SELECT commodity_guid FROM accounts " |
|
55
|
|
|
|
|
|
|
. "WHERE guid = (".$self->account_guid_sql($account_name).")"; |
|
56
|
|
|
|
|
|
|
|
|
57
|
3
|
|
|
|
|
10
|
return $self->_runsql($sql)->[0][0]; |
|
58
|
|
|
|
|
|
|
} |
|
59
|
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
# Given a date in YYYYMMDD format, |
|
61
|
|
|
|
|
|
|
# This is always in the local timezone |
|
62
|
|
|
|
|
|
|
# And GnuCash stores all dates in UTC timezone |
|
63
|
|
|
|
|
|
|
# This function needs to: |
|
64
|
|
|
|
|
|
|
# 1. Create a date time with the local timezone |
|
65
|
|
|
|
|
|
|
# 2. Switch to the UTC timezone |
|
66
|
|
|
|
|
|
|
# 3. Store that timestamp |
|
67
|
|
|
|
|
|
|
# For example, the 'Asia/Bangkok' timezone is UTC +7:00 |
|
68
|
|
|
|
|
|
|
# given txn date of 20140101 (in the local timezone) |
|
69
|
|
|
|
|
|
|
# return 20131231170000 (which gets stored in the db) |
|
70
|
|
|
|
|
|
|
sub UTC_post_date { |
|
71
|
4
|
|
|
4
|
0
|
144
|
my $self = shift; |
|
72
|
4
|
|
|
|
|
25
|
my ($YYYY, $MM, $DD) = (shift =~ /(....)(..)(..)/); |
|
73
|
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
# Create a new |
|
75
|
4
|
|
|
|
|
33
|
my $dt = DateTime->new( |
|
76
|
|
|
|
|
|
|
year => $YYYY, |
|
77
|
|
|
|
|
|
|
month => $MM, |
|
78
|
|
|
|
|
|
|
day => $DD, |
|
79
|
|
|
|
|
|
|
time_zone => 'local' ); |
|
80
|
4
|
|
|
|
|
8978
|
$dt->set_time_zone('UTC'); |
|
81
|
4
|
|
|
|
|
65
|
return $dt->ymd('') . $dt->hms(''); |
|
82
|
|
|
|
|
|
|
} |
|
83
|
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
# Returns the system date in YYYYMMDDhhmmss format |
|
85
|
|
|
|
|
|
|
# Timezone is UTC (GMT 00:00) |
|
86
|
|
|
|
|
|
|
sub UTC_enter_date { |
|
87
|
3
|
|
|
3
|
0
|
1256
|
my $dt = DateTime->now(); |
|
88
|
3
|
|
|
|
|
710
|
return $dt->ymd('').$dt->hms(''); |
|
89
|
|
|
|
|
|
|
} |
|
90
|
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
# Given an account name, return the GUID of the account |
|
92
|
|
|
|
|
|
|
sub account_guid { |
|
93
|
18
|
|
|
18
|
0
|
649
|
my $self = shift; |
|
94
|
18
|
|
|
|
|
22
|
my $account_name = shift; |
|
95
|
|
|
|
|
|
|
|
|
96
|
18
|
|
|
|
|
51
|
my $sql = $self->account_guid_sql($account_name); |
|
97
|
18
|
|
|
|
|
43
|
return $self->_runsql($sql)->[0][0]; |
|
98
|
|
|
|
|
|
|
} |
|
99
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
# Given an account name, return the SQL that reads its GUID |
|
101
|
|
|
|
|
|
|
# Generate a recursive SQL given the full account name e.g. Assets:Cash |
|
102
|
|
|
|
|
|
|
# A naive implementation may just extract the tail account |
|
103
|
|
|
|
|
|
|
# i.e. SELECT guid FROM accounts WHERE name = 'Cash'; |
|
104
|
|
|
|
|
|
|
# That fails when accounts of the same name have different parents |
|
105
|
|
|
|
|
|
|
# e.g. Assets:Husband:Cash and Assets:Wife:Cash |
|
106
|
|
|
|
|
|
|
sub account_guid_sql { |
|
107
|
22
|
|
|
22
|
0
|
26
|
my $self = shift; |
|
108
|
22
|
|
|
|
|
37
|
my ($acct_name) = @_; |
|
109
|
22
|
|
|
|
|
31
|
my $sub_sql = 'SELECT guid FROM accounts WHERE name = "Root Account"'; |
|
110
|
22
|
|
|
|
|
71
|
foreach my $acct (split ":", $acct_name) { |
|
111
|
39
|
|
|
|
|
393
|
$sub_sql = 'SELECT guid FROM accounts ' |
|
112
|
|
|
|
|
|
|
. 'WHERE name = "'.$acct.'" ' |
|
113
|
|
|
|
|
|
|
. 'AND parent_guid = ('.$sub_sql.')'; |
|
114
|
|
|
|
|
|
|
} |
|
115
|
22
|
|
|
|
|
56
|
return $sub_sql; |
|
116
|
|
|
|
|
|
|
} |
|
117
|
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
# Given a guid, return a list of child guids or if none, an empty arrayref |
|
119
|
|
|
|
|
|
|
sub child_guid { |
|
120
|
27
|
|
|
27
|
0
|
35
|
my $self = shift; |
|
121
|
27
|
|
|
|
|
28
|
my $parent_guid = shift; |
|
122
|
|
|
|
|
|
|
|
|
123
|
27
|
|
|
|
|
58
|
my $sql = qq/SELECT guid FROM accounts WHERE parent_guid = "$parent_guid"/; |
|
124
|
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
# The map belows converts [[x],[y],[z]] into [x,y,z] |
|
126
|
27
|
|
|
|
|
30
|
my @res = map { $$_[0] } @{ $self->_runsql($sql) }; |
|
|
17
|
|
|
|
|
52
|
|
|
|
27
|
|
|
|
|
39
|
|
|
127
|
27
|
|
|
|
|
93
|
return \@res; |
|
128
|
|
|
|
|
|
|
} |
|
129
|
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
# Given an account guid, |
|
131
|
|
|
|
|
|
|
# Return the balance at that guid, ignoring child accounts if any. |
|
132
|
|
|
|
|
|
|
sub _node_bal { |
|
133
|
26
|
|
|
26
|
|
39
|
my $self = shift; |
|
134
|
26
|
|
|
|
|
31
|
my $guid = shift; |
|
135
|
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
# Use quantity_num instead of value_num to handle foreign currency |
|
137
|
|
|
|
|
|
|
# transactions |
|
138
|
26
|
|
|
|
|
36
|
my $sql = "SELECT printf('%.2f',SUM(quantity_num/(quantity_denom*1.0)))" |
|
139
|
|
|
|
|
|
|
. " FROM splits" |
|
140
|
|
|
|
|
|
|
. " WHERE account_guid = ?"; |
|
141
|
26
|
|
50
|
|
|
47
|
return $self->_runsql($sql,$guid)->[0][0] || 0; |
|
142
|
|
|
|
|
|
|
} |
|
143
|
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
# Recursive accumulator |
|
145
|
|
|
|
|
|
|
sub _guid_bal { |
|
146
|
25
|
|
|
25
|
|
39
|
my $self = shift; |
|
147
|
25
|
|
|
|
|
27
|
my $guid = shift; |
|
148
|
25
|
|
50
|
|
|
81
|
my $bal = shift || 0; |
|
149
|
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
# Accumulate balances in child accounts |
|
151
|
25
|
|
|
|
|
28
|
foreach my $g (@{$self->child_guid($guid)}) { |
|
|
25
|
|
|
|
|
49
|
|
|
152
|
15
|
|
|
|
|
40
|
$bal += $self->_guid_bal($g); |
|
153
|
|
|
|
|
|
|
} |
|
154
|
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
# Add balance in node and return |
|
156
|
25
|
|
|
|
|
62
|
return $bal + $self->_node_bal($guid); |
|
157
|
|
|
|
|
|
|
} |
|
158
|
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
# Given an account name, |
|
160
|
|
|
|
|
|
|
# Return the balance in that account, include child accounts, if any |
|
161
|
|
|
|
|
|
|
sub account_balance { |
|
162
|
9
|
|
|
9
|
1
|
2103
|
my $self = shift; |
|
163
|
9
|
|
|
|
|
14
|
my $acct_name = shift; |
|
164
|
|
|
|
|
|
|
|
|
165
|
9
|
|
|
|
|
27
|
my $guid = $self->account_guid($acct_name); |
|
166
|
9
|
100
|
|
|
|
35
|
return undef unless defined ($guid); |
|
167
|
8
|
|
|
|
|
27
|
return $self->_guid_bal($guid); |
|
168
|
|
|
|
|
|
|
} |
|
169
|
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
# Add a transaction to the GnuCash. |
|
171
|
|
|
|
|
|
|
# Transaction is a hashref e.g.: |
|
172
|
|
|
|
|
|
|
# |
|
173
|
|
|
|
|
|
|
# my $txn = { |
|
174
|
|
|
|
|
|
|
# date => '20140102', |
|
175
|
|
|
|
|
|
|
# description => 'Deposit monthly savings', |
|
176
|
|
|
|
|
|
|
# from_account => 'Assets:Cash', |
|
177
|
|
|
|
|
|
|
# to_account => 'Assets:aBank', |
|
178
|
|
|
|
|
|
|
# amount => 2540.15, |
|
179
|
|
|
|
|
|
|
# number => '' |
|
180
|
|
|
|
|
|
|
# }; |
|
181
|
|
|
|
|
|
|
# |
|
182
|
|
|
|
|
|
|
# To effect the transaction, do the following: |
|
183
|
|
|
|
|
|
|
# 1. Add 1 row to transactions table |
|
184
|
|
|
|
|
|
|
# 2. Add 2 rows to splits table |
|
185
|
|
|
|
|
|
|
# 3. Add 1 row to slots table |
|
186
|
|
|
|
|
|
|
# See |
|
187
|
|
|
|
|
|
|
# http://wideopenstudy.blogspot.com/2014/11/how-to-add-transaction-programmatically.html |
|
188
|
|
|
|
|
|
|
sub add_transaction { |
|
189
|
1
|
|
|
1
|
1
|
1003
|
my $self = shift; |
|
190
|
1
|
|
|
|
|
3
|
my $txn = shift; |
|
191
|
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
# augment the transaction with needed data |
|
193
|
1
|
|
|
|
|
3
|
$txn = $self->_augment($txn); |
|
194
|
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
# List the SQLs |
|
196
|
1
|
|
|
|
|
2
|
my $txn_sql = 'INSERT INTO transactions VALUES (?,?,?,?,?,?)'; |
|
197
|
1
|
|
|
|
|
2
|
my $splt_sql = 'INSERT INTO splits VALUES ' |
|
198
|
|
|
|
|
|
|
. ' (?,?,?,"","","n","",?,100,?,100,null)'; |
|
199
|
1
|
|
|
|
|
3
|
my $slot_sql = 'INSERT INTO slots (obj_guid,name,slot_type,int64_val,' |
|
200
|
|
|
|
|
|
|
. ' string_val,double_val,timespec_val,' |
|
201
|
|
|
|
|
|
|
. ' guid_val,numeric_val_num,' |
|
202
|
|
|
|
|
|
|
. ' numeric_val_denom,gdate_val) ' |
|
203
|
|
|
|
|
|
|
. 'VALUES (?,"date-posted",10,0,"",0.0,"","",0,1,?)'; |
|
204
|
|
|
|
|
|
|
# This SQL form because slots has auto-increment field |
|
205
|
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
# Run the SQLs |
|
207
|
1
|
|
|
|
|
2
|
$self->_runsql($txn_sql, map { $txn->{$_} } |
|
|
6
|
|
|
|
|
9
|
|
|
208
|
|
|
|
|
|
|
qw/tx_guid tx_ccy_guid number tx_post_date tx_enter_date |
|
209
|
|
|
|
|
|
|
description /); |
|
210
|
1
|
|
|
|
|
5
|
$self->_runsql($splt_sql, map { $txn->{$_} } |
|
|
5
|
|
|
|
|
18
|
|
|
211
|
|
|
|
|
|
|
qw/splt_guid_1 tx_guid tx_from_guid tx_from_numer tx_from_numer/); |
|
212
|
1
|
|
|
|
|
5
|
$self->_runsql($splt_sql, map { $txn->{$_} } |
|
|
5
|
|
|
|
|
17
|
|
|
213
|
|
|
|
|
|
|
qw/splt_guid_2 tx_guid tx_to_guid tx_to_numer tx_to_numer/); |
|
214
|
1
|
|
|
|
|
7
|
$self->_runsql($slot_sql, map { $txn->{$_} } |
|
|
2
|
|
|
|
|
13
|
|
|
215
|
|
|
|
|
|
|
qw/tx_guid date/); |
|
216
|
|
|
|
|
|
|
} |
|
217
|
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
# Augment the transaction with data required to generate data rows |
|
219
|
|
|
|
|
|
|
sub _augment { |
|
220
|
2
|
|
|
2
|
|
93
|
my $self = shift; |
|
221
|
2
|
|
|
|
|
3
|
my $txn_orig = shift; |
|
222
|
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
# Make a copy of the original transaction so as not to clobber it |
|
224
|
|
|
|
|
|
|
# Copy only the fields needed |
|
225
|
2
|
|
|
|
|
4
|
my $txn = {}; |
|
226
|
2
|
|
|
|
|
6
|
map { $txn->{$_} = $txn_orig->{$_} } ( |
|
|
12
|
|
|
|
|
22
|
|
|
227
|
|
|
|
|
|
|
qw/date description from_account to_account amount number/); |
|
228
|
|
|
|
|
|
|
|
|
229
|
2
|
|
|
|
|
6
|
$txn->{tx_guid} = $self->create_guid(); |
|
230
|
2
|
|
|
|
|
8
|
$txn->{tx_ccy_guid} = $self->commodity_guid($txn->{from_account}); |
|
231
|
2
|
|
|
|
|
8
|
$txn->{tx_post_date} = $self->UTC_post_date($txn->{date}); |
|
232
|
2
|
|
|
|
|
57
|
$txn->{tx_enter_date} = $self->UTC_enter_date(); |
|
233
|
2
|
|
|
|
|
40
|
$txn->{tx_from_guid} = $self->account_guid($txn->{from_account}); |
|
234
|
2
|
|
|
|
|
6
|
$txn->{tx_to_guid} = $self->account_guid($txn->{to_account}); |
|
235
|
2
|
|
|
|
|
7
|
$txn->{tx_from_numer} = $txn->{amount} * -100; |
|
236
|
2
|
|
|
|
|
5
|
$txn->{tx_to_numer} = $txn->{amount} * 100; |
|
237
|
2
|
|
|
|
|
9
|
$txn->{splt_guid_1} = $self->create_guid(); |
|
238
|
2
|
|
|
|
|
5
|
$txn->{splt_guid_2} = $self->create_guid(); |
|
239
|
|
|
|
|
|
|
|
|
240
|
2
|
|
|
|
|
7
|
return $txn; |
|
241
|
|
|
|
|
|
|
} |
|
242
|
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
# Return 1 if Gnucash database is locked, |
|
244
|
|
|
|
|
|
|
# Return 0 if no other application has locked the database. |
|
245
|
|
|
|
|
|
|
sub is_locked { |
|
246
|
2
|
|
|
2
|
0
|
4
|
my $self = shift; |
|
247
|
2
|
|
|
|
|
6
|
my $sql = "SELECT count(*) FROM gnclock"; |
|
248
|
2
|
100
|
|
|
|
6
|
return $self->_runsql($sql)->[0][0] == 0 ? 0 : 1; |
|
249
|
|
|
|
|
|
|
} |
|
250
|
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
# Given an SQL statement and optionally a list of arguments |
|
252
|
|
|
|
|
|
|
# execute the SQL with those arguments |
|
253
|
|
|
|
|
|
|
sub _runsql { |
|
254
|
82
|
|
|
82
|
|
97
|
my $self = shift; |
|
255
|
82
|
|
|
|
|
136
|
my ($sql,@args) = @_; |
|
256
|
|
|
|
|
|
|
|
|
257
|
82
|
|
|
|
|
435
|
my $sth = $self->{dbh}->prepare($sql); |
|
258
|
82
|
|
|
|
|
73673
|
$sth->execute(@args); |
|
259
|
82
|
|
|
|
|
1302
|
my $data = $sth->fetchall_arrayref(); |
|
260
|
82
|
|
|
|
|
281
|
$sth->finish; |
|
261
|
|
|
|
|
|
|
|
|
262
|
82
|
|
|
|
|
1632
|
return $data; |
|
263
|
|
|
|
|
|
|
} |
|
264
|
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
1; |
|
266
|
|
|
|
|
|
|
__END__ |
|
267
|
|
|
|
|
|
|
# Below is stub documentation for your module. You'd better edit it! |
|
268
|
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
270
|
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
use GnuCash::SQLite; |
|
272
|
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
# create the book |
|
274
|
|
|
|
|
|
|
$book = GnuCash::SQLite->new(db => 'my_accounts.gnucash'); |
|
275
|
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
# get account balances |
|
277
|
|
|
|
|
|
|
$on_hand = $book->account_balance('Assets:Cash'); |
|
278
|
|
|
|
|
|
|
$total = $book->account_balance('Assets'); |
|
279
|
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
# check if book is locked by another application |
|
281
|
|
|
|
|
|
|
die "Book is currently used by another application." |
|
282
|
|
|
|
|
|
|
if $book->is_locked; |
|
283
|
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
# add a transaction |
|
285
|
|
|
|
|
|
|
$book->add_transaction({ |
|
286
|
|
|
|
|
|
|
date => '20140102', |
|
287
|
|
|
|
|
|
|
description => 'Deposit monthly savings', |
|
288
|
|
|
|
|
|
|
from_account => 'Assets:Cash', |
|
289
|
|
|
|
|
|
|
to_account => 'Assets:aBank', |
|
290
|
|
|
|
|
|
|
amount => 2540.15, |
|
291
|
|
|
|
|
|
|
number => '' |
|
292
|
|
|
|
|
|
|
}); |
|
293
|
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
# access internal GUIDs |
|
295
|
|
|
|
|
|
|
$book->account_guid('Assets:Cash'); # GUID of account |
|
296
|
|
|
|
|
|
|
$book->commodity_guid('Assets:Cash'); # GUID of currency |
|
297
|
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
299
|
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
GnuCash::SQLite provides an API to read account balances and write |
|
301
|
|
|
|
|
|
|
transactions against a GnuCash set of accounts (only SQLite3 backend |
|
302
|
|
|
|
|
|
|
supported). |
|
303
|
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
When using the module, always provide account names in full e.g. "Assets:Cash" |
|
305
|
|
|
|
|
|
|
rather than just "Cash". This lets the module distinguish between accounts |
|
306
|
|
|
|
|
|
|
with the same name but different parents e.g. Assets:Misc and |
|
307
|
|
|
|
|
|
|
Expenses:Misc |
|
308
|
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
=head1 METHODS |
|
310
|
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
=head2 Constructor |
|
312
|
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
$book = GnuCash::SQLite->new(db => 'my_account.gnucash'); |
|
314
|
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
Returns a new C<GnuCash::SQLite> object that accesses a GnuCash with and |
|
316
|
|
|
|
|
|
|
SQLite backend. The module assumes you have already created a GnuCash file |
|
317
|
|
|
|
|
|
|
with an SQLite backend and that is the file that should be passed as the |
|
318
|
|
|
|
|
|
|
parameter. |
|
319
|
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
If no file parameter is passed, or if the file is missing, the program will |
|
321
|
|
|
|
|
|
|
terminate. |
|
322
|
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
=head2 account_balance |
|
324
|
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
$book->account_balance('Assets:Cash'); # always provide account names in full |
|
326
|
|
|
|
|
|
|
$book->account_balance('Assets'); # includes child accounts e.g. Assets:Cash |
|
327
|
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
Given an account name, return the balance in the account. Account names must |
|
329
|
|
|
|
|
|
|
be provided in full to distinguish between accounts with the same name but |
|
330
|
|
|
|
|
|
|
different parents e.g. Assets:Alice:Cash and Assets:Bob:Cash |
|
331
|
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
If a parent account name is provided, the total balance, which includes all |
|
333
|
|
|
|
|
|
|
children accounts, will be returned. |
|
334
|
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
=head2 add_transaction |
|
336
|
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
$deposit = { |
|
338
|
|
|
|
|
|
|
date => '20140102', |
|
339
|
|
|
|
|
|
|
description => 'Deposit monthly savings', |
|
340
|
|
|
|
|
|
|
from_account => 'Assets:Cash', |
|
341
|
|
|
|
|
|
|
to_account => 'Assets:aBank', |
|
342
|
|
|
|
|
|
|
amount => 2540.15, |
|
343
|
|
|
|
|
|
|
number => '' |
|
344
|
|
|
|
|
|
|
}; |
|
345
|
|
|
|
|
|
|
$book->add_transaction($deposit); |
|
346
|
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
A transaction is defined to have the fields as listed in the example above. |
|
348
|
|
|
|
|
|
|
All fields are mandatory and hopefully self-explanatory. Constraints on some |
|
349
|
|
|
|
|
|
|
of the fields are listed below: |
|
350
|
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
date Date of the transaction. Formatted as YYYYMMDD. |
|
352
|
|
|
|
|
|
|
from_account Full account name required. |
|
353
|
|
|
|
|
|
|
to_account Full account name required. |
|
354
|
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
=head1 CAVEATS/LIMITATIONS |
|
357
|
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
Some things to be aware of: |
|
359
|
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
1. You should have created a GnuCash file with an SQLite backend already |
|
361
|
|
|
|
|
|
|
2. Module accesses the GnuCash SQLite3 db directly; i.e. use at your own risk. |
|
362
|
|
|
|
|
|
|
3. Only transactions between Asset accounts have been tested. |
|
363
|
|
|
|
|
|
|
4. Only two (2) splits for each transaction will be created |
|
364
|
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
This module works with GnuCash v2.4.13 on Linux. |
|
366
|
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
368
|
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
GnuCash wiki pages includes a section on C API and a section on Python |
|
370
|
|
|
|
|
|
|
bindings which may be of interest. |
|
371
|
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
C API : http://wiki.gnucash.org/wiki/C_API |
|
373
|
|
|
|
|
|
|
Python bindings: http://wiki.gnucash.org/wiki/Python_Bindings |
|
374
|
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
This module does not rely on the C API (maybe it should). Instead it relies on |
|
376
|
|
|
|
|
|
|
some reverse engineering work to understand the changes a transaction makes |
|
377
|
|
|
|
|
|
|
to the sqlite database. See |
|
378
|
|
|
|
|
|
|
http://wideopenstudy.blogspot.com/search/label/GnuCash for details. |
|
379
|
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
=head1 SUPPORT |
|
381
|
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
=head2 Bugs / Feature Requests |
|
383
|
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
Please report any bugs or feature requests through the issue tracker at |
|
385
|
|
|
|
|
|
|
L<https://github.com/hoekit/GnuCash-SQLite/issues>. You will be notified |
|
386
|
|
|
|
|
|
|
automatically of any progress on your issue. |
|
387
|
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
=head2 Source Code |
|
389
|
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
This is open source software. The code repository is available for public |
|
391
|
|
|
|
|
|
|
review and contribution under the terms of the license. |
|
392
|
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
<https://github.com/hoekit/GnuCash-SQLite> |
|
394
|
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
git clone git@github.com:hoekit/GnuCash-SQLite.git |
|
396
|
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
=head1 CREDITS |
|
398
|
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
Credit goes to L<Sawyer X|https://metacpan.org/author/XSAWYERX> for fixing long-standing floating-point bug. |
|
400
|
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
=head1 AUTHOR |
|
402
|
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
Hoe Kit CHEW, E<lt>hoekit at gmail.comE<gt> |
|
404
|
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
|
406
|
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
Copyright (C) 2014 by Chew Hoe Kit |
|
408
|
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or modify |
|
410
|
|
|
|
|
|
|
it under the same terms as Perl itself, either Perl version 5.10.0 or, |
|
411
|
|
|
|
|
|
|
at your option, any later version of Perl 5 you may have available. |
|
412
|
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
=cut |