Class: FinancialYear

Inherits:
Ekylibre::Record::Base show all
Includes:
Attachable, Customizable
Defined in:
app/models/financial_year.rb

Overview

Informations

License

Ekylibre - Simple agricultural ERP Copyright (C) 2008-2009 Brice Texier, Thibaud Merigon Copyright (C) 2010-2012 Brice Texier Copyright (C) 2012-2016 Brice Texier, David Joulin

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License along with this program. If not, see www.gnu.org/licenses.

Table: financial_years

closed                :boolean          default(FALSE), not null
code                  :string           not null
created_at            :datetime         not null
creator_id            :integer
currency              :string           not null
currency_precision    :integer
custom_fields         :jsonb
id                    :integer          not null, primary key
last_journal_entry_id :integer
lock_version          :integer          default(0), not null
started_on            :date             not null
stopped_on            :date             not null
updated_at            :datetime         not null
updater_id            :integer

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Customizable

#custom_value, #set_custom_value, #validate_custom_fields

Methods inherited from Ekylibre::Record::Base

#already_updated?, attr_readonly_with_conditions, #check_if_destroyable?, #check_if_updateable?, columns_definition, complex_scopes, customizable?, #customizable?, #customized?, #destroyable?, #editable?, has_picture, #human_attribute_name, human_attribute_name_with_id, nomenclature_reflections, #old_record, #others, refers_to, scope_with_registration, simple_scopes, #updateable?

Class Method Details

.at(searched_at = Time.zone.now) ⇒ Object

Find or create if possible the requested financial year for the searched date


69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'app/models/financial_year.rb', line 69

def at(searched_at = Time.zone.now)
  searched_on = searched_at.to_date
  year = where('? BETWEEN started_on AND stopped_on', searched_on).order(started_on: :desc).first
  return year if year
  # First
  unless (first = first_of_all)
    started_on = Time.zone.today
    first = create!(started_on: started_on, stopped_on: (started_on >> 11).end_of_month)
  end
  if first.stopped_on == (first.started_on >> 12) - 1
    other = first
    other = other.find_or_create_previous! while other.started_on > searched_on
    return other
  elsif first.started_on > searched_on
    return nil
  end

  # Next years
  other = first
  other = other.find_or_create_next! while searched_on > other.stopped_on
  other
end

.balance_expr(credit = false, options = {}) ⇒ Object


314
315
316
317
318
319
320
321
322
323
# File 'app/models/financial_year.rb', line 314

def self.balance_expr(credit = false, options = {})
  columns = [:debit, :credit]
  columns.reverse! if credit
  prefix = (options[:record] ? options.delete(:record).to_s + '.' : '') + 'local_'
  if options[:forced]
    return "(#{prefix}#{columns[0]} - #{prefix}#{columns[1]})"
  else
    return "(CASE WHEN #{prefix}#{columns[0]} > #{prefix}#{columns[1]} THEN #{prefix}#{columns[0]} - #{prefix}#{columns[1]} ELSE 0 END)"
  end
end

.closableObject


100
101
102
# File 'app/models/financial_year.rb', line 100

def closable
  closables.first
end

.currentObject


96
97
98
# File 'app/models/financial_year.rb', line 96

def current
  at(Time.zone.now)
end

.first_of_allObject


92
93
94
# File 'app/models/financial_year.rb', line 92

def first_of_all
  reorder(:started_on).first
end

.last_closureObject

Returns the date of the last closure if any


105
106
107
108
109
110
# File 'app/models/financial_year.rb', line 105

def last_closure
  if year = closed.reorder(started_on: :desc).first
    return year.stopped_on
  end
  nil
end

Instance Method Details

#balance(accounts, credit = false) ⇒ Object

Computes the value of list of accounts in a String 123 will take all accounts 123* ^456 will remove all accounts 456* 789X will compute the balance although result is negative


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
291
292
293
294
# File 'app/models/financial_year.rb', line 266

def balance(accounts, credit = false)
  normals = ['(XD)']
  excepts = []
  negatives = []
  forceds = []
  accounts.strip.split(/\s*[\,\s]+\s*/).each do |prefix|
    code = prefix.gsub(/(^(\-|\^)|[CDX]+$)/, '')
    excepts << code if prefix =~ /^\^\d+$/
    negatives << code if prefix =~ /^\-\d+/
    forceds << code if prefix =~ /^\-?\d+[CDX]$/
    normals << code if prefix =~ /^\-?\d+[CDX]?$/
  end

  balance = FinancialYear.balance_expr(credit)
  if !forceds.empty? || !negatives.empty?
    forceds_and_negatives = forceds & negatives
    balance = 'CASE'
    balance << ' WHEN ' + forceds_and_negatives.sort.collect { |c| "a.number LIKE '#{c}%'" }.join(' OR ') + " THEN -#{FinancialYear.balance_expr(!credit, forced: true)}" unless forceds_and_negatives.empty?
    balance << ' WHEN ' + forceds.collect { |c| "a.number LIKE '#{c}%'" }.join(' OR ') + " THEN #{FinancialYear.balance_expr(credit, forced: true)}" unless forceds.empty?
    balance << ' WHEN ' + negatives.sort.collect { |c| "a.number LIKE '#{c}%'" }.join(' OR ') + " THEN -#{FinancialYear.balance_expr(!credit)}" unless negatives.empty?
    balance << " ELSE #{FinancialYear.balance_expr(credit)} END"
  end

  query = "SELECT sum(#{balance}) AS balance FROM #{AccountBalance.table_name} AS ab JOIN #{Account.table_name} AS a ON (a.id=ab.account_id) WHERE ab.financial_year_id=#{id}"
  query << ' AND (' + normals.sort.collect { |c| "a.number LIKE '#{c}%'" }.join(' OR ') + ')'
  query << ' AND NOT (' + excepts.sort.collect { |c| "a.number LIKE '#{c}%'" }.join(' OR ') + ')' unless excepts.empty?
  balance = ActiveRecord::Base.connection.select_value(query)
  (balance.blank? ? nil : balance.to_d)
end

#closable?(noticed_on = nil) ⇒ Boolean

tests if the financial_year can be closed.

Returns:

  • (Boolean)

153
154
155
156
157
158
159
160
161
# File 'app/models/financial_year.rb', line 153

def closable?(noticed_on = nil)
  noticed_on ||= Time.zone.today
  return false if closed
  if previous = self.previous
    return false if self.previous.closable?
  end
  return false unless journal_entries('debit != credit').empty?
  (stopped_on < noticed_on)
end

#close(to_close_on = nil, options = {}) ⇒ Object

When a financial year is closed,.all the matching journals are closed too.


180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'app/models/financial_year.rb', line 180

def close(to_close_on = nil, options = {})
  return false unless closable?

  to_close_on ||= stopped_on

  ActiveRecord::Base.transaction do
    # Close all journals to the
    for journal in Journal.where('closed_on < ?', to_close_on)
      raise "Journal #{journal.name} cannot be closed on #{to_close_on}" unless journal.close!(to_close_on)
    end

    # Close year
    update_attributes(stopped_on: to_close_on, closed: true)

    # Compute balance of closed year
    compute_balances!

    # Create first entry of the new year
    if journal = Journal.find_by(id: options[:journal_id].to_i)

      if .any?
        entry = journal.entries.create!(printed_on: to_close_on + 1, currency: journal.currency)
        result   = 0
        profit   = Account.find_in_nomenclature(:financial_year_result_profit)
        losses   = Account.find_in_nomenclature(:financial_year_result_loss)
        expenses = Account.find_in_nomenclature(:expenses)
        revenues = Account.find_in_nomenclature(:revenues)

        for balance in .joins(:account).order('number')
          if balance..number.to_s =~ /^(#{expenses.number}|#{revenues.number})/
            result += balance.balance
          elsif balance.balance != 0
            # TODO: Use currencies properly in account_balances !
            entry.items.create!(account_id: balance., name: balance..name, real_debit: balance.balance_debit, real_credit: balance.balance_credit)
          end
        end

        if result > 0
          entry.items.create!(account_id: losses.id, name: losses.name, real_debit: result, real_credit: 0.0)
        elsif result < 0
          entry.items.create!(account_id: profit.id, name: profit.name, real_debit: 0.0, real_credit: result.abs)
        end

      end
    end
  end
  true
end

#closures(noticed_on = nil) ⇒ Object


163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'app/models/financial_year.rb', line 163

def closures(noticed_on = nil)
  noticed_on ||= Time.zone.today
  array = []
  first_year = self.class.order('started_on').first
  if (first_year.nil? || first_year == self) && self.class.count <= 1
    date = started_on.end_of_month
    while date < noticed_on
      array << date
      date = (date + 1).end_of_month
    end
  else
    array << stopped_on
  end
  array
end

#compute_balances!Object

Re-create all account_balances record for the financial year


326
327
328
329
330
331
332
333
# File 'app/models/financial_year.rb', line 326

def compute_balances!
  results = ActiveRecord::Base.connection.select_all("SELECT account_id, sum(debit) AS debit, sum(credit) AS credit, count(id) AS count FROM #{JournalEntryItem.table_name} WHERE state != 'draft' AND printed_on BETWEEN #{self.class.connection.quote(started_on)} AND #{self.class.connection.quote(stopped_on)} GROUP BY account_id")
  .clear
  results.each do |result|
    .create!(account_id: result['account_id'].to_i, local_count: result['count'].to_i, local_credit: result['credit'].to_f, local_debit: result['debit'].to_f, currency: self.currency)
  end
  self
end

#credit_balance(accounts) ⇒ Object

Computes and formats credit balance for an account regexp Use I18n to produce string


307
308
309
310
311
312
# File 'app/models/financial_year.rb', line 307

def credit_balance(accounts)
  if value = balance(accounts, true)
    return value.l(currency: self.currency)
  end
  nil
end

#debit_balance(accounts) ⇒ Object

Computes and formats debit balance for an account regexp Use I18n to produce string


298
299
300
301
302
303
# File 'app/models/financial_year.rb', line 298

def debit_balance(accounts)
  if value = balance(accounts, false)
    return value.l(currency: self.currency)
  end
  nil
end

#default_codeObject


148
149
150
# File 'app/models/financial_year.rb', line 148

def default_code
  tc('code.' + (started_on.year != stopped_on.year ? 'double' : 'single'), first_year: started_on.year, second_year: stopped_on.year)
end

#find_or_create_next!Object

Find or create the next financial year based on the date of the current


240
241
242
243
244
245
# File 'app/models/financial_year.rb', line 240

def find_or_create_next!
  unless (year = self.next)
    year = self.class.create!(started_on: stopped_on + 1, stopped_on: stopped_on >> 12, currency: self.currency)
  end
  year
end

#find_or_create_previous!Object

Find or create the previous financial year based on the date of the current


248
249
250
251
252
253
# File 'app/models/financial_year.rb', line 248

def find_or_create_previous!
  unless (year = previous)
    year = self.class.create!(started_on: started_on << 12, stopped_on: started_on - 1, currency: self.currency)
  end
  year
end

#generate_last_journal_entry(options = {}) ⇒ Object

Generate last journal entry with financial assets depreciations (option.ally)


336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'app/models/financial_year.rb', line 336

def generate_last_journal_entry(options = {})
  unless last_journal_entry
    create_last_journal_entry!(printed_on: stopped_on, journal_id: options[:journal_id])
  end

  # Empty journal entry
  last_journal_entry.items.clear

  if options[:fixed_assets_depreciations]
    for depreciation in fixed_asset_depreciations.includes(:fixed_asset)
      name = tc(:bookkeep, resource: FixedAsset.model_name.human, number: depreciation.fixed_asset.number, name: depreciation.fixed_asset.name, position: depreciation.position, total: depreciation.fixed_asset.depreciations.count)
      # Charges
      last_journal_entry.add_debit(name, depreciation.fixed_asset., depreciation.amount)
      # Allocation
      last_journal_entry.add_credit(name, depreciation.fixed_asset., depreciation.amount)
      depreciation.update_attributes(journal_entry_id: last_journal_entry.id)
    end
  end
  self
end

#journal_entries(conditions = nil) ⇒ Object


138
139
140
141
142
# File 'app/models/financial_year.rb', line 138

def journal_entries(conditions = nil)
  entries = JournalEntry.where(printed_on: started_on..stopped_on)
  entries = entries.where(conditions) unless conditions.blank?
  entries
end

#nameObject


144
145
146
# File 'app/models/financial_year.rb', line 144

def name
  code
end

#nextObject

this method returns the next financial_year by default.


235
236
237
# File 'app/models/financial_year.rb', line 235

def next
  self.class.find_by(started_on: stopped_on + 1)
end

#previousObject

this method returns the previous financial_year by default.


230
231
232
# File 'app/models/financial_year.rb', line 230

def previous
  self.class.find_by(stopped_on: started_on - 1)
end

#sum_entry_items(expression, options = {}) ⇒ Object

See Journal.sum_entry_items


256
257
258
259
260
# File 'app/models/financial_year.rb', line 256

def sum_entry_items(expression, options = {})
  options[:started_on] = started_on
  options[:stopped_on] = stopped_on
  Journal.sum_entry_items(expression, options)
end