2019-01-08 17:32:45 +01:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2019-01-31 12:19:50 +01:00
|
|
|
require 'checksum'
|
|
|
|
require 'version'
|
|
|
|
|
2019-01-08 17:32:45 +01:00
|
|
|
# AccountingPeriod is a period of N days (N > 0) which as been closed by an admin
|
|
|
|
# to prevent writing new accounting lines (invoices & refunds) during this period of time.
|
2019-01-07 12:29:52 +01:00
|
|
|
class AccountingPeriod < ActiveRecord::Base
|
|
|
|
before_destroy { false }
|
|
|
|
before_update { false }
|
2019-02-25 14:51:19 +01:00
|
|
|
before_create :compute_totals
|
2019-01-10 10:52:29 +01:00
|
|
|
after_create :archive_closed_data
|
2019-01-07 12:29:52 +01:00
|
|
|
|
2019-01-08 17:32:45 +01:00
|
|
|
validates :start_at, :end_at, :closed_at, :closed_by, presence: true
|
|
|
|
validates_with DateRangeValidator
|
|
|
|
validates_with PeriodOverlapValidator
|
2019-02-12 17:38:44 +01:00
|
|
|
validates_with PeriodIntegrityValidator
|
2019-01-08 17:32:45 +01:00
|
|
|
|
2019-01-07 12:29:52 +01:00
|
|
|
def delete
|
|
|
|
false
|
|
|
|
end
|
2019-01-10 10:52:29 +01:00
|
|
|
|
2019-02-25 14:51:19 +01:00
|
|
|
def invoices
|
2019-03-12 12:15:14 +01:00
|
|
|
Invoice.where('created_at >= :start_date AND CAST(created_at AS DATE) <= :end_date', start_date: start_at, end_date: end_at)
|
|
|
|
end
|
|
|
|
|
|
|
|
def invoices_with_vat(invoices)
|
|
|
|
invoices.map do |i|
|
|
|
|
if i.type == 'Avoir'
|
|
|
|
{ invoice: i, vat_rate: vat_rate(i.avoir_date) }
|
|
|
|
else
|
|
|
|
{ invoice: i, vat_rate: vat_rate(i.created_at) }
|
|
|
|
end
|
|
|
|
end
|
2019-02-25 14:51:19 +01:00
|
|
|
end
|
|
|
|
|
2019-01-10 10:52:29 +01:00
|
|
|
def archive_file
|
|
|
|
dir = 'accounting'
|
|
|
|
|
|
|
|
# create directory if it doesn't exists (accounting)
|
|
|
|
FileUtils.mkdir_p dir
|
|
|
|
"#{dir}/#{start_at.iso8601}_#{end_at.iso8601}.json"
|
|
|
|
end
|
|
|
|
|
2019-03-11 16:11:49 +01:00
|
|
|
def check_footprint
|
|
|
|
footprint == compute_footprint
|
|
|
|
end
|
|
|
|
|
2019-03-12 12:15:14 +01:00
|
|
|
def vat_rate(date)
|
2019-03-12 15:26:17 +01:00
|
|
|
@vat_rates = vat_history if @vat_rates.nil?
|
|
|
|
|
|
|
|
first_rate = @vat_rates.first
|
2019-03-12 12:15:14 +01:00
|
|
|
return first_rate[:rate] if date < first_rate[:date]
|
|
|
|
|
2019-03-12 15:26:17 +01:00
|
|
|
@vat_rates.each do |h|
|
2019-03-12 12:15:14 +01:00
|
|
|
return h[:rate] if h[:date] <= date
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-01-10 15:12:22 +01:00
|
|
|
private
|
|
|
|
|
2019-03-12 12:15:14 +01:00
|
|
|
def vat_history
|
|
|
|
key_dates = []
|
|
|
|
Setting.find_by(name: 'invoice_VAT-rate').history_values.each do |rate|
|
|
|
|
key_dates.push(date: rate.created_at, rate: (rate.value.to_i / 100.0))
|
|
|
|
end
|
|
|
|
Setting.find_by(name: 'invoice_VAT-active').history_values.each do |v|
|
|
|
|
key_dates.push(date: v.created_at, rate: 0) if v.value == 'false'
|
|
|
|
end
|
|
|
|
key_dates.sort_by { |k| k[:date] }
|
|
|
|
end
|
|
|
|
|
2019-01-10 10:52:29 +01:00
|
|
|
def to_json_archive(invoices)
|
2019-01-31 12:19:50 +01:00
|
|
|
previous_file = previous_period&.archive_file
|
|
|
|
code_checksum = Checksum.code
|
2019-02-12 16:19:11 +01:00
|
|
|
last_archive_checksum = previous_file ? Checksum.file(previous_file) : nil
|
2019-01-10 10:52:29 +01:00
|
|
|
ApplicationController.new.view_context.render(
|
|
|
|
partial: 'archive/accounting',
|
2019-01-31 12:19:50 +01:00
|
|
|
locals: {
|
2019-03-12 12:15:14 +01:00
|
|
|
invoices: invoices_with_vat(invoices),
|
2019-02-25 14:51:19 +01:00
|
|
|
period_total: period_total,
|
|
|
|
perpetual_total: perpetual_total,
|
|
|
|
period_footprint: footprint,
|
2019-01-31 12:19:50 +01:00
|
|
|
code_checksum: code_checksum,
|
|
|
|
last_archive_checksum: last_archive_checksum,
|
|
|
|
previous_file: previous_file,
|
2019-03-11 16:25:51 +01:00
|
|
|
software_version: Version.current,
|
|
|
|
date: Time.now.iso8601
|
2019-01-31 12:19:50 +01:00
|
|
|
},
|
2019-01-10 10:52:29 +01:00
|
|
|
formats: [:json],
|
|
|
|
handlers: [:jbuilder]
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2019-01-31 12:19:50 +01:00
|
|
|
def previous_period
|
2019-02-12 16:19:11 +01:00
|
|
|
AccountingPeriod.where('closed_at < ?', closed_at).order(closed_at: :desc).limit(1).last
|
2019-01-31 12:19:50 +01:00
|
|
|
end
|
|
|
|
|
2019-01-10 10:52:29 +01:00
|
|
|
def archive_closed_data
|
2019-02-25 14:51:19 +01:00
|
|
|
data = invoices.includes(:invoice_items)
|
2019-01-10 10:52:29 +01:00
|
|
|
File.write(archive_file, to_json_archive(data))
|
|
|
|
end
|
2019-02-25 14:51:19 +01:00
|
|
|
|
2019-03-12 13:36:10 +01:00
|
|
|
def price_without_taxe(invoice)
|
|
|
|
invoice[:invoice].total - (invoice[:invoice].total * invoice[:vat_rate])
|
|
|
|
end
|
|
|
|
|
2019-02-25 14:51:19 +01:00
|
|
|
def compute_totals
|
2019-03-12 13:36:10 +01:00
|
|
|
period_invoices = invoices_with_vat(invoices.where(type: nil))
|
|
|
|
period_avoirs = invoices_with_vat(invoices.where(type: 'Avoir'))
|
|
|
|
self.period_total = (period_invoices.map(&method(:price_without_taxe)).reduce(:+) || 0) -
|
|
|
|
(period_avoirs.map(&method(:price_without_taxe)).reduce(:+) || 0)
|
|
|
|
|
|
|
|
all_invoices = invoices_with_vat(Invoice.where('CAST(created_at AS DATE) <= :end_date AND type IS NULL', end_date: end_at))
|
|
|
|
all_avoirs = invoices_with_vat(Invoice.where("CAST(created_at AS DATE) <= :end_date AND type = 'Avoir'", end_date: end_at))
|
|
|
|
self.perpetual_total = (all_invoices.map(&method(:price_without_taxe)).reduce(:+) || 0) -
|
|
|
|
(all_avoirs.map(&method(:price_without_taxe)).reduce(:+) || 0)
|
2019-02-25 14:51:19 +01:00
|
|
|
self.footprint = compute_footprint
|
|
|
|
end
|
|
|
|
|
|
|
|
def compute_footprint
|
2019-03-11 16:11:49 +01:00
|
|
|
columns = AccountingPeriod.columns.map(&:name)
|
2019-03-12 15:26:17 +01:00
|
|
|
.delete_if { |c| %w[id footprint created_at updated_at].include? c }
|
2019-02-25 14:51:19 +01:00
|
|
|
|
|
|
|
sha256 = Digest::SHA256.new
|
|
|
|
sha256.hexdigest "#{columns.map { |c| self[c] }.join}#{previous_period ? previous_period.footprint : ''}"
|
|
|
|
end
|
2019-01-07 12:29:52 +01:00
|
|
|
end
|