snappler_contable_multicurrency 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +398 -0
  4. data/Rakefile +39 -0
  5. data/app/helpers/snappler_contable_helper.rb +42 -0
  6. data/app/models/ledger_account.rb +217 -0
  7. data/app/models/ledger_account_activo.rb +8 -0
  8. data/app/models/ledger_account_pasivo.rb +6 -0
  9. data/app/models/ledger_account_patrimonio_neto.rb +6 -0
  10. data/app/models/ledger_account_relation.rb +8 -0
  11. data/app/models/ledger_account_resultado_negativo.rb +6 -0
  12. data/app/models/ledger_account_resultado_positivo.rb +6 -0
  13. data/app/models/ledger_currency.rb +5 -0
  14. data/app/models/ledger_entry.rb +7 -0
  15. data/app/models/ledger_move.rb +76 -0
  16. data/lib/generators/snappler_contable/initializer_generator.rb +11 -0
  17. data/lib/generators/snappler_contable/migrate_generator.rb +25 -0
  18. data/lib/generators/snappler_contable/new_currency_generator.rb +23 -0
  19. data/lib/generators/snappler_contable/templates/snappler_contable_add_currencies.rb +20 -0
  20. data/lib/generators/snappler_contable/templates/snappler_contable_app_ledger_accounts.rb +48 -0
  21. data/lib/generators/snappler_contable/templates/snappler_contable_migrate.rb +68 -0
  22. data/lib/generators/snappler_contable/templates/snappler_contable_new_currency.rb +14 -0
  23. data/lib/snappler_contable/ext/string.rb +5 -0
  24. data/lib/snappler_contable/railtie.rb +4 -0
  25. data/lib/snappler_contable/snappler_contable.rb +154 -0
  26. data/lib/snappler_contable/version.rb +3 -0
  27. data/lib/snappler_contable_multicurrency.rb +14 -0
  28. data/lib/tasks/snappler_contable_tasks.rake +4 -0
  29. data/spec/internal/config/database_example.yml +8 -0
  30. data/spec/internal/config/initializers/snappler_contable.rb +8 -0
  31. data/spec/internal/config/routes.rb +3 -0
  32. data/spec/internal/db/schema.rb +64 -0
  33. data/spec/internal/public/favicon.ico +0 -0
  34. data/spec/models/ledger_account_spec.rb +308 -0
  35. data/spec/spec_helper.rb +75 -0
  36. metadata +84 -0
@@ -0,0 +1,42 @@
1
+ module SnapplerContableHelper
2
+
3
+ #---------------------------------------------------------------
4
+ def print_accounts_tree
5
+ accounts = SnapplerContable.root_accounts
6
+ html_return = '<style>#account_list b{float: right;}#account_list li{border-bottom: 1px dashed;}#account_list ul{padding-left: 15px;}#account_list b .column{display: inline-block;text-align: center;width: 150px;}</style>'
7
+ html_return += '<div id="account_list">'
8
+ html_return += print_accounts(accounts)
9
+ html_return += '</div>'
10
+ html_return.html_safe
11
+ end
12
+
13
+ def print_accounts(accounts)
14
+ html_return = "<ul>"
15
+ accounts.each do |account|
16
+ html_return += print_account(account)
17
+ end
18
+ html_return += "</ul>"
19
+ return html_return
20
+ end
21
+
22
+ def print_account(account)
23
+ if(account.child_ledger_accounts.empty?)
24
+ return "<li>#{account.name} <b>#{number_to_all_currency account.balance}</b></li>"
25
+ else
26
+ return %{<li>#{account.name} <b>#{number_to_all_currency account.balance}</b></li>
27
+ <ul>
28
+ #{print_accounts(account.child_ledger_accounts)}
29
+ </ul>
30
+ }
31
+ end
32
+ end
33
+
34
+ def number_to_all_currency(hash, reject_zero=false)
35
+ hash.reject!{|x,y| y.zero?} if reject_zero
36
+ hash.empty? ? '-' : hash.map{|key,val| number_to_currency(val, unit: t(key, scope: 'ledger_currency'), format: "%u %n") }.collect{|x| "<span class='column'>#{x}</span>" }.join()
37
+ end
38
+
39
+
40
+
41
+ end
42
+
@@ -0,0 +1,217 @@
1
+ class LedgerAccount < ActiveRecord::Base
2
+
3
+
4
+
5
+ #--------------------------------------------- RELATIONS
6
+ belongs_to :owner, polymorphic: true, optional: true
7
+ belongs_to :master_ledger_account, class_name: "LedgerAccount", optional: true
8
+ has_many :child_ledger_accounts, class_name: "LedgerAccount", foreign_key: "master_ledger_account_id"
9
+ has_many :ledger_moves, dependent: :destroy
10
+
11
+ has_many :father_relations, class_name: "LedgerAccountRelation", foreign_key: :ledger_account_child_id, dependent: :destroy
12
+ has_many :fathers, through: :father_relations, source: :ledger_account_father
13
+ has_many :child_relations, class_name: "LedgerAccountRelation", foreign_key: :ledger_account_father_id
14
+ has_many :childs, through: :child_relations, source: :ledger_account_child
15
+
16
+ #--------------------------------------------- VALIDATIONS
17
+ validates :name, presence: true
18
+ validates :code_name, uniqueness: true
19
+
20
+ #--------------------------------------------- CALLBACKS
21
+ after_create :set_ledger_account_relations
22
+
23
+ #--------------------------------------------- SCOPES
24
+
25
+
26
+ #--------------------------------------------- METHODS
27
+
28
+ #devuelve la cuenta (de clase)
29
+ def self.get(value)
30
+ find_by(code_name: value.to_s.snp_underscore)
31
+ end
32
+
33
+ #--------------------
34
+ #Crea las relaciones de la cuenta con sus padres, todos los niveles
35
+ #se va mandando recursivamente la cuenta creada a sus padres
36
+ def set_ledger_account_relations
37
+ if master_ledger_account.present?
38
+ master_ledger_account.add_relation_child(self)
39
+ end
40
+ end
41
+
42
+ def add_relation_child(account)
43
+ self.childs << account
44
+ if master_ledger_account.present?
45
+ master_ledger_account.add_relation_child(account)
46
+ end
47
+ end
48
+ #-----------------------------------
49
+
50
+ #-----------------------------------
51
+ #Agrega una cuenta hija, con o sin relacion con un objeto
52
+ def add_child(name, code_name, owner=nil)
53
+ if persisted?
54
+ if owner.nil?
55
+ self.class.create(name: name, code_name: code_name, master_ledger_account: self)
56
+ else
57
+ self.class.create(name: name, master_ledger_account: self, owner: owner, code_name: "#{code_name}_#{owner.class.name.underscore}_#{owner.id}")
58
+ end
59
+ else
60
+ errores = ["La instancia debe estar persistida para poder agregar una cuenta hija."]
61
+ errores << "La cuenta '#{name}' no se pudo persistir porque sus campos no cumplen la validacion de LedgerAccount." if errors.any?
62
+ raise errores.join(' ')
63
+ end
64
+ end
65
+ #-----------------------------------
66
+
67
+ def to_s
68
+ name
69
+ end
70
+
71
+ def name
72
+ internal_name
73
+ end
74
+
75
+ def name=(value)
76
+ self.internal_name = value
77
+ self.code_name = value.snp_underscore if code_name.blank?
78
+ end
79
+
80
+ def root?
81
+ master_ledger_account.nil?
82
+ end
83
+
84
+ def has_ledger_moves?
85
+ ledger_moves.any?
86
+ end
87
+
88
+
89
+ def balance(params={}, format_value=true)
90
+ from_date = params[:from]
91
+ to_date = params[:to]
92
+ balance_hash = {}
93
+
94
+ if from_date.nil? && to_date.nil?
95
+ SnapplerContable.used_currencies.each do |c|
96
+ balance_hash[c.to_sym] = self.send("normal_balance_#{c.downcase}", format_value)
97
+ end
98
+ elsif from_date.present? && to_date.present?
99
+ SnapplerContable.used_currencies.each do |c|
100
+ balance_hash[c.to_sym] = self.send("balance_from_to_#{c.downcase}", format_value, from_date, to_date)
101
+ end
102
+ elsif from_date.present?
103
+ SnapplerContable.used_currencies.each do |c|
104
+ balance_hash[c.to_sym] = self.send("balance_from_#{c.downcase}", format_value, from_date)
105
+ end
106
+ else
107
+ SnapplerContable.used_currencies.each do |c|
108
+ balance_hash[c.to_sym] = self.send("balance_to_#{c.downcase}", format_value, to_date)
109
+ end
110
+ end
111
+ return balance_hash
112
+ end
113
+
114
+
115
+ SnapplerContable.used_currencies.each do |c|
116
+ c = c.downcase # ARS => ars
117
+
118
+ #Metodo que en base a los parametros, busca saldo en cache o por rango de fecha
119
+ define_method "balance_#{c}" do |params={}, format_value=true|
120
+ from_date = params[:from]
121
+ to_date = params[:to]
122
+ if from_date.nil? && to_date.nil?
123
+ self.send("normal_balance_#{c}", format_value)
124
+ elsif from_date.present? && to_date.present?
125
+ self.send("balance_from_to_#{c}", format_value, from_date, to_date)
126
+ elsif from_date.present?
127
+ self.send("balance_from_#{c}", format_value, from_date)
128
+ else
129
+ self.send("balance_to#{c}", format_value, to_date)
130
+ end
131
+ end
132
+
133
+ #Saldo en cache
134
+ define_method "normal_balance_#{c}" do |format_value|
135
+ balance_sum_currency = "balance_sum_#{c}".to_sym
136
+ val = LedgerAccount.where(id: child_ids+[self.id]).sum(balance_sum_currency)
137
+ (format_value)? LedgerMove.format_value(val) : val
138
+ end
139
+
140
+ #Saldo hasta
141
+ define_method "balance_to_#{c}" do |format_value, to_date|
142
+ ledger_currency = get_currency(c.upcase)
143
+ #process(children_moves.where('date <= ? AND represent_ledger_currency_id = ?', to_date, ledger_currency.id).group(:dh).sum(:represent_value), format_value)
144
+ process(children_moves.filter_date_end(to_date).where('represent_ledger_currency_id = ?', ledger_currency.id).group(:dh).sum(:represent_value), format_value)
145
+ end
146
+
147
+ #Saldo desde
148
+ define_method "balance_from_#{c}" do |format_value, from_date|
149
+ ledger_currency = get_currency(c.upcase)
150
+ #process(children_moves.where('date >= ? AND represent_ledger_currency_id = ?', from_date, ledger_currency.id).group(:dh).sum(:represent_value), format_value)
151
+ process(children_moves.filter_date_start(from_date).where('represent_ledger_currency_id = ?', ledger_currency.id).group(:dh).sum(:represent_value), format_value)
152
+ end
153
+
154
+ #Saldo desde - hasta
155
+ define_method "balance_from_to_#{c}" do |format_value, from_date, to_date|
156
+ ledger_currency = get_currency(c.upcase)
157
+ process(children_moves.filter_date_start(from_date).filter_date_end(to_date).where('represent_ledger_currency_id = ?', ledger_currency.id).group(:dh).sum(:represent_value), format_value)
158
+ end
159
+
160
+ end
161
+
162
+
163
+
164
+
165
+ def process(dh_hash, format_value)
166
+ debe = dh_hash["D"].to_i
167
+ haber = dh_hash["H"].to_i
168
+ format_value ? LedgerMove.format_value(process_balance(debe, haber)) : process_balance(debe, haber)
169
+ end
170
+
171
+
172
+ def get_currency(c)
173
+ if c.nil?
174
+ c = SnapplerContable.default_currency
175
+ else
176
+ if c.is_a? Numeric
177
+ c = LedgerCurrency.find(c)
178
+ elsif c.is_a? String
179
+ c = LedgerCurrency.find_by_code(c)
180
+ end
181
+ end
182
+ return c
183
+ end
184
+
185
+
186
+ def children_moves
187
+ LedgerMove.where(ledger_account_id: child_ids+[self.id])
188
+ end
189
+
190
+ def process_balance
191
+ raise "Este metodo solo se tiene que implementar en las subclases"
192
+ end
193
+
194
+
195
+ def update_balance(value, dh, ledger_currency_id)
196
+ balance_update = case dh.upcase
197
+ when 'D' then process_balance(value, 0)
198
+ when 'H' then process_balance(0, value)
199
+ end
200
+
201
+ lc = LedgerCurrency.find(ledger_currency_id)
202
+
203
+
204
+
205
+ currency = lc.code.downcase
206
+ self.send("balance_sum_#{currency}=", (self.send("balance_sum_#{currency}") + balance_update))
207
+ save
208
+ end
209
+
210
+ def update_balance_destroy(value, dh, ledger_currency_id)
211
+ #inverse operation
212
+ ops = {'D' => 'H', 'H' => 'D'}
213
+ update_balance(value, ops[dh], ledger_currency_id)
214
+ end
215
+
216
+
217
+ end
@@ -0,0 +1,8 @@
1
+ class LedgerAccountActivo < LedgerAccount
2
+
3
+ def process_balance(debe, haber)
4
+ debe - haber
5
+ end
6
+ end
7
+
8
+
@@ -0,0 +1,6 @@
1
+ class LedgerAccountPasivo < LedgerAccount
2
+
3
+ def process_balance(debe, haber)
4
+ haber - debe
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ class LedgerAccountPatrimonioNeto < LedgerAccount
2
+
3
+ def process_balance(debe, haber)
4
+ haber - debe
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ class LedgerAccountRelation < ActiveRecord::Base
2
+
3
+ #--------------------------------------------- RELATIONS
4
+ belongs_to :ledger_account_father, class_name: "LedgerAccount"
5
+ belongs_to :ledger_account_child, class_name: "LedgerAccount"
6
+
7
+
8
+ end
@@ -0,0 +1,6 @@
1
+ class LedgerAccountResultadoNegativo < LedgerAccount
2
+
3
+ def process_balance(debe, haber)
4
+ debe - haber
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ class LedgerAccountResultadoPositivo < LedgerAccount
2
+
3
+ def process_balance(debe, haber)
4
+ haber - debe
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class LedgerCurrency < ActiveRecord::Base
2
+ def to_s
3
+ code
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ class LedgerEntry < ActiveRecord::Base
2
+
3
+ #--------------------------------------------- RELATIONS
4
+ belongs_to :owner, polymorphic: true, optional: true
5
+ has_many :ledger_moves, dependent: :destroy
6
+
7
+ end
@@ -0,0 +1,76 @@
1
+ class LedgerMove < ActiveRecord::Base
2
+
3
+ #--------------------------------------------- RELATIONS
4
+ belongs_to :ledger_entry
5
+ belongs_to :ledger_account
6
+ belongs_to :real_ledger_currency, class_name: 'LedgerCurrency'
7
+ belongs_to :represent_ledger_currency, class_name: 'LedgerCurrency'
8
+
9
+ #--------------------------------------------- CALLBACKS
10
+ after_create :update_balance_create
11
+ after_destroy :update_balance_destroy
12
+ after_update :update_balance_update
13
+
14
+ #--------------------------------------------- SCOPES
15
+ scope :filter_date_start, ->(date) { where('ledger_moves.datetime >= :datetime', datetime: Date.parse(date).beginning_of_day.to_datetime) if date.present? }
16
+ scope :filter_date_end, ->(date) { where('ledger_moves.datetime <= :datetime', datetime: Date.parse(date).end_of_day.to_datetime) if date.present? }
17
+ scope :order_date_asc, -> { order('ledger_moves.datetime ASC, ledger_moves.id ASC')}
18
+
19
+
20
+
21
+ #--------------------------------------------- METHODS
22
+ def update_balance_create
23
+ self.ledger_account.update_balance(raw_represent_value, dh, represent_ledger_currency_id)
24
+ end
25
+
26
+ def update_balance_destroy
27
+ self.ledger_account.update_balance_destroy(raw_represent_value, dh, represent_ledger_currency_id)
28
+ end
29
+
30
+ def update_balance_update
31
+ self.ledger_account.update_balance_destroy(represent_value_was, dh, represent_ledger_currency_id_was)
32
+ self.ledger_account.update_balance(raw_represent_value, dh, represent_ledger_currency_id)
33
+ end
34
+
35
+
36
+ def self.format_value(value)
37
+ value / SnapplerContable.default_divisor
38
+ end
39
+
40
+ def self.unformat_value(value)
41
+ (value * SnapplerContable.default_divisor).round.to_i
42
+ end
43
+
44
+
45
+
46
+ #Defino metodos para los 2 valores ("real_value", "represent_value")
47
+ ['real_value', 'represent_value'].each do |the_method|
48
+
49
+ #Getter
50
+ define_method "#{the_method}" do
51
+ value_formated = read_attribute the_method.to_sym
52
+ self.class.format_value(value_formated)
53
+ end
54
+
55
+ #Setter
56
+ define_method "#{the_method}=" do |val|
57
+ if val.is_a? Numeric
58
+ write_attribute the_method.to_sym, self.class.unformat_value(val)
59
+ else
60
+ raise "El valor debe ser un numero"
61
+ end
62
+ end
63
+
64
+ #Crudo Getter
65
+ define_method "raw_#{the_method}" do
66
+ return read_attribute the_method.to_sym
67
+ end
68
+
69
+ end
70
+
71
+
72
+
73
+
74
+
75
+ end
76
+
@@ -0,0 +1,11 @@
1
+ require 'rails/generators'
2
+
3
+ module SnapplerContable
4
+ class InitializerGenerator < ::Rails::Generators::Base
5
+ def create_initializer_file
6
+ create_file "config/initializers/snappler_contable.rb", "# Monedas usadas\nSnapplerContable.used_currencies = ['ARS','USD','EUR','BRL'] \#'JPY'\n\n# Moneda por defecto\nSnapplerContable.default_currency = 'ARS'\n\n# Divisor por defecto (2 decimales)\nSnapplerContable.default_divisor = 100.0"
7
+ end
8
+ end
9
+ end
10
+
11
+
@@ -0,0 +1,25 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module SnapplerContable
5
+ class MigrateGenerator < ::Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+ source_root File.expand_path('../templates', __FILE__)
8
+
9
+ desc "add snappler_contable migration"
10
+ def self.next_migration_number(path)
11
+ unless @prev_migration_nr
12
+ @prev_migration_nr = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
13
+ else
14
+ @prev_migration_nr += 1
15
+ end
16
+ @prev_migration_nr.to_s
17
+ end
18
+
19
+ def copy_migrations
20
+ migration_template "snappler_contable_migrate.rb", "db/migrate/snappler_contable_migrate.rb"
21
+ migration_template "snappler_contable_add_currencies.rb", "db/migrate/snappler_contable_add_currencies.rb"
22
+ migration_template "snappler_contable_app_ledger_accounts.rb", "db/migrate/snappler_contable_app_ledger_accounts.rb"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module SnapplerContable
5
+ class NewCurrencyGenerator < ::Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+ source_root File.expand_path('../templates', __FILE__)
8
+
9
+ desc "add snappler_contable migration"
10
+ def self.next_migration_number(path)
11
+ unless @prev_migration_nr
12
+ @prev_migration_nr = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
13
+ else
14
+ @prev_migration_nr += 1
15
+ end
16
+ @prev_migration_nr.to_s
17
+ end
18
+
19
+ def copy_migrations
20
+ migration_template "snappler_contable_new_currency.rb", "db/migrate/snappler_contable_new_currency.rb"
21
+ end
22
+ end
23
+ end