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