diff --git a/.rubocop.yml b/.rubocop.yml index 89595f901..9b23c3187 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -3,7 +3,7 @@ Metrics/LineLength: Metrics/MethodLength: Max: 30 Metrics/CyclomaticComplexity: - Max: 9 + Max: 12 Metrics/PerceivedComplexity: Max: 9 Metrics/AbcSize: diff --git a/app/models/invoice.rb b/app/models/invoice.rb index b052253ad..9533b89d2 100644 --- a/app/models/invoice.rb +++ b/app/models/invoice.rb @@ -188,7 +188,7 @@ class Invoice < ActiveRecord::Base def subscription_invoice? invoice_items.each do |ii| - return true if ii.subscription && !ii.subscription.expired? + return true if ii.subscription end false end diff --git a/app/services/accounting_export_service.rb b/app/services/accounting_export_service.rb new file mode 100644 index 000000000..8184cadc2 --- /dev/null +++ b/app/services/accounting_export_service.rb @@ -0,0 +1,200 @@ +# frozen_string_literal: true + +# Provides the routine to export the accounting data to an external accounting software +class AccountingExportService + attr_reader :file, :encoding, :format, :separator, :log_code, :date_format + + def initialize(file, columns, encoding = 'UTF-8', format = 'CSV', separator = ';') + @file = file + @encoding = encoding + @format = format + @separator = separator + @log_code = Setting.find_by(name: 'accounting-export_log-code').value + @date_format = Setting.find_by(name: 'accounting-export_date-format').value + @columns = columns + end + + def export(start_date, end_date) + # build CVS content + content = '' + invoices = Invoice.where('created_at >= ? AND created_at <= ?', start_date, end_date).order('created_at ASC') + invoices.each do |i| + content << generate_rows(i) + end + + # write content to file + File.open(file, "w:#{encoding}+b") { |f| f.puts content } + end + + private + + def generate_rows(invoice) + client_row(invoice) << "\n" << + items_rows(invoice) << "\n" << + vat_row(invoice) << "\n" + end + + # Generate the "subscription" and "reservation" rows associated with the provided invoice + def items_rows(invoice) + rows = invoice.subscription_invoice? ? subscription_row(invoice) << "\n" : '' + invoice.invoice_items.each do |item| + rows << reservation_row(invoice, item) << "\n" + end + end + + # Generate the "client" row, which contains the debit to the client account, all taxes included + def client_row(invoice) + row = '' + columns.each do |column| + case column + when :log_code + row << log_code + when :date + row << invoice.created_at.strftime(date_format) + when :account_code + row << account(invoice, :client) + when :account_label + row << account(invoice, :client, :label) + when :piece + row << invoice.reference + when :line_label + row << invoice.invoicing_profile.full_name + when :debit_origin + row << invoice.total / 100.0 + when :credit_origin + row << 0 + when :debit_euro + row << invoice.total / 100.0 + when :credit_euro + row << 0 + else + puts "Unsupported column: #{column}" + end + row << separator + end + end + + # Generate the "reservation" row, which contains the credit to the reservation account, all taxes excluded + def reservation_row(invoice, item) + vat_rate = Setting.find_by(name: 'invoice_VAT-rate').value.to_f + wo_taxes = item.amount / (vat_rate / 100 + 1) + row = '' + columns.each do |column| + case column + when :log_code + row << log_code + when :date + row << invoice.created_at.strftime(date_format) + when :account_code + row << account(invoice, :reservation) + when :account_label + row << account(invoice, :reservation, :label) + when :piece + row << invoice.reference + when :line_label + row << item.description + when :debit_origin + row << 0 + when :credit_origin + row << wo_taxes + when :debit_euro + row << 0 + when :credit_euro + row << wo_taxes + else + puts "Unsupported column: #{column}" + end + row << separator + end + end + + # Generate the "subscription" row, which contains the credit to the subscription account, all taxes excluded + def subscription_row(invoice) + subscription_item = invoice.invoice_items.select(&:subscription).first + vat_rate = Setting.find_by(name: 'invoice_VAT-rate').value.to_f + wo_taxes = subscription_item.amount / (vat_rate / 100 + 1) + row = '' + columns.each do |column| + case column + when :log_code + row << log_code + when :date + row << invoice.created_at.strftime(date_format) + when :account_code + row << account(invoice, :subscription) + when :account_label + row << account(invoice, :subscription, :label) + when :piece + row << invoice.reference + when :line_label + row << subscription_item.description + when :debit_origin + row << 0 + when :credit_origin + row << wo_taxes + when :debit_euro + row << 0 + when :credit_euro + row << wo_taxes + else + puts "Unsupported column: #{column}" + end + row << separator + end + end + # Generate the "VAT" row, which contains the credit to the VAT account, with VAT amount only + def vat_row(invoice) + # first compute the VAT amount + vat_rate = Setting.find_by(name: 'invoice_VAT-rate').value.to_f + vat = invoice.total - (invoice.total / (vat_rate / 100 + 1)) + # now feed the row + row = '' + columns.each do |column| + case column + when :log_code + row << log_code + when :date + row << invoice.created_at.strftime(date_format) + when :account_code + row << account(invoice, :vat) + when :account_label + row << account(invoice, :vat, :label) + when :piece + row << invoice.reference + when :line_label + row << I18n.t('accounting_export.VAT') + when :debit_origin + row << vat + when :credit_origin + row << 0 + when :debit_euro + row << vat + when :credit_euro + row << 0 + else + puts "Unsupported column: #{column}" + end + row << separator + end + end + + # Get the account code (or label) for the given invoice and the specified line type (client, vat, subscription or reservation) + def account(invoice, account, type = :code) + case account + when :client + Setting.find_by(name: "accounting-export_client-account-#{type.to_s}").value + when :vat + Setting.find_by(name: "accounting-export_VAT-account-#{type.to_s}").value + when :subscription + return if invoice.invoiced_type != 'Subscription' + + Setting.find_by(name: "accounting-export_subscription-account-#{type.to_s}").value + when :reservation + return if invoice.invoiced_type != 'Reservation' + + Setting.find_by(name: "accounting-export_#{invoice.invoiced.reservable_type}-account-#{type.to_s}").value + else + puts "Unsupported account #{account}" + end + end +end diff --git a/app/services/statistics_export_service.rb b/app/services/statistics_export_service.rb index 58996a92e..9c51dad29 100644 --- a/app/services/statistics_export_service.rb +++ b/app/services/statistics_export_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'abstract_controller' require 'action_controller' require 'action_view' @@ -13,7 +15,7 @@ class StatisticsExportService # query all stats with range arguments query = MultiJson.load(export.query) - @results = Elasticsearch::Model.client.search({index: 'stats', scroll: '30s', body: query}) + @results = Elasticsearch::Model.client.search(index: 'stats', scroll: '30s', body: query) scroll_id = @results['_scroll_id'] while @results['hits']['hits'].size != @results['hits']['total'] scroll_res = Elasticsearch::Model.client.scroll(scroll: '30s', scroll_id: scroll_id) @@ -22,9 +24,9 @@ class StatisticsExportService end ids = @results['hits']['hits'].map { |u| u['_source']['userId'] } - @users = User.includes(:profile).where(:id => ids) + @users = User.includes(:profile).where(id: ids) - @indices = StatisticIndex.all.includes(:statistic_fields, :statistic_types => [:statistic_sub_types]) + @indices = StatisticIndex.all.includes(:statistic_fields, statistic_types: [:statistic_sub_types]) ActionController::Base.prepend_view_path './app/views/' # place data in view_assigns @@ -37,10 +39,10 @@ class StatisticsExportService content = av.render template: 'exports/statistics_global.xlsx.axlsx' # write content to file - File.open(export.file,"w+b") {|f| f.puts content } + File.open(export.file, 'w+b') { |f| f.puts content } end - %w(account event machine project subscription training space).each do |path| + %w[account event machine project subscription training space].each do |path| class_eval %{ def export_#{path}(export) @@ -76,7 +78,7 @@ class StatisticsExportService # write content to file File.open(export.file,"w+b") {|f| f.puts content } end - } + }, __FILE__, __LINE__ - 35 end -end \ No newline at end of file +end diff --git a/config/locales/fr.yml b/config/locales/fr.yml index e011c9103..2110ff832 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -126,6 +126,9 @@ fr: subscription_of_NAME_extended_starting_from_STARTDATE_until_ENDDATE: "Prolongement Abonnement (Jours gratuits) de %{NAME} à compter du %{STARTDATE} jusqu'au %{ENDDATE}" and: 'et' + accounting_export: + VAT: 'TVA' + trainings: # disponibilités formations i_ve_reserved: "J'ai réservé"