africompta 1.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +74 -0
- data/TODO +31 -0
- data/Test/ac_account.rb +128 -0
- data/Test/ac_africompta.rb +1001 -0
- data/Test/ac_big.rb +62 -0
- data/Test/ac_movement.rb +59 -0
- data/Test/ac_sqlite.rb +139 -0
- data/Test/config_test.yaml +31 -0
- data/Test/db.testGestion +0 -0
- data/Test/test.rb +39 -0
- data/VERSION +140 -0
- data/africompta.gemspec +20 -0
- data/lib/africompta/acaccess.rb +257 -0
- data/lib/africompta/acqooxview.rb +77 -0
- data/lib/africompta/africompta.rb +83 -0
- data/lib/africompta/entities/account.rb +995 -0
- data/lib/africompta/entities/acschemas.rb +16 -0
- data/lib/africompta/entities/movement.rb +292 -0
- data/lib/africompta/entities/remote.rb +27 -0
- data/lib/africompta/entities/users.rb +55 -0
- data/lib/africompta/views/edit/movement.rb +8 -0
- data/lib/africompta/views/edit/tabs.rb +8 -0
- data/lib/africompta/views/report/annual.rb +3 -0
- data/lib/africompta/views/report/tabs.rb +3 -0
- data/lib/africompta.rb +2 -0
- metadata +84 -0
@@ -0,0 +1,257 @@
|
|
1
|
+
# This is the interface to AfriCompta. It is called through different
|
2
|
+
# Post/Get-handlers over HTTP
|
3
|
+
|
4
|
+
|
5
|
+
$VERSION = 0x1120
|
6
|
+
|
7
|
+
class ACaccess < RPCQooxdooPath
|
8
|
+
def self.parse(r, p, q)
|
9
|
+
dputs(2) { "in ACaccess: #{p} - #{q.inspect}" }
|
10
|
+
method = p.gsub(/^\/acaccess\/merge\//, '')
|
11
|
+
dputs(3) { "Calling method #{method} of #{r}" }
|
12
|
+
case r
|
13
|
+
when /GET/
|
14
|
+
ret = self.get(method)
|
15
|
+
when /POST/
|
16
|
+
ret = self.post(method, q)
|
17
|
+
end
|
18
|
+
dputs(3) { "Result is #{ret.inspect}" }
|
19
|
+
ret
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.print_movements(start, stop)
|
23
|
+
start, stop = start.to_i, stop.to_i
|
24
|
+
dputs(2) { "Doing print_movements from #{start.class}:#{start}"+
|
25
|
+
" to #{stop.class}:#{stop}" }
|
26
|
+
ret = ''
|
27
|
+
# Movements.search_all.select{|m|
|
28
|
+
# mi = m.rev_index
|
29
|
+
# m and mi and mi >= start and mi <= stop
|
30
|
+
Movements.search_index_range(start, stop).each { |m|
|
31
|
+
if start > 0
|
32
|
+
dputs(4) { "Mer: Movement #{m.desc}, #{m.value}" }
|
33
|
+
end
|
34
|
+
ret += m.to_s + "\n"
|
35
|
+
}
|
36
|
+
dputs(3) { "Found movements: #{ret.inspect}" }
|
37
|
+
ret
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.print_movements_actual(start, stop)
|
41
|
+
start, stop = start.to_i, stop.to_i
|
42
|
+
dputs(2) { "Doing print_movements_actual from #{start.class}:#{start}"+
|
43
|
+
" to #{stop.class}:#{stop}" }
|
44
|
+
ret = ''
|
45
|
+
actual_ids = []
|
46
|
+
AccountRoot.actual.get_tree { |a|
|
47
|
+
actual_ids.push a.id
|
48
|
+
}
|
49
|
+
movs = Movements.search_all.select { |m|
|
50
|
+
mi = m.rev_index
|
51
|
+
m and mi and mi >= start and mi <= stop and actual_ids.find(m.account_src_id)
|
52
|
+
}
|
53
|
+
dputs(2) { "Found #{movs.length} movements between #{start}..#{stop}" }
|
54
|
+
movs.each { |m|
|
55
|
+
if start > 0
|
56
|
+
dputs(4) { "Mer: Movement #{m.desc}, #{m.value}" }
|
57
|
+
ai = actual_ids.find(m.account_src_id.id)
|
58
|
+
dputs(4) { "movement_src is #{m.account_src_id.inspect} from #{ai.inspect}" }
|
59
|
+
dputs(4) { "actual_ids is #{actual_ids.inspect}" }
|
60
|
+
end
|
61
|
+
ret += m.to_s + "\n"
|
62
|
+
}
|
63
|
+
dputs(3) { "Found movements: #{ret.inspect}" }
|
64
|
+
ret
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.accounts_fetch_old(user)
|
68
|
+
ret = ''
|
69
|
+
|
70
|
+
Accounts.matches_by_account_id(0).to_a.concat(
|
71
|
+
Accounts.matches_by_account_id(nil)).sort { |a, b|
|
72
|
+
a.global_id <=> b.global_id }.each { |a|
|
73
|
+
dputs(2) { "Found one root-account #{a.rev_index} - #{a.path_id}" }
|
74
|
+
if a.global_id
|
75
|
+
dputs(3) { "It's global" }
|
76
|
+
a.get_tree { |acc|
|
77
|
+
dputs(4) { "In get_tree #{acc.path_id}: #{acc.deleted == true} - #{acc.rev_index}" }
|
78
|
+
if acc.rev_index > user.account_index
|
79
|
+
dputs(4) { "Found account #{acc.name} with index #{acc.rev_index}" }
|
80
|
+
ret += "#{acc.to_s}\n"
|
81
|
+
end
|
82
|
+
}
|
83
|
+
else
|
84
|
+
dputs(3) { "It's not global" }
|
85
|
+
end
|
86
|
+
dputs(3) { 'Will search for next' }
|
87
|
+
}
|
88
|
+
ret
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.accounts_fetch(user)
|
92
|
+
Accounts.data.select { |_k, v| v._rev_index > user.account_index }.
|
93
|
+
collect { |k, _v| Accounts.get_data_instance(k) }.
|
94
|
+
sort_by { |a| a.path }.
|
95
|
+
collect { |a| a.to_s }.
|
96
|
+
join("\n")
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.get(p)
|
100
|
+
# Two cases:
|
101
|
+
# path/arg/user,pass - arg is used
|
102
|
+
# path/user,pass - arg is nil
|
103
|
+
path, arg, id = p.split("/")
|
104
|
+
arg, id = id, arg if not id
|
105
|
+
user, pass = id.split(",")
|
106
|
+
|
107
|
+
log_msg 'ACaccess.get', "get-merge-path #{path} - #{arg} with " +
|
108
|
+
"user #{user} and pass #{pass}"
|
109
|
+
u = Users.match_by_name(user)
|
110
|
+
u_local = Users.match_by_name('local')
|
111
|
+
if not (u and u.pass == pass)
|
112
|
+
return "User #{user} not known with pass #{pass}"
|
113
|
+
end
|
114
|
+
|
115
|
+
case path
|
116
|
+
when /accounts_get(.*)/
|
117
|
+
# Gets all accounts available (to that user) that have been changed
|
118
|
+
# since the last update, again, do it from the root(s), else we have
|
119
|
+
# a problem for children without parents
|
120
|
+
ret = ''
|
121
|
+
dputs(2) { "user index is: #{u.account_index}" }
|
122
|
+
# Returns only one account
|
123
|
+
if $1 == '_one'
|
124
|
+
return Accounts.match_by_global_id(arg).to_s
|
125
|
+
end
|
126
|
+
if $1 == '_all'
|
127
|
+
dputs(2) { 'Putting all accounts' }
|
128
|
+
ret = Accounts.search_all.collect { |acc|
|
129
|
+
dputs(4) { "Found account #{acc.name} with index #{acc.rev_index}" }
|
130
|
+
acc.to_s(true)
|
131
|
+
}.join("\n")
|
132
|
+
elsif $1 == '_count'
|
133
|
+
ret += Accounts.search_all.size.to_s
|
134
|
+
elsif $1 == '_part'
|
135
|
+
acc_start, acc_end = arg.split(",")
|
136
|
+
dputs(2) { "Putting accounts #{acc_start}..#{acc_end}" }
|
137
|
+
Accounts.search_all.select { |acc|
|
138
|
+
acc.rev_index >= acc_start.to_i and acc.rev_index <= acc_end.to_i
|
139
|
+
}.each { |acc|
|
140
|
+
dputs(4) { "Found account #{acc.name} with index #{acc.rev_index}" }
|
141
|
+
ret += "#{acc.to_s(true)}\n"
|
142
|
+
}
|
143
|
+
else
|
144
|
+
dputs(2) { 'Starting to search accounts' }
|
145
|
+
t = Time.now
|
146
|
+
ret += ACaccess.accounts_fetch(u)
|
147
|
+
dputs(2) { "Found #{ret.count("\n")} after #{Time.now - t} seconds" }
|
148
|
+
end
|
149
|
+
dputs(3) { 'Finished search' }
|
150
|
+
return ret
|
151
|
+
|
152
|
+
# Gets all movements (for the accounts of that user)
|
153
|
+
when /movements_get(.*)/
|
154
|
+
dputs(2) { "movements_get#{$1} with #{arg.inspect}" }
|
155
|
+
start, stop = u.movement_index + 1, u_local.movement_index - 1
|
156
|
+
# Returns only one account
|
157
|
+
if $1 == '_one'
|
158
|
+
return Movements.match_by_global_id(arg).to_s
|
159
|
+
end
|
160
|
+
if $1 == '_all_actual'
|
161
|
+
start, stop = arg.split(/,/)
|
162
|
+
ret = print_movements_actual(start, stop)
|
163
|
+
else
|
164
|
+
if $1 == '_all'
|
165
|
+
start, stop = arg.split(/,/)
|
166
|
+
end
|
167
|
+
ret = print_movements(start, stop)
|
168
|
+
end
|
169
|
+
dputs(2) { "Sending a total of #{ret.length}" }
|
170
|
+
dputs(3) { "Sending:\n #{ret.inspect}" }
|
171
|
+
return ret
|
172
|
+
|
173
|
+
when 'version'
|
174
|
+
return $VERSION.to_s
|
175
|
+
|
176
|
+
when 'index'
|
177
|
+
return [u_local.account_index, u_local.movement_index].join(",")
|
178
|
+
|
179
|
+
when 'local_id'
|
180
|
+
return u_local.full
|
181
|
+
|
182
|
+
when 'reset_user_indexes'
|
183
|
+
u.update_account_index
|
184
|
+
u.update_movement_index
|
185
|
+
|
186
|
+
when 'reset_user_account_indexes'
|
187
|
+
u.update_account_index
|
188
|
+
|
189
|
+
when 'reset_user_movement_indexes'
|
190
|
+
u.update_movement_index
|
191
|
+
|
192
|
+
when 'movement_delete'
|
193
|
+
dputs(3) { "Going to delete movement #{arg}" }
|
194
|
+
while mov = Movements.match_by_global_id(arg)
|
195
|
+
dputs(3) { "Found movement #{mov.inspect}" }
|
196
|
+
mov.delete
|
197
|
+
end
|
198
|
+
dputs(3) { 'Finished deleting' }
|
199
|
+
end
|
200
|
+
return ''
|
201
|
+
end
|
202
|
+
|
203
|
+
def self.post(path, input)
|
204
|
+
dputs(5) { "self.post with #{path} and #{input.inspect}" }
|
205
|
+
log_msg 'ACaccess.post', "post-merge-path #{path} with " +
|
206
|
+
"user #{input['user']} and pass #{input['pass']}"
|
207
|
+
user, pass = input['user'], input['pass']
|
208
|
+
u = Users.match_by_name(user)
|
209
|
+
if not (u and u.pass == pass)
|
210
|
+
dputs(1) { "Didn't find user #{user}" }
|
211
|
+
return "User #{user} not known with pass #{pass}"
|
212
|
+
end
|
213
|
+
case path
|
214
|
+
# Retrieves id of the path of the account
|
215
|
+
when /account_get_id/
|
216
|
+
account = input['account']
|
217
|
+
dputs(2) { "account_get_id with path #{account}" }
|
218
|
+
a = Accounts.get_id_by_path(account)
|
219
|
+
a and return a
|
220
|
+
dputs(2) { "didn't find anything" }
|
221
|
+
return nil
|
222
|
+
|
223
|
+
when 'movements_put'
|
224
|
+
dputs(3) { "Going to put some movements: #{input['movements'].inspect}" }
|
225
|
+
movs = ActiveSupport::JSON.decode(input['movements'])
|
226
|
+
dputs(3) { "movs is now #{movs.inspect}" }
|
227
|
+
if movs.size > 0
|
228
|
+
movs.each { |m|
|
229
|
+
if mov = Movements.from_json(m)
|
230
|
+
dputs(2) { "Saved movement #{mov.global_id}" }
|
231
|
+
else
|
232
|
+
dputs(0) { "Error: couldn't create movement from #{m.inspect}" }
|
233
|
+
end
|
234
|
+
u.update_movement_index
|
235
|
+
}
|
236
|
+
end
|
237
|
+
when 'movement_delete'
|
238
|
+
dputs(3) { 'Going to delete movement' }
|
239
|
+
while mov = Movements.match_by_global_id(input['global_id'])
|
240
|
+
dputs(3) { "Found movement #{mov.inspect}" }
|
241
|
+
mov.delete
|
242
|
+
end
|
243
|
+
dputs(3) { 'Finished deleting' }
|
244
|
+
when 'account_put'
|
245
|
+
dputs(3) { "Going to put account #{input['account'].inspect}" }
|
246
|
+
acc = Accounts.from_s(input['account'])
|
247
|
+
u.update_account_index
|
248
|
+
dputs(2) { "Saved account #{acc.global_id}" }
|
249
|
+
when 'account_delete'
|
250
|
+
dputs(3) { 'Going to delete account' }
|
251
|
+
acc = Accounts.match_by_global_id(input['global_id'])
|
252
|
+
acc.delete(true)
|
253
|
+
end
|
254
|
+
return 'ok'
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# AfriCompta - handler of a simple accounting-system for "Gestion"
|
2
|
+
#
|
3
|
+
# What follows are some definitions used by other modules
|
4
|
+
|
5
|
+
require 'digest/md5'
|
6
|
+
|
7
|
+
# We want a simple time-print
|
8
|
+
class Time
|
9
|
+
def to_s
|
10
|
+
day.to_s + '/' + month.to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_ss
|
14
|
+
to_s + '/' + year.to_s
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Date
|
19
|
+
def month_s
|
20
|
+
%w(janvier février mars avril mai juin
|
21
|
+
juillet août septembre octobre novembre décembre)[month-1]
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s_eu
|
25
|
+
strftime('%d/%m/%y')
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s_euy
|
29
|
+
strftime('%d/%m/%Y')
|
30
|
+
end
|
31
|
+
|
32
|
+
def Date.from_s(s)
|
33
|
+
# Do some date-magic, so that we can give either the day, day and month or
|
34
|
+
# a complete date. The rest is filled up with todays date.
|
35
|
+
date = []
|
36
|
+
if s.index('/')
|
37
|
+
date = s.split('/')
|
38
|
+
else
|
39
|
+
date = s.split('-').reverse
|
40
|
+
end
|
41
|
+
da = Date.today
|
42
|
+
d = [da.day, da.month, da.year]
|
43
|
+
date += d.last(3 - date.size)
|
44
|
+
if date[2].to_s.size > 2
|
45
|
+
date = Date.strptime(date.join('/'), '%d/%m/%Y')
|
46
|
+
else
|
47
|
+
date = Date.strptime(date.join('/'), '%d/%m/%y')
|
48
|
+
end
|
49
|
+
return date
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class SQLiteAC < SQLite
|
54
|
+
def configure(config)
|
55
|
+
super(config, 'compta', 'compta.db')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Float
|
60
|
+
def round(precision = 0)
|
61
|
+
if precision > 0
|
62
|
+
return (self * 10**precision).round / 10.0**precision
|
63
|
+
else
|
64
|
+
return super()
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
module ACQooxView
|
70
|
+
def self.load_entities(preload = true)
|
71
|
+
require 'africompta/acaccess'
|
72
|
+
Dir[File.dirname(__FILE__) + '/entities/*.rb'].each { |f|
|
73
|
+
dputs(2) { "Adding #{f}" }
|
74
|
+
require(f)
|
75
|
+
}
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
#!/usr/bin/ruby -I../QooxView -wKU
|
2
|
+
|
3
|
+
DEBUG_LVL=5
|
4
|
+
VERSION_AFRICOMPTA='1.9.8'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
AFRICOMPTA_DIR=File.dirname(__FILE__)
|
8
|
+
CONFIG_FILE='config.yaml'
|
9
|
+
if not FileTest.exists? CONFIG_FILE
|
10
|
+
puts "Config-file doesn't exist"
|
11
|
+
print 'Do you want me to copy a standard one? [Y/n] '
|
12
|
+
if gets.chomp.downcase != 'n'
|
13
|
+
FileUtils.cp 'config.yaml.default', 'config.yaml'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
begin
|
18
|
+
require 'qooxview'
|
19
|
+
require 'africompta/acqooxview'
|
20
|
+
rescue Exception => e
|
21
|
+
puts e.inspect
|
22
|
+
puts e.to_s
|
23
|
+
puts e.backtrace
|
24
|
+
|
25
|
+
dputs( 0 ){ "#{e.inspect}" }
|
26
|
+
dputs( 0 ){ "#{e.to_s}" }
|
27
|
+
puts e.backtrace
|
28
|
+
|
29
|
+
puts "Couldn't start AfriCompta - perhaps missing libraries?"
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
|
33
|
+
Welcome.nologin
|
34
|
+
QooxView::init( 'entities', 'views')
|
35
|
+
|
36
|
+
# Autosave every 2 minutes
|
37
|
+
if ConfigBase.autosave == %w(true)
|
38
|
+
$autosave = Thread.new{
|
39
|
+
loop {
|
40
|
+
sleep 2 * 60
|
41
|
+
Entities.save_all
|
42
|
+
}
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
trap('SIGINT') {
|
47
|
+
throw :ctrl_c
|
48
|
+
}
|
49
|
+
|
50
|
+
$profiling = get_config( nil, :profiling )
|
51
|
+
catch :ctrl_c do
|
52
|
+
begin
|
53
|
+
webrick_port = get_config( 3302, :Webrick, :port )
|
54
|
+
dputs(2){"Starting at port #{webrick_port}" }
|
55
|
+
if $profiling
|
56
|
+
require 'rubygems'
|
57
|
+
require 'perftools'
|
58
|
+
PerfTools::CpuProfiler.start("/tmp/#{$profiling}") do
|
59
|
+
QooxView::startWeb webrick_port
|
60
|
+
end
|
61
|
+
else
|
62
|
+
QooxView::startWeb webrick_port
|
63
|
+
end
|
64
|
+
rescue Exception => e
|
65
|
+
dputs( 0 ){ "#{e.inspect}" }
|
66
|
+
dputs( 0 ){ "#{e.to_s}" }
|
67
|
+
puts e.backtrace
|
68
|
+
dputs( 0 ){ 'Saving all' }
|
69
|
+
Entities.save_all
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
if get_config( true, :autosave )
|
74
|
+
$autosave.kill
|
75
|
+
end
|
76
|
+
|
77
|
+
if $profiling
|
78
|
+
puts "Now run the following:
|
79
|
+
pprof.rb --pdf /tmp/#{$profiling} > /tmp/#{$profiling}.pdf
|
80
|
+
open /tmp/#{$profiling}.pdf
|
81
|
+
CPUPROFILE_FREQUENCY=500
|
82
|
+
"
|
83
|
+
end
|