snappler_contable_multicurrency 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +398 -0
- data/Rakefile +39 -0
- data/app/helpers/snappler_contable_helper.rb +42 -0
- data/app/models/ledger_account.rb +217 -0
- data/app/models/ledger_account_activo.rb +8 -0
- data/app/models/ledger_account_pasivo.rb +6 -0
- data/app/models/ledger_account_patrimonio_neto.rb +6 -0
- data/app/models/ledger_account_relation.rb +8 -0
- data/app/models/ledger_account_resultado_negativo.rb +6 -0
- data/app/models/ledger_account_resultado_positivo.rb +6 -0
- data/app/models/ledger_currency.rb +5 -0
- data/app/models/ledger_entry.rb +7 -0
- data/app/models/ledger_move.rb +76 -0
- data/lib/generators/snappler_contable/initializer_generator.rb +11 -0
- data/lib/generators/snappler_contable/migrate_generator.rb +25 -0
- data/lib/generators/snappler_contable/new_currency_generator.rb +23 -0
- data/lib/generators/snappler_contable/templates/snappler_contable_add_currencies.rb +20 -0
- data/lib/generators/snappler_contable/templates/snappler_contable_app_ledger_accounts.rb +48 -0
- data/lib/generators/snappler_contable/templates/snappler_contable_migrate.rb +68 -0
- data/lib/generators/snappler_contable/templates/snappler_contable_new_currency.rb +14 -0
- data/lib/snappler_contable/ext/string.rb +5 -0
- data/lib/snappler_contable/railtie.rb +4 -0
- data/lib/snappler_contable/snappler_contable.rb +154 -0
- data/lib/snappler_contable/version.rb +3 -0
- data/lib/snappler_contable_multicurrency.rb +14 -0
- data/lib/tasks/snappler_contable_tasks.rake +4 -0
- data/spec/internal/config/database_example.yml +8 -0
- data/spec/internal/config/initializers/snappler_contable.rb +8 -0
- data/spec/internal/config/routes.rb +3 -0
- data/spec/internal/db/schema.rb +64 -0
- data/spec/internal/public/favicon.ico +0 -0
- data/spec/models/ledger_account_spec.rb +308 -0
- data/spec/spec_helper.rb +75 -0
- 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,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
|