snappler_contable_multicurrency 2.2.0

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.
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