From 00b9bce587a5b3a99152e0e7ae7e801cf7b982da Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 29 Dec 2021 17:00:49 +0100 Subject: [PATCH] export collected VAT by rate --- app/controllers/api/exports_controller.rb | 2 + .../javascript/controllers/admin/invoices.js | 11 +++ .../admin/invoices/accountingExportModal.html | 10 +- app/models/invoice_item.rb | 4 +- app/services/accounting_export_service.rb | 1 - app/services/vat_export_service.rb | 97 +++++++++++++++++++ app/workers/accounting_export_worker.rb | 3 +- config/locales/app.admin.en.yml | 9 +- config/locales/en.yml | 6 ++ 9 files changed, 135 insertions(+), 8 deletions(-) create mode 100644 app/services/vat_export_service.rb diff --git a/app/controllers/api/exports_controller.rb b/app/controllers/api/exports_controller.rb index 66dc270c3..2b8ab1980 100644 --- a/app/controllers/api/exports_controller.rb +++ b/app/controllers/api/exports_controller.rb @@ -70,6 +70,8 @@ class API::ExportsController < API::ApiController case type when 'acd' export = export.where('created_at > ?', Invoice.maximum('updated_at')) + when 'vat' + export = export.where('created_at > ?', Invoice.maximum('updated_at')) else raise ArgumentError, "Unknown type accounting/#{type}" end diff --git a/app/frontend/src/javascript/controllers/admin/invoices.js b/app/frontend/src/javascript/controllers/admin/invoices.js index e0b52eafc..960025762 100644 --- a/app/frontend/src/javascript/controllers/admin/invoices.js +++ b/app/frontend/src/javascript/controllers/admin/invoices.js @@ -1414,6 +1414,16 @@ Application.Controllers.controller('AccountingExportModalController', ['$scope', decimalSeparator: ',', exportInvoicesAtZero: false, columns: ['journal_code', 'date', 'account_code', 'account_label', 'piece', 'line_label', 'debit_origin', 'credit_origin', 'debit_euro', 'credit_euro', 'lettering'] + }, + vat: { + format: 'csv', + encoding: 'UTF-8', + separator: ';', + dateFormat: '%Y-%m-%d', + labelMaxLength: 'N/A', + decimalSeparator: '.', + exportInvoicesAtZero: false, + columns: ['start_date', 'end_date', 'vat_rate', 'amount'] } }; @@ -1433,6 +1443,7 @@ Application.Controllers.controller('AccountingExportModalController', ['$scope', // binding to radio button "export to" $scope.exportTarget = { + type: null, software: null, startDate: null, endDate: null, diff --git a/app/frontend/templates/admin/invoices/accountingExportModal.html b/app/frontend/templates/admin/invoices/accountingExportModal.html index 4c952b4c9..4d6f97523 100644 --- a/app/frontend/templates/admin/invoices/accountingExportModal.html +++ b/app/frontend/templates/admin/invoices/accountingExportModal.html @@ -42,11 +42,15 @@
-

{{ 'app.admin.invoices.export_to' }}

+

{{ 'app.admin.invoices.export_what' }}

-
diff --git a/app/models/invoice_item.rb b/app/models/invoice_item.rb index 7212a91f5..9d8cfe867 100644 --- a/app/models/invoice_item.rb +++ b/app/models/invoice_item.rb @@ -40,8 +40,10 @@ class InvoiceItem < Footprintable def invoice_item_type if object_type == Reservation.name object.try(:reservable_type) || '' - elsif object_type == Subscription.name + elsif [Subscription.name, OfferDay.name].include? object_type Subscription.name + elsif object_type == StatisticProfilePrepaidPack.name + object.prepaid_pack.priceable_type else '' end diff --git a/app/services/accounting_export_service.rb b/app/services/accounting_export_service.rb index b4451c13e..e9f15f302 100644 --- a/app/services/accounting_export_service.rb +++ b/app/services/accounting_export_service.rb @@ -16,7 +16,6 @@ class AccountingExportService @label_max_length = 50 @export_zeros = false @journal_code = Setting.get('accounting_journal_code') || '' - @date_format = date_format @columns = columns end diff --git a/app/services/vat_export_service.rb b/app/services/vat_export_service.rb new file mode 100644 index 000000000..a5a9ebcbf --- /dev/null +++ b/app/services/vat_export_service.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: false + +# Provides the routine to export the collected VAT data to a CSV file. +class VatExportService + include ActionView::Helpers::NumberHelper + + attr_reader :encoding, :format, :separator, :date_format, :columns, :decimal_separator + + def initialize(columns, encoding: 'UTF-8', format: 'CSV', separator: ';') + @encoding = encoding + @format = format + @separator = separator + @decimal_separator = '.' + @date_format = '%Y-%m-%d' + @columns = columns + end + + def set_options(decimal_separator: ',', date_format: '%d/%m/%Y', label_max_length: nil, export_zeros: nil) + @decimal_separator = decimal_separator + @date_format = date_format + end + + def export(start_date, end_date, file) + # build CSV content + content = header_row + invoices = Invoice.where('created_at >= ? AND created_at <= ?', start_date, end_date).order('created_at ASC') + vat_totals = compute_vat_totals(invoices) + content << generate_rows(vat_totals, start_date, end_date) + + # write content to file + File.open(file, "w:#{encoding}") { |f| f.puts content.encode(encoding, invalid: :replace, undef: :replace) } + end + + private + + def header_row + row = '' + columns.each do |column| + row << I18n.t("vat_export.#{column}") << separator + end + "#{row}\n" + end + + def generate_rows(vat_totals, start_date, end_date) + rows = '' + + vat_totals.each do |rate, total| + next if rate.zero? + + rows += "#{row( + start_date, + end_date, + rate, + total + )}\n" + end + + rows + end + + def compute_vat_totals(invoices) + vat_total = [] + service = VatHistoryService.new + invoices.each do |i| + puts "processing invoice #{i.id}..." unless Rails.env.test? + vat_total.push service.invoice_vat(i) + end + + vat_total.map(&:values).flatten.group_by { |tot| tot[:vat_rate] }.map { |k, v| [k, v.map { |t| t[:total_vat] }.reduce(:+)] }.to_h + end + + # Generate a row of the export, filling the configured columns with the provided values + def row(start_date, end_date, vat_rate, amount) + row = '' + columns.each do |column| + case column + when 'start_date' + row << DateTime.parse(start_date).strftime(date_format) + when 'end_date' + row << DateTime.parse(end_date).strftime(date_format) + when 'vat_rate' + row << vat_rate.to_s + when 'amount' + row << format_number(amount / 100.0) + else + puts "Unsupported column: #{column}" + end + row << separator + end + row + end + + # Format the given number as a string, using the configured separator + def format_number(num) + number_to_currency(num, unit: '', separator: decimal_separator, delimiter: '', precision: 2) + end +end diff --git a/app/workers/accounting_export_worker.rb b/app/workers/accounting_export_worker.rb index a0bd28c22..2fe5b217a 100644 --- a/app/workers/accounting_export_worker.rb +++ b/app/workers/accounting_export_worker.rb @@ -10,7 +10,8 @@ class AccountingExportWorker raise SecurityError, 'Not allowed to export' unless export.user.admin? data = JSON.parse(export.query) - service = AccountingExportService.new( + service = export.export_type == 'vat' ? VatExportService : AccountingExportService + service = service.new( data['columns'], encoding: data['encoding'], format: export.extension, separator: export.key ) diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index 5ac4ef33d..5d1f85fe5 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -679,9 +679,10 @@ en: codes_customization_success: "Customization of the accounting codes successfully saved." unexpected_error_occurred: "An unexpected error occurred while saving the codes. Please try again later." export_accounting_data: "Export accounting data" - export_to: "Export to the accounting software" + export_what: "What do you want to export?" + export_VAT: "Export the collected VAT" + export_to_ACD: "Export all data to the accounting software ACD" export_is_running: "Export is running. You'll be notified when it's ready." - acd: "ACD" export_form_date: "Export from" export_to_date: "Export until" format: "File format" @@ -704,6 +705,10 @@ en: debit_euro: "Euro debit" credit_euro: "Euro credit" lettering: "Lettering" + start_date: "Start date" + end_date: "End date" + vat_rate: "VAT rate" + amount: "Total amount" payment: payment_settings: "Payment settings" online_payment: "Online payment" diff --git a/config/locales/en.yml b/config/locales/en.yml index a25312b9d..84e6745e2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -147,6 +147,11 @@ en: Event_reservation: "event reserv." Space_reservation: "space reserv." wallet: "wallet" + vat_export: + start_date: "Start date" + end_date: "End date" + vat_rate: "VAT rate" + amount: "Total amount" #training availabilities trainings: i_ve_reserved: "I've reserved" @@ -331,6 +336,7 @@ en: users_reservations: "of the reservations' list" availabilities_index: "of the reservations availabilities" accounting_acd: "of the accounting data to ACD" + accounting_vat: "of the collected VAT" is_over: "is over." download_here: "Download here" notify_admin_import_complete: