Class: Accounts

Inherits:
Entities
  • Object
show all
Defined in:
lib/africompta/entities/account.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#check_progressObject (readonly)

Returns the value of attribute check_progress.



7
8
9
# File 'lib/africompta/entities/account.rb', line 7

def check_progress
  @check_progress
end

#check_stateObject (readonly)

Returns the value of attribute check_state.



7
8
9
# File 'lib/africompta/entities/account.rb', line 7

def check_state
  @check_state
end

Class Method Details

.archive(month_start = 1, this_year = nil, only_account = nil) ⇒ Object



292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# File 'lib/africompta/entities/account.rb', line 292

def self.archive(month_start = 1, this_year = nil,  = nil)
  if not this_year
    now = Time.now
    this_year = now.year
    now.month < month_start and this_year -= 1
  end

  root = AccountRoot.actual
  if 
    root = 
  elsif not root
    dputs(0) { 'Error: Root-account not available!' }
    return false
  elsif (root. > 0)
    dputs(0) { "Error: Can't archive with Root is not in root: #{root..inspect}!" }
    return false
  end

  archive = AccountRoot.archive
  if not archive
    archive = self.create('Archive')
  end

  years_archived = {}
  archive.accounts.each { |a| years_archived[a.name.to_i] = a }

  dputs(2) { 'Got root and archive' }
  # For every account we search the most-used year, so
  # that we can move the account to that archive. This way
  # we omit as many as possible updates for the clients, as
  # every displacement of a movement will have to be updated,
  # while the displacement of an account is much simpler
  root.get_tree_depth { |acc|
    dputs(2) { "Looking at account #{acc.path}" }
    years = (acc, month_start)

    if years.size > 0
      most_used = last_used = this_year
      if acc.accounts.count == 0
        most_used = years.key(years.values.max)
        last_used = years.keys.max
        years.delete most_used
      end
      acc_path = acc.path

      dputs(3) { "most_used: #{most_used} - last_used: #{last_used}" +
          "- acc_path: #{acc_path}" }

      # First move all other movements around
      if years.keys.size > 0
        create_accounts(acc, years, years_archived, this_year)

        move_movements(acc, years, month_start)
      end

      if most_used != this_year
        # Now move account to archive-year of most movements
        parent = archive_parent(acc, years_archived, most_used)
        if double = Accounts.get_by_path("#{parent.get_path}::#{acc.name}")
          dputs(3) { "Account #{acc.path_id} already exists in #{parent.path_id}" }
          # Move all movements
          acc.movements.each { |m|
            dputs(4) { "Moving movement #{m.to_json}" }
            value = m.value
            m.value = 0
            if m. == acc
              m. = double
            else
              m. = double
            end
            m.value = value
          }
          # Delete acc
          acc.delete
        else
          dputs(3) { "Moving account #{acc.path_id} to #{parent.path_id}" }
          acc.parent = parent
        end
      end

      # Check whether we need to add the account to the current year
      if (last_used >= this_year - 1) and
          (most_used != this_year)
        dputs(3) { "Adding #{acc_path} to this year with mult #{acc.multiplier}" }
        Accounts.create_path(acc_path, 'Copied from archive', false,
                             acc.multiplier, acc.keep_total)
      end

      if acc.keep_total
        dputs(2) { "Keeping total for #{acc_path}" }
        # And create a trail so that every year contains the previous
        # years worth of "total"
        sum_up_total(acc_path, years_archived, month_start)
        dputs(5) { "acc_path is now #{acc_path}" }
      else
        dputs(2) { "Not keeping for #{acc_path}" }
      end
    else
      dputs(3) { "Empty account #{acc.movements.count} - #{acc.accounts.count}" }
    end

    if acc.accounts.count == 0
      movs = acc.movements
      case movs.count
        when 0
          dputs(3) { "Deleting empty account #{acc.path}" }
          if acc.path != 'Root'
            acc.delete
          else
            dputs(2) { 'Not deleting root!' }
          end
        when 1
          dputs(3) { "Found only one movement for #{acc.path}" }
          if movs.first.desc =~ /^-- Sum of/
            dputs(3) { 'Deleting account which has only a sum' }
            movs.first.delete
            if acc.path != 'Root'
              acc.delete
            else
              dputs(2) { 'Not deleting root!' }
            end
          end
      end
    end
  }
  if DEBUG_LVL >= 3
    self.dump
  end
end

.create(name, desc = 'Too lazy', parent = nil, global_id = '', mult = nil) ⇒ Object



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/africompta/entities/account.rb', line 26

def self.create(name, desc = 'Too lazy', parent = nil, global_id = '', mult = nil)
  dputs(5) { "Parent is #{parent.inspect}" }
  if parent
    if parent.class != Account && parent != AccountRoot
      parent = Accounts.matches_by_id(parent).first
    end
    mult ||= parent.multiplier
    a = super(:name => name, :desc => desc, :account_id => parent.id,
              :global_id => global_id.to_s, :multiplier => mult,
              :deleted => false, :keep_total => parent.keep_total)
  else
    mult ||= 1
    a = super(:name => name, :desc => desc, :account_id => 0,
              :global_id => global_id.to_s, :multiplier => mult,
              :deleted => false, :keep_total => false)
  end
  a.total = 0
  if global_id == ''
    a.global_id = Users.match_by_name('local').full + '-' + a.id.to_s
  end
  a.new_index
  dputs(2) { "Created account #{a.path_id} - #{a.inspect}" }
  a
end

.create_path(path, desc = '', double_last = false, mult = 1, keep_total = false) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/africompta/entities/account.rb', line 51

def self.create_path(path, desc = '', double_last = false, mult = 1,
    keep_total = false)
  dputs(3) { "Path: #{path.inspect}, mult: #{mult}" }
  elements = path.split('::')
  parent = AccountRoot
  while elements.size > 0
    name = elements.shift
    dputs(4) { "Working on element #{name} with base of #{parent.path_id}" }
    child = parent.accounts.find { |a|
      dputs(5) { "Searching child #{a.name} - #{a.path_id}" }
      a.name == name
    }
    child and dputs(4) { "Found existing child #{child.path_id}" }
    if (not child) or (elements.size == 0 and double_last)
      dputs(4) { "Creating child #{name}" }
      child = Accounts.create(name, desc, parent)
    end
    parent = child
  end
  parent.set_nochildmult(name, desc, nil, mult, [], keep_total)
end

.dump(mov = false) ⇒ Object



428
429
430
431
432
433
434
435
436
437
438
439
440
# File 'lib/africompta/entities/account.rb', line 428

def self.dump(mov = false)
  dputs(1) { 'Root-tree is now' }
  AccountRoot.actual.dump_rec(mov)
  if archive = AccountRoot.archive
    dputs(1) { 'Archive-tree is now' }
    archive.dump_rec(mov)
  else
    dputs(1) { 'No archive-tree' }
  end
  AccountRoot.accounts.each { |a|
    dputs(1) { "Root-Account: #{a.inspect}" }
  }
end

.dump_raw(mov = false) ⇒ Object



422
423
424
425
426
# File 'lib/africompta/entities/account.rb', line 422

def self.dump_raw(mov = false)
  Accounts.search_all.each { |a|
    a.dump(mov)
  }
end

.find_by_path(parent) ⇒ Object



145
146
147
# File 'lib/africompta/entities/account.rb', line 145

def self.find_by_path(parent)
  return Accounts.get_by_path(parent)
end

.from_s(str) ⇒ Object

Gets an account from a string, if it doesn’t exist yet, creates it. It will update it anyway.



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/africompta/entities/account.rb', line 75

def self.from_s(str)
  str.force_encoding(Encoding::UTF_8)
  desc, str = str.split("\r")
  if not str
    dputs(0) { "Error: Invalid account found: #{desc}" }
    return [-1, nil]
  end
  global_id, total, name, multiplier, par,
      deleted_s, keep_total_s = str.split("\t")
  total, multiplier = total.to_f, multiplier.to_f
  deleted = deleted_s == 'true'
  keep_total = keep_total_s == 'true'
  dputs(3) { [global_id, total, name, multiplier].inspect }
  dputs(3) { [par, deleted_s, keep_total_s].inspect }
  dputs(5) { "deleted, keep_total is #{deleted.inspect}, #{keep_total.inspect}" }
  dputs(3) { 'Here comes the account: ' + global_id.to_s }
  dputs(3) { "global_id: #{global_id}" }

  if par.to_s.length > 0
    parent = Accounts.match_by_global_id(par)
    parent_id = parent.id
    dputs(3) { "Parent is #{parent.inspect}" }
  else
    parent = nil
    parent_id = 0
  end

  # Does the account already exist?
  our_a = nil
  if not (our_a = Accounts.match_by_global_id(global_id))
    # Create it
    dputs(3) { "Creating account #{name} - #{desc} - #{parent} - #{global_id}" }
    our_a = Accounts.create(name, desc, parent, global_id)
  end
  # And update it
  our_a.deleted = deleted
  our_a.set_nochildmult(name, desc, parent_id, multiplier, [], keep_total)
  our_a.global_id = global_id
  dputs(2) { "Saved account #{name} with index #{our_a.rev_index} and global_id #{our_a.global_id}" }
  dputs(4) { "Account is now #{our_a.inspect}" }
  return our_a
end

.get_by_path(parent, elements = nil) ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/africompta/entities/account.rb', line 123

def self.get_by_path(parent, elements = nil)
  if not elements
    if parent
      return get_by_path(AccountRoot, parent.split('::'))
    else
      return nil
    end
  end

  child = elements.shift
  parent.accounts.each { |a|
    if a.name == child
      if elements.length > 0
        return get_by_path(a, elements)
      else
        return a
      end
    end
  }
  return nil
end

.get_by_path_or_create(p, desc = '', last = false, mult = 1, keep = false) ⇒ Object



118
119
120
121
# File 'lib/africompta/entities/account.rb', line 118

def self.get_by_path_or_create(p, desc = '', last = false, mult = 1, keep = false)
  get_by_path(p) or
      create_path(p, desc, last, mult, keep)
end

.get_id_by_path(p) ⇒ Object



149
150
151
152
153
154
155
# File 'lib/africompta/entities/account.rb', line 149

def self.get_id_by_path(p)
  if a = get_by_path(p)
    return a.id.to_s
  else
    return nil
  end
end

Instance Method Details

#archive_parent(acc, years_archived, year) ⇒ Object



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/africompta/entities/account.rb', line 157

def archive_parent(acc, years_archived, year)
  dputs(3) { "years_archived is #{years_archived.inspect}" }
  if not years_archived.has_key? year
    dputs(2) { "Adding #{year}" }
    years_archived[year] =
        Accounts.create_path("Archive::#{year}", 'New archive')
  end
  # This means we're more than one level below root, so we can't
  # just copy easily
  if acc.path.split('::').count > 2
    dputs(3) { "Creating archive #{acc.path} with mult #{acc.multiplier}" }
    return Accounts.create_path("Archive::#{year}::"+
                                    "#{acc.parent.path.gsub(/^Root::/, '')}", 'New archive', false,
                                acc.multiplier, acc.keep_total)
  else
    return years_archived[year]
  end
end

#bool_to_s(b) ⇒ Object



496
497
498
# File 'lib/africompta/entities/account.rb', line 496

def bool_to_s(b)
  (b && b != 'f') ? 'true' : 'false'
end

#check_against_db(file) ⇒ Object



500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
# File 'lib/africompta/entities/account.rb', line 500

def check_against_db(file)
  # First build
  # in_db - content of 'file' in .to_s format
  # in_local - content available locally in .to_s format
  in_db, diff, in_local = [], [], []
  dputs(3) { 'Searching all accounts' }
  @check_state = 'Collect local'
  @check_progress = 0.0
  in_local = Accounts.search_all_
  progress_step = 1.0 / (in_local.size + 1)
  dputs(3) { "Found #{in_local.size} accounts" }

  @check_state = 'Collect local'
  in_local = in_local.collect { |a|
    @check_progress += progress_step
    a.to_s
  }

  dputs(3) { 'Loading file-db' }
  @check_state = 'Collect file-DB'
  @check_progress = 0.0
  SQLite3::Database.new(file) do |db|
    db.execute('select id, account_id, name, desc, global_id, total, '+
                   'multiplier, "index", rev_index, deleted, keep_total '+
                   'from compta_accounts').sort_by { |a| a[4] }.each do |row|
      #dputs(3) { "Looking at #{row}" }
      @check_progress += progress_step

      _, acc_id_, name_, desc_, gid_, tot_, mult_, _, _, del_, keep_, = row
      parent = if acc_id_
                 acc_id_ == 0 ? '' :
                     db.execute("select * from compta_accounts where id=#{acc_id_}").first[4]
               else
                 ''
               end
      in_db.push "#{desc_}\r#{gid_}\t" +
                     "#{sprintf('%.3f', tot_.to_f.round(3))}\t#{name_.to_s}\t"+
                     "#{mult_.to_i.to_s}\t#{parent}" +
                     "\t#{bool_to_s(del_)}" + "\t#{bool_to_s(keep_)}"
    end
  end

  # Now compare what is available only in db and what is available only locally
  dputs(3) { 'Comparing local accounts with file-db accounts' }
  @check_state = 'On one side'
  @check_progress = 0.0
  in_db.delete_if { |a|
    @check_progress += progress_step
    in_local.delete(a)
  }

  # And search for accounts with same global-id but different content
  dputs(3) { 'Seaching mix-ups' }
  @check_state = 'Mixed-up'
  @check_progress = 0.0
  progress_step = 1.0 / (in_db.size + 1)
  (in_db + in_local).sort_by { |a| a.match(/\r(.*?)\t/)[1] }
  in_db.delete_if { |a|
    @check_progress += progress_step
    gid = a.match(/\r(.*?)\t/)[1]
    if c = in_local.find { |b| b =~ /\r#{gid}\t/ }
      diff.push [a, c]
      in_local.delete c
    end
  }

  @check_state = 'Done'
  [in_db, diff, in_local]
end

#create_accounts(acc, years, years_archived, this_year) ⇒ Object



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/africompta/entities/account.rb', line 190

def create_accounts(acc, years, years_archived, this_year)
  years.keys.each { |y|
    if y == this_year
      dputs(3) { "Creating path #{acc.path} with mult #{acc.multiplier}" }
      years[y] = Accounts.create_path(acc.path, acc.desc,
                                      true, acc.multiplier, acc.keep_total)
    else
      path = "#{archive_parent(acc, years_archived, y).path}::" +
          acc.name
      dputs(3) { "Creating other path #{path} with mult #{acc.multiplier}" }
      years[y] = Accounts.create_path(path, acc.desc, false,
                                      acc.multiplier, acc.keep_total)
    end
    dputs(3) { "years[y] is #{years[y].path_id}" }
  }
end

#initObject



442
443
444
445
446
447
448
449
450
451
452
453
# File 'lib/africompta/entities/account.rb', line 442

def init
  root = Accounts.create('Root', 'Initialisation')
  %w( Income Outcome Lending Cash ).each { |a|
    Accounts.create(a, 'Initialisation', root)
  }
  %w( Lending Cash ).each { |a|
    acc = Accounts.match_by_name(a)
    acc.multiplier = -1
    acc.keep_total = true
  }
  Accounts.save
end

#listp_pathObject



488
489
490
491
492
493
494
# File 'lib/africompta/entities/account.rb', line 488

def listp_path
  dputs(3) { 'Being called' }
  Accounts.search_all.select { |a| !a.deleted }.collect { |a| [a.id, a.path] }.
      sort { |a, b|
    a[1] <=> b[1]
  }
end

#loadObject



455
456
457
458
459
460
461
# File 'lib/africompta/entities/account.rb', line 455

def load
  super
  if Accounts.search_by_name('Root').count == 0
    dputs(1) { "Didn't find 'Root' in database - creating base" }
    Accounts.init
  end
end

#migration_1(a) ⇒ Object



463
464
465
466
467
468
469
470
# File 'lib/africompta/entities/account.rb', line 463

def migration_1(a)
  dputs(4) { Accounts.storage[:SQLiteAC].db_class.inspect }
  a.deleted = false
  # As most of the accounts in Cash have -1 and shall be kept, this
  # gives a good first initialisation
  a.keep_total = (a.multiplier == -1.0) || (a.multiplier == -1)
  dputs(4) { "#{a.name}: #{a.deleted.inspect} - #{a.keep_total.inspect}" }
end

#migration_2(a) ⇒ Object



472
473
474
# File 'lib/africompta/entities/account.rb', line 472

def migration_2(a)
  a.rev_index = a.id
end

#migration_3(m) ⇒ Object



476
477
478
479
480
481
482
483
484
485
486
# File 'lib/africompta/entities/account.rb', line 476

def migration_3(m)
  # dp "Migrating #{m.inspect}"
  if m.id == 1 && m. != nil
    dp 'Root-account has parent...'
    m. = 0
    m.name = 'Root'
    m.desc = 'Root'
    m.global_id = Digest::MD5.hexdigest((rand 2**128).to_s).to_s + '-1'
    m.total = 0.0
  end
end

#move_movements(acc, years, month_start) ⇒ Object



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/africompta/entities/account.rb', line 207

def move_movements(acc, years, month_start)
  acc.movements.each { |mov|
    dputs(5) { 'Start of each' }
    y, m, _ = mov.date.to_s.split('-').collect { |d| d.to_i }
    dputs(5) { "Date of #{mov.desc} is #{mov.date}" }
    m < month_start and y -= 1
    if years.has_key? y
      value = mov.value
      mov.value = 0
      dputs(5) { "Moving to #{years[y].inspect}: " +
          "#{mov..id} - #{mov..id} - #{acc.id}" }
      if mov..id == acc.id
        dputs(5) { 'Moving src' }
        mov. = years[y]
      else
        dputs(5) { 'Moving dst' }
        mov. = years[y]
      end
      mov.value = value
    end
  }
  dputs(5) { "Movements left in account #{acc.path}:" }
  acc.movements.each { |m|
    dputs(5) { m.desc }
  }
end

#search_account(acc, month_start) ⇒ Object



176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/africompta/entities/account.rb', line 176

def (acc, month_start)
  years = Hash.new(0)
  acc.movements.each { |mov|
    if not mov.desc =~ /^-- Sum of/
      y, m, _ = mov.date.to_s.split('-').collect { |d| d.to_i }
      dputs(5) { "Date of #{mov.desc} is #{mov.date}" }
      m < month_start and y -= 1
      years[y] += 1
    end
  }
  dputs(3) { "years is #{years.inspect}" }
  years
end

#setup_dataObject



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/africompta/entities/account.rb', line 9

def setup_data
  @default_type = :SQLiteAC
  @data_field_id = :id
  value_int :index

  value_str :name
  value_str :desc
  value_str :global_id
  value_float :total
  value_int :multiplier
  value_int :rev_index
  value_bool :deleted
  value_bool :keep_total
  # This is the ID of the parent account
  value_int :account_id
end

#sum_up_total(acc_path, years_archived, month_start) ⇒ Object



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/africompta/entities/account.rb', line 234

def sum_up_total(acc_path, years_archived, month_start)
  a_path = acc_path.sub(/[^:]*::/, '')
  dputs(2) { "Summing up account #{a_path}" }
  acc_sum = []
  years_archived.each { |y, a|
    dputs(5) { "Found archived year #{y.inspect} which is #{y.class.name}" }
    aacc = Accounts.get_by_path(a.get_path + '::' + a_path)
    acc_sum.push [y, a, aacc]
  }
  dputs(5) { 'Trying to add current year' }
  if curr_acc = Accounts.get_by_path(acc_path)
    dputs(4) { 'Adding current year' }
    acc_sum.push [9999, nil, curr_acc]
  end

  last_total = 0
  last_year_acc = nil
  last_year_acc_parent = nil
  last_year = 0
  dputs(5) { "Sorting account_sums #{acc_sum.length}" }
  acc_sum.sort { |a, b| a[0] <=> b[0] }.each { |y, a, aacc|
    dputs(5) { "y, a, aacc: #{y}, #{a.to_json}, #{aacc.to_json}" }
    if aacc
      last_year_acc_parent and last_year_acc_parent.dump true
      dputs(4) { "Found archived account #{aacc.get_path} for year #{y}" +
          " with last_total #{last_total}" }
      dputs(5) { 'And has movements' }
      aacc.movements.each { |m|
        dputs(5) { m.to_json }
      }
      if last_total != 0
        desc = "-- Sum of #{last_year} of #{last_year_acc.path}"
        date = "#{last_year + 1}-#{month_start.to_s.rjust(2, '0')}-01"
        dputs(3) { "Deleting old sums with date #{date.inspect}" }
        Movements.matches_by_desc("^#{desc}$").each { |m|
          dputs(3) { "Testing movement with date #{m.date.to_s.inspect}: #{m.to_json}" }
          if m.date.to_s == date.to_s
            dputs(3) { 'Deleting it' }
            m.delete
          end
        }
        dputs(3) { "Creating movement for the sum of last year: #{last_total}" }
        mov = Movements.create(desc, date, last_total,
                               last_year_acc_parent, aacc)
        last_year_acc_parent.dump true
        dputs(3) { "Movement is: #{mov.to_json}" }
      end
      aacc.update_total
      dputs(5) { "#{aacc.total} - #{aacc.multiplier}" }
      last_total = aacc.total * aacc.multiplier
    else
      dputs(4) { "Didn't find archived account for #{y}" }
      last_total = 0
    end
    last_year, last_year_acc, last_year_acc_parent = y, aacc, a
  }
end