From ab295f064d6c9cbc7e215397b95881aee65c55d8 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 29 Jul 2019 17:51:53 +0200 Subject: [PATCH 01/46] service to export invoices into a given period to CSV lines for an accounting sofware --- .rubocop.yml | 2 +- app/models/invoice.rb | 2 +- app/services/accounting_export_service.rb | 200 ++++++++++++++++++++++ app/services/statistics_export_service.rb | 16 +- config/locales/fr.yml | 3 + 5 files changed, 214 insertions(+), 9 deletions(-) create mode 100644 app/services/accounting_export_service.rb 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é" From 82ad69d386a5801651964083ee8fdd2e3a4d42f4 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 30 Jul 2019 10:27:47 +0200 Subject: [PATCH 02/46] handle avoirs --- .rubocop.yml | 2 +- app/services/accounting_export_service.rb | 63 ++++++++++++++++------- 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 9b23c3187..869bba2fc 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,7 +1,7 @@ Metrics/LineLength: Max: 140 Metrics/MethodLength: - Max: 30 + Max: 35 Metrics/CyclomaticComplexity: Max: 12 Metrics/PerceivedComplexity: diff --git a/app/services/accounting_export_service.rb b/app/services/accounting_export_service.rb index 8184cadc2..88a23d74b 100644 --- a/app/services/accounting_export_service.rb +++ b/app/services/accounting_export_service.rb @@ -60,13 +60,13 @@ class AccountingExportService when :line_label row << invoice.invoicing_profile.full_name when :debit_origin - row << invoice.total / 100.0 + row << debit_client(invoice, invoice.total / 100.0) when :credit_origin - row << 0 + row << credit_client(invoice, invoice.total / 100.0) when :debit_euro - row << invoice.total / 100.0 + row << debit_client(invoice, invoice.total / 100.0) when :credit_euro - row << 0 + row << credit_client(invoice, invoice.total / 100.0) else puts "Unsupported column: #{column}" end @@ -94,13 +94,13 @@ class AccountingExportService when :line_label row << item.description when :debit_origin - row << 0 + row << debit(invoice, wo_taxes) when :credit_origin - row << wo_taxes + row << credit(invoice, wo_taxes) when :debit_euro - row << 0 + row << debit(invoice, wo_taxes) when :credit_euro - row << wo_taxes + row << credit(invoice, wo_taxes) else puts "Unsupported column: #{column}" end @@ -129,19 +129,20 @@ class AccountingExportService when :line_label row << subscription_item.description when :debit_origin - row << 0 + row << debit(invoice, wo_taxes) when :credit_origin - row << wo_taxes + row << credit(invoice, wo_taxes) when :debit_euro - row << 0 + row << debit(invoice, wo_taxes) when :credit_euro - row << wo_taxes + row << credit(invoice, 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 @@ -164,13 +165,13 @@ class AccountingExportService when :line_label row << I18n.t('accounting_export.VAT') when :debit_origin - row << vat + row << debit(invoice, vat) when :credit_origin - row << 0 + row << credit(invoice, vat) when :debit_euro - row << vat + row << debit(invoice, vat) when :credit_euro - row << 0 + row << credit(invoice, vat) else puts "Unsupported column: #{column}" end @@ -182,19 +183,41 @@ class AccountingExportService def account(invoice, account, type = :code) case account when :client - Setting.find_by(name: "accounting-export_client-account-#{type.to_s}").value + Setting.find_by(name: "accounting-export_client-account-#{type}").value when :vat - Setting.find_by(name: "accounting-export_VAT-account-#{type.to_s}").value + Setting.find_by(name: "accounting-export_VAT-account-#{type}").value when :subscription return if invoice.invoiced_type != 'Subscription' - Setting.find_by(name: "accounting-export_subscription-account-#{type.to_s}").value + Setting.find_by(name: "accounting-export_subscription-account-#{type}").value when :reservation return if invoice.invoiced_type != 'Reservation' - Setting.find_by(name: "accounting-export_#{invoice.invoiced.reservable_type}-account-#{type.to_s}").value + Setting.find_by(name: "accounting-export_#{invoice.invoiced.reservable_type}-account-#{type}").value else puts "Unsupported account #{account}" end end + + # Fill the value of the "debit" column: if the invoice is a refund, returns the given amount, returns 0 otherwise + def debit(invoice, amount) + avoir = invoice.is_a? Avoir + avoir ? amount : 0 + end + + # Fill the value of the "credit" column: if the invoice is a refund, returns 0, otherwise, returns the given amount + def credit(invoice, amount) + avoir = invoice.is_a? Avoir + avoir ? 0 : amount + end + + # Fill the value of the "debit" column for the client row: if the invoice is a refund, returns 0, otherwise, returns the given amount + def debit_client(invoice, amount) + credit(invoice, amount) + end + + # Fill the value of the "credit" column, for the client row: if the invoice is a refund, returns the given amount, returns 0 otherwise + def credit_client(invoice, amount) + debit(invoice, amount) + end end From f772bc3509bbb107d7f75eeff057a2dcc7ea1342 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 30 Jul 2019 11:43:51 +0200 Subject: [PATCH 03/46] api endpoint and worker to export accounting data --- CHANGELOG.md | 2 + .../api/accounting_exports_controller.rb | 34 ++++++++++ app/controllers/api/exports_controller.rb | 66 ++++++++++++------- app/models/export.rb | 4 +- app/policies/accounting_exports_policy.rb | 8 +++ app/workers/accounting_export_worker.rb | 21 ++++++ config/routes.rb | 2 + .../20190730085826_add_extension_to_export.rb | 5 ++ db/schema.rb | 9 +-- 9 files changed, 123 insertions(+), 28 deletions(-) create mode 100644 app/controllers/api/accounting_exports_controller.rb create mode 100644 app/policies/accounting_exports_policy.rb create mode 100644 app/workers/accounting_export_worker.rb create mode 100644 db/migrate/20190730085826_add_extension_to_export.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 33c367e39..d0322400d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog Fab Manager +- Ability to configure and export the accounting data to the ACD accounting software - Fix a bug: no user can be created after the last member was deleted - Fix a bug: unable to generate a refund (Avoir) - Fix a bug: a newly generated refund is displayed as broken (unchained record) even if it is correctly chained @@ -9,6 +10,7 @@ - Fix some security issues: updated sidekiq to 5.2.7 to fix XSS and CRSF issues - Removed dependency to jQuery UI - Updated angular-xeditable, to remove dependency to jquery 1.11.1 +- [TODO DEPLOY] `rake db:migrate` ## v4.0.2 2019 July 10 diff --git a/app/controllers/api/accounting_exports_controller.rb b/app/controllers/api/accounting_exports_controller.rb new file mode 100644 index 000000000..9efa1a321 --- /dev/null +++ b/app/controllers/api/accounting_exports_controller.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# API Controller for exporting accounting data to external accounting softwares +class API::AccountingExportsController < API::ApiController + + before_action :authenticate_user! + + def export + authorize :export + + export = Export.where(category: 'accounting', export_type: 'accounting-software') + .where('created_at > ?', Invoice.maximum('updated_at')) + .last + if export.nil? || !FileTest.exist?(export.file) + @export = Export.new( + category: 'accounting', + export_type: 'accounting-software', + user: current_user, + extension: params[:extension], + query: params[:query], + key: params[:separator] + ) + if @export.save + render json: { export_id: @export.id }, status: :ok + else + render json: @export.errors, status: :unprocessable_entity + end + else + send_file File.join(Rails.root, export.file), + type: 'text/csv', + disposition: 'attachment' + end + end +end diff --git a/app/controllers/api/exports_controller.rb b/app/controllers/api/exports_controller.rb index a785562e9..e02a6e7b4 100644 --- a/app/controllers/api/exports_controller.rb +++ b/app/controllers/api/exports_controller.rb @@ -8,10 +8,17 @@ class API::ExportsController < API::ApiController def download authorize @export + mime_type = if @export.extension == 'xlsx' + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + elsif @export.extension == 'csv' + 'text/csv' + else + 'application/octet-stream' + end if FileTest.exist?(@export.file) send_file File.join(Rails.root, @export.file), - type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + type: mime_type, disposition: 'attachment' else render text: I18n.t('errors.messages.export_not_found'), status: :not_found @@ -21,28 +28,8 @@ class API::ExportsController < API::ApiController def status authorize Export - export = Export.where(category: params[:category], export_type: params[:type], query: params[:query], key: params[:key]) - - if params[:category] == 'users' - case params[:type] - when 'subscriptions' - export = export.where('created_at > ?', Subscription.maximum('updated_at')) - when 'reservations' - export = export.where('created_at > ?', Reservation.maximum('updated_at')) - when 'members' - export = export.where('created_at > ?', User.with_role(:member).maximum('updated_at')) - else - raise ArgumentError, "Unknown export users/#{params[:type]}" - end - elsif params[:category] == 'availabilities' - case params[:type] - when 'index' - export = export.where('created_at > ?', [Availability.maximum('updated_at'), Reservation.maximum('updated_at')].max) - else - raise ArgumentError, "Unknown type availabilities/#{params[:type]}" - end - end - export = export.last + exports = Export.where(category: params[:category], export_type: params[:type], query: params[:query], key: params[:key]) + export = retrieve_last_export(exports, params[:category], params[:type]) if export.nil? || !FileTest.exist?(export.file) render json: { exists: false, id: nil }, status: :ok @@ -53,6 +40,39 @@ class API::ExportsController < API::ApiController private + def retrieve_last_export(export, category, type) + case category + when 'users' + case type + when 'subscriptions' + export = export.where('created_at > ?', Subscription.maximum('updated_at')) + when 'reservations' + export = export.where('created_at > ?', Reservation.maximum('updated_at')) + when 'members' + export = export.where('created_at > ?', User.with_role(:member).maximum('updated_at')) + else + raise ArgumentError, "Unknown export users/#{type}" + end + when 'availabilities' + case type + when 'index' + export = export.where('created_at > ?', [Availability.maximum('updated_at'), Reservation.maximum('updated_at')].max) + else + raise ArgumentError, "Unknown type availabilities/#{type}" + end + when 'accounting' + case type + when 'accounting-software' + export = export.where('created_at > ?', Invoice.maximum('updated_at')) + else + raise ArgumentError, "Unknown type accounting/#{type}" + end + else + raise ArgumentError, "Unknown category #{category}" + end + export.last + end + def set_export @export = Export.find(params[:id]) end diff --git a/app/models/export.rb b/app/models/export.rb index 32ab8ed68..74056d3a9 100644 --- a/app/models/export.rb +++ b/app/models/export.rb @@ -21,7 +21,7 @@ class Export < ActiveRecord::Base end def filename - "#{export_type}-#{id}_#{created_at.strftime('%d%m%Y')}.xlsx" + "#{export_type}-#{id}_#{created_at.strftime('%d%m%Y')}.#{extension}" end private @@ -34,6 +34,8 @@ class Export < ActiveRecord::Base UsersExportWorker.perform_async(id) when 'availabilities' AvailabilitiesExportWorker.perform_async(id) + when 'accounting' + AccountingExportWorker.perform_async(id) else raise NoMethodError, "Unknown export service for #{category}/#{export_type}" end diff --git a/app/policies/accounting_exports_policy.rb b/app/policies/accounting_exports_policy.rb new file mode 100644 index 000000000..378dbc2c7 --- /dev/null +++ b/app/policies/accounting_exports_policy.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# Check the access policies for API::AccountingExportsController +class AccountingExportsPolicy < ApplicationPolicy + def export? + user.admin? + end +end diff --git a/app/workers/accounting_export_worker.rb b/app/workers/accounting_export_worker.rb new file mode 100644 index 000000000..306698398 --- /dev/null +++ b/app/workers/accounting_export_worker.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# Asynchronously export the accounting data (Invoices & Avoirs) to an external accounting software +class AccountingExportWorker + include Sidekiq::Worker + + def perform(export_id) + export = Export.find(export_id) + + raise SecurityError, 'Not allowed to export' unless export.user.admin? + + data = JSON.parse(export.query) + service = AccountingExportService.new(export.file, data['columns'], data['encoding'], export.extension, export.key) + + service.export(data['start_date'], data['end_date']) + + NotificationCenter.call type: :notify_admin_export_complete, + receiver: export.user, + attached_object: export + end +end diff --git a/config/routes.rb b/config/routes.rb index e036624e5..2193dd9cc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -135,6 +135,8 @@ Rails.application.routes.draw do get 'last_closing_end', on: :collection get 'archive', action: 'download_archive', on: :member end + # export accounting data to csv or equivalent + post 'accounting/export' => 'accounting_exports#export' # i18n # regex allows using dots in URL for 'state' diff --git a/db/migrate/20190730085826_add_extension_to_export.rb b/db/migrate/20190730085826_add_extension_to_export.rb new file mode 100644 index 000000000..40e15a140 --- /dev/null +++ b/db/migrate/20190730085826_add_extension_to_export.rb @@ -0,0 +1,5 @@ +class AddExtensionToExport < ActiveRecord::Migration + def change + add_column :exports, :extension, :string, default: 'xlsx' + end +end diff --git a/db/schema.rb b/db/schema.rb index f1baff8ba..227076c17 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,12 +11,12 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20190606074801) do +ActiveRecord::Schema.define(version: 20190730085826) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" - enable_extension "unaccent" enable_extension "pg_trgm" + enable_extension "unaccent" create_table "abuses", force: :cascade do |t| t.integer "signaled_id" @@ -202,10 +202,11 @@ ActiveRecord::Schema.define(version: 20190606074801) do t.string "category" t.string "export_type" t.string "query" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "user_id" t.string "key" + t.string "extension", default: "xlsx" end add_index "exports", ["user_id"], name: "index_exports_on_user_id", using: :btree From a9ea4057f3d50d36b9aad1a62bccd1068f4ad5aa Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 30 Jul 2019 16:06:35 +0200 Subject: [PATCH 04/46] interface to customize accounting codes and labels --- .../controllers/admin/invoices.js.erb | 117 +++++++++++++++- app/assets/javascripts/router.js.erb | 15 +-- app/assets/stylesheets/modules/invoice.scss | 8 ++ .../admin/invoices/accountingExportModal.html | 10 ++ .../templates/admin/invoices/index.html.erb | 126 ++++++++++++++++++ app/models/setting.rb | 21 ++- app/services/accounting_export_service.rb | 35 +++-- app/workers/accounting_export_worker.rb | 4 +- config/locales/app.admin.en.yml | 41 ++++++ config/locales/app.admin.es.yml | 41 ++++++ config/locales/app.admin.fr.yml | 41 ++++++ config/locales/app.admin.pt.yml | 41 ++++++ 12 files changed, 469 insertions(+), 31 deletions(-) create mode 100644 app/assets/templates/admin/invoices/accountingExportModal.html diff --git a/app/assets/javascripts/controllers/admin/invoices.js.erb b/app/assets/javascripts/controllers/admin/invoices.js.erb index 3115f50f2..c18322289 100644 --- a/app/assets/javascripts/controllers/admin/invoices.js.erb +++ b/app/assets/javascripts/controllers/admin/invoices.js.erb @@ -76,6 +76,86 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I } }; + // Accounting codes + $scope.settings = { + journalCode: { + name: 'accounting_journal_code', + value: settings['accounting_journal_code'] + }, + clientCode: { + name: 'accounting_client_code', + value: settings['accounting_client_code'] + }, + clientLabel: { + name: 'accounting_client_label', + value: settings['accounting_client_label'] + }, + walletCode: { + name: 'accounting_wallet_code', + value: settings['accounting_wallet_code'] + }, + walletLabel: { + name: 'accounting_wallet_label', + value: settings['accounting_wallet_label'] + }, + vatCode: { + name: 'accounting_VAT_code', + value: settings['accounting_VAT_code'] + }, + vatLabel: { + name: 'accounting_VAT_label', + value: settings['accounting_VAT_label'] + }, + subscriptionCode: { + name: 'accounting_subscription_code', + value: settings['accounting_subscription_code'] + }, + subscriptionLabel: { + name: 'accounting_subscription_label', + value: settings['accounting_subscription_label'] + }, + machineCode: { + name: 'accounting_Machine_code', + value: settings['accounting_Machine_code'] + }, + machineLabel: { + name: 'accounting_Machine_label', + value: settings['accounting_Machine_label'] + }, + trainingCode: { + name: 'accounting_Training_code', + value: settings['accounting_Training_code'] + }, + trainingLabel: { + name: 'accounting_Training_label', + value: settings['accounting_Training_label'] + }, + eventCode: { + name: 'accounting_Event_code', + value: settings['accounting_Event_code'] + }, + eventLabel: { + name: 'accounting_Event_label', + value: settings['accounting_Event_label'] + }, + spaceCode: { + name: 'accounting_Space_code', + value: settings['accounting_Space_code'] + }, + spaceLabel: { + name: 'accounting_Space_label', + value: settings['accounting_Space_label'] + }, + couponCode: { + name: 'accounting_coupon_code', + value: settings['accounting_coupon_code'] + }, + couponLabel: { + name: 'accounting_coupon_label', + value: settings['accounting_coupon_label'] + } + }; + // Placeholding date for the invoice creation $scope.today = moment(); @@ -432,6 +512,14 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I }); } + $scope.toggleExportModal = function() { + $uibModal.open({ + templateUrl: '<%= asset_path "admin/invoices/accountingExportModal.html" %>', + controller: 'AccountingExportModalController', + size: 'lg' + }); + } + /** * Test if the given date is within a closed accounting period * @param date {Date} date to test @@ -446,6 +534,19 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I return false; } + /** + * Callback to save the setting value to the database + * @param setting {{value:*, name:string}} note that the value will be stringified + */ + $scope.save = function(setting) { + Setting.update( + { name: setting.name }, + { value: setting.value }, + function () { growl.success(_t('invoices.customization_of_SETTING_successfully_saved', { SETTING: _t(`invoices.${setting.name}`) })); }, + function (error) { console.log(error); } + ); + } + /* PRIVATE SCOPE */ /** @@ -791,7 +892,7 @@ Application.Controllers.controller('ClosePeriodModalController', ['$scope', '$ui }; /** - * Cancel the refund, dismiss the modal window + * Just dismiss the modal window */ $scope.cancel = function () { $uibModalInstance.dismiss('cancel'); }; @@ -803,3 +904,17 @@ Application.Controllers.controller('ClosePeriodModalController', ['$scope', '$ui } } ]); + +Application.Controllers.controller('AccountingExportModalController', ['$scope', '$uibModalInstance', function ($scope, $uibModalInstance) { + + /** + * Validate the close period creation + */ + $scope.ok = function () { + console.log('ok'); + }; + /** + * Just dismiss the modal window + */ + $scope.cancel = function () { $uibModalInstance.dismiss('cancel'); }; +}]); diff --git a/app/assets/javascripts/router.js.erb b/app/assets/javascripts/router.js.erb index 3616fac07..a4213db07 100644 --- a/app/assets/javascripts/router.js.erb +++ b/app/assets/javascripts/router.js.erb @@ -899,15 +899,12 @@ angular.module('application.router', ['ui.router']) resolve: { settings: ['Setting', function (Setting) { return Setting.query({ - names: `['invoice_legals', \ - 'invoice_text', \ - 'invoice_VAT-rate', \ - 'invoice_VAT-active', \ - 'invoice_order-nb', \ - 'invoice_code-value', \ - 'invoice_code-active', \ - 'invoice_reference', \ - 'invoice_logo']` }).$promise; + names: `['invoice_legals', 'invoice_text', 'invoice_VAT-rate', 'invoice_VAT-active', 'invoice_order-nb', 'invoice_code-value', \ + 'invoice_code-active', 'invoice_reference', 'invoice_logo', 'accounting_journal_code', 'accounting_client_code' \ + 'accounting_client_label', 'accounting_wallet_code', 'accounting_wallet_label', 'accounting_VAT_code', 'accounting_VAT_label', \ + 'accounting_subscription_code', 'accounting_subscription_label', 'accounting_Machine_code', 'accounting_Machine_label' \ + 'accounting_Training_code', 'accounting_Training_label', 'accounting_Event_code', 'accounting_Event_label' \ + 'accounting_Space_code', 'accounting_Space_label', 'accounting_coupon_code', 'accounting_coupon_label']` }).$promise; }], invoices: [ 'Invoice', function (Invoice) { return Invoice.list({ diff --git a/app/assets/stylesheets/modules/invoice.scss b/app/assets/stylesheets/modules/invoice.scss index d4904f49c..84c4c1a21 100644 --- a/app/assets/stylesheets/modules/invoice.scss +++ b/app/assets/stylesheets/modules/invoice.scss @@ -276,3 +276,11 @@ table.scrollable-3-cols { input.form-control.as-writable { background-color: white; } + +.accounting-codes .row { + margin-top: 2rem; + + button { + margin-top: 1em; + } +} diff --git a/app/assets/templates/admin/invoices/accountingExportModal.html b/app/assets/templates/admin/invoices/accountingExportModal.html new file mode 100644 index 000000000..d569e8d67 --- /dev/null +++ b/app/assets/templates/admin/invoices/accountingExportModal.html @@ -0,0 +1,10 @@ + + + diff --git a/app/assets/templates/admin/invoices/index.html.erb b/app/assets/templates/admin/invoices/index.html.erb index 6ab3ab14e..3ccfcb1de 100644 --- a/app/assets/templates/admin/invoices/index.html.erb +++ b/app/assets/templates/admin/invoices/index.html.erb @@ -12,6 +12,7 @@ @@ -201,6 +202,131 @@ + + + + + +
+
+
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+
+
diff --git a/app/models/setting.rb b/app/models/setting.rb index 517c2b912..88ce5c1bb 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -40,7 +40,26 @@ class Setting < ActiveRecord::Base visibility_yearly visibility_others display_name_enable - machines_sort_by] } + machines_sort_by + accounting_journal_code + accounting_client_code + accounting_client_label + accounting_wallet_code + accounting_wallet_label + accounting_VAT_code + accounting_VAT_label + accounting_subscription_code + accounting_subscription_label + accounting_Machine_code + accounting_Machine_label + accounting_Training_code + accounting_Training_label + accounting_Event_code + accounting_Event_label + accounting_Space_code + accounting_Space_label + accounting_coupon_code + accounting_coupon_label] } after_update :update_stylesheet, :notify_privacy_policy_changed if :value_changed? diff --git a/app/services/accounting_export_service.rb b/app/services/accounting_export_service.rb index 88a23d74b..75a1c8815 100644 --- a/app/services/accounting_export_service.rb +++ b/app/services/accounting_export_service.rb @@ -2,19 +2,18 @@ # 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 + attr_reader :encoding, :format, :separator, :journal_code, :date_format - def initialize(file, columns, encoding = 'UTF-8', format = 'CSV', separator = ';') - @file = file + def initialize(columns, encoding = 'UTF-8', format = 'CSV', separator = ';', date_format = '%d/%m/%Y') @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 + @journal_code = Setting.find_by(name: 'accounting-export_journal-code').value + @date_format = date_format @columns = columns end - def export(start_date, end_date) + def export(start_date, end_date, file) # build CVS content content = '' invoices = Invoice.where('created_at >= ? AND created_at <= ?', start_date, end_date).order('created_at ASC') @@ -47,8 +46,8 @@ class AccountingExportService row = '' columns.each do |column| case column - when :log_code - row << log_code + when :journal_code + row << journal_code when :date row << invoice.created_at.strftime(date_format) when :account_code @@ -81,8 +80,8 @@ class AccountingExportService row = '' columns.each do |column| case column - when :log_code - row << log_code + when :journal_code + row << journal_code when :date row << invoice.created_at.strftime(date_format) when :account_code @@ -116,8 +115,8 @@ class AccountingExportService row = '' columns.each do |column| case column - when :log_code - row << log_code + when :journal_code + row << journal_code when :date row << invoice.created_at.strftime(date_format) when :account_code @@ -152,8 +151,8 @@ class AccountingExportService row = '' columns.each do |column| case column - when :log_code - row << log_code + when :journal_code + row << journal_code when :date row << invoice.created_at.strftime(date_format) when :account_code @@ -183,17 +182,17 @@ class AccountingExportService def account(invoice, account, type = :code) case account when :client - Setting.find_by(name: "accounting-export_client-account-#{type}").value + Setting.find_by(name: "accounting_client_#{type}").value when :vat - Setting.find_by(name: "accounting-export_VAT-account-#{type}").value + Setting.find_by(name: "accounting_VAT_#{type}").value when :subscription return if invoice.invoiced_type != 'Subscription' - Setting.find_by(name: "accounting-export_subscription-account-#{type}").value + Setting.find_by(name: "accounting_subscription_#{type}").value when :reservation return if invoice.invoiced_type != 'Reservation' - Setting.find_by(name: "accounting-export_#{invoice.invoiced.reservable_type}-account-#{type}").value + Setting.find_by(name: "accounting_#{invoice.invoiced.reservable_type}_#{type}").value else puts "Unsupported account #{account}" end diff --git a/app/workers/accounting_export_worker.rb b/app/workers/accounting_export_worker.rb index 306698398..8bdf41fbd 100644 --- a/app/workers/accounting_export_worker.rb +++ b/app/workers/accounting_export_worker.rb @@ -10,9 +10,9 @@ class AccountingExportWorker raise SecurityError, 'Not allowed to export' unless export.user.admin? data = JSON.parse(export.query) - service = AccountingExportService.new(export.file, data['columns'], data['encoding'], export.extension, export.key) + service = AccountingExportService.new(data['columns'], data['encoding'], export.extension, export.key, data['date_format']) - service.export(data['start_date'], data['end_date']) + service.export(data['start_date'], data['end_date'], export.file) NotificationCenter.call type: :notify_admin_export_complete, receiver: export.user, diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index efd0f08cb..cfc49eb34 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -432,6 +432,47 @@ en: period_START_END_closed_success: "The accounting period from {{START}} to {{END}} has been successfully closed. Archive generation is running, you'll be notified when it's done." failed_to_close_period: "An error occurred, unable to close the accounting period" no_periods: "No closings for now" + accounting_codes: "Accounting codes" + accounting_journal_code: "Journal code" + general_journal_code: "Journal code" + accounting_client_code: "Customers code" + general_client_code: "Accounting code for all customers" + accounting_client_label: "Customers label" + general_client_label: "Account label for all customers" + accounting_wallet_code: "Wallet code" + general_wallet_code: "Accounting code for wallet credit" + accounting_wallet_label: "Wallet label" + general_wallet_label: "Account label for wallet credit" + accounting_vat_code: "VAT code" + general_vat_code: "Accounting code for VAT" + accounting_vat_label: "VAT label" + general_vat_label: "VAT account label" + accounting_subscription_code: "Subscriptions code" + general_subscription_code: "Accounting code for all subscriptions" + accounting_subscription_label: "Subscriptions label" + general_subscription_label: "Account label for all subscriptions" + accounting_machine_code: "Machines code" + general_machine_code: "Accounting code for all machines" + accounting_machine_label: "Machine label" + general_machine_label: "Account label for all machines" + accounting_training_code: "Trainings code" + general_training_code: "Accounting code for all trainings" + accounting_training_label: "Trainings label" + general_training_label: "Account label for all trainings" + accounting_event_code: "Events code" + general_event_code: "Accounting code for all events" + accounting_event_label: "Events label" + general_event_label: "Account label for all events" + accounting_space_code: "Space code" + general_space_code: "Accounting code for all spaces" + accounting_space_label: "Spaces label" + general_space_label: "Account label for all spaces" + accounting_coupon_code: "Coupons code" + general_coupon_code: "Accounting code for all coupons" + accounting_coupon_label: "Coupons label" + general_coupon_label: "Account label for all coupons" + customization_of_SETTING_successfully_saved: "Customization of the {{SETTING}} successfully saved." # angular interpolation + export_accounting_data: "Export accounting data" members: # management of users, labels, groups, and so on diff --git a/config/locales/app.admin.es.yml b/config/locales/app.admin.es.yml index 1b325ac0a..b3a64fa02 100644 --- a/config/locales/app.admin.es.yml +++ b/config/locales/app.admin.es.yml @@ -432,6 +432,47 @@ es: period_START_END_closed_success: "The accounting period from {{START}} to {{END}} has been successfully closed. Archive generation is running, you'll be notified when it's done." # translation_missing failed_to_close_period: "An error occurred, unable to close the accounting period" # translation_missing no_periods: "No closings for now" # translation_missing + accounting_codes: "Accounting codes" # translation_missing + accounting_journal_code: "Journal code" # translation_missing + general_journal_code: "Journal code" # translation_missing + accounting_client_code: "Customers code" # translation_missing + general_client_code: "Accounting code for all customers" # translation_missing + accounting_client_label: "Customers label" # translation_missing + general_client_label: "Account label for all customers" # translation_missing + accounting_wallet_code: "Wallet code" # translation_missing + general_wallet_code: "Accounting code for wallet credit" # translation_missing + accounting_wallet_label: "Wallet label" # translation_missing + general_wallet_label: "Account label for wallet credit" # translation_missing + accounting_vat_code: "VAT code" # translation_missing + general_vat_code: "Accounting code for VAT" # translation_missing + accounting_vat_label: "VAT label" # translation_missing + general_vat_label: "VAT account label" # translation_missing + accounting_subscription_code: "Subscriptions code" # translation_missing + general_subscription_code: "Accounting code for all subscriptions" # translation_missing + accounting_subscription_label: "Subscriptions label" # translation_missing + general_subscription_label: "Account label for all subscriptions" # translation_missing + accounting_machine_code: "Machines code" # translation_missing + general_machine_code: "Accounting code for all machines" # translation_missing + accounting_machine_label: "Machine label" # translation_missing + general_machine_label: "Account label for all machines" # translation_missing + accounting_training_code: "Trainings code" # translation_missing + general_training_code: "Accounting code for all trainings" # translation_missing + accounting_training_label: "Trainings label" # translation_missing + general_training_label: "Account label for all trainings" # translation_missing + accounting_event_code: "Events code" # translation_missing + general_event_code: "Accounting code for all events" # translation_missing + accounting_event_label: "Events label" # translation_missing + general_event_label: "Account label for all events" # translation_missing + accounting_space_code: "Space code" # translation_missing + general_space_code: "Accounting code for all spaces" # translation_missing + accounting_space_label: "Spaces label" # translation_missing + general_space_label: "Account label for all spaces" # translation_missing + accounting_coupon_code: "Coupons code" # translation_missing + general_coupon_code: "Accounting code for all coupons" # translation_missing + accounting_coupon_label: "Coupons label" # translation_missing + general_coupon_label: "Account label for all coupons" # translation_missing + customization_of_SETTING_successfully_saved: "Customization of the {{SETTING}} successfully saved." # angular interpolation # translation_missing + export_accounting_data: "Export accounting data" # translation_missing members: # management of users, labels, groups, and so on diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 9e07bba47..e8b9c80af 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -432,6 +432,47 @@ fr: period_START_END_closed_success: "La période comptable du {{START}} au {{END}} a bien été clôturée. La génération de l'archive est en cours, vous serez prévenu lorsque celle-ci sera terminée." failed_to_close_period: "Une erreur est survenue, impossible de clôturer la période comptable" no_periods: "Aucune clôture pour le moment" + accounting_codes: "Codes comptables" + accounting_journal_code: "Code journal" + general_journal_code: "Code journal" + accounting_client_code: "Code clients" + general_client_code: "Code comptable pour tous les clients" + accounting_client_label: "Libellé client" + general_client_label: "Libellé du compte pour tous les clients" + accounting_wallet_code: "Code porte-monnaie" + general_wallet_code: "Code comptable pour le crédit du porte-monnaie" + accounting_wallet_label: "Libellé porte-monnaie" + general_wallet_label: "Libellé du compte pour le crédit du porte-monnaie" + accounting_vat_code: "Code TVA" + general_vat_code: "Code comptable pour la TVA" + accounting_vat_label: "Libellé TVA" + general_vat_label: "Libellé du compte TVA" + accounting_subscription_code: "Code abonnements" + general_subscription_code: "Code comptable pour tous les abonnements" + accounting_subscription_label: "Libellé abonnements" + general_subscription_label: "Libellé du compte pour tous les abonnements" + accounting_machine_code: "Code machines" + general_machine_code: "Code comptable pour toutes les machines" + accounting_machine_label: "Libellé machine" + general_machine_label: "Libellé du compte pour toutes les machines" + accounting_training_code: "Code formations" + general_training_code: "Code comptable pour toutes les formations" + accounting_training_label: "Libellé formations" + general_training_label: "Libellé du compte pour toutes les formations" + accounting_event_code: "Code évènements" + general_event_code: "Code comptable pour tous les évènements" + accounting_event_label: "Libellé évènements" + general_event_label: "Libellé du compte pour tous les évènements" + accounting_space_code: "Code espaces" + general_space_code: "Code comptable pour tous les espaces" + accounting_space_label: "Libellé espaces" + general_space_label: "Libellé du compte pour tous les espaces" + accounting_coupon_code: "Code pour codes promo" + general_coupon_code: "Code comptable pour tous les codes promo" + accounting_coupon_label: "Libellé codes promo" + general_coupon_label: "Libellé du compte pour tous les codes promo" + customization_of_SETTING_successfully_saved: "La personnalisation de {{SETTING}} a bien été enregistrée." # angular interpolation + export_accounting_data: "Exporter les données comptables" members: # gestion des utilisateurs, des groupes, des étiquettes, etc. diff --git a/config/locales/app.admin.pt.yml b/config/locales/app.admin.pt.yml index 470d15baf..026a0ea2f 100755 --- a/config/locales/app.admin.pt.yml +++ b/config/locales/app.admin.pt.yml @@ -432,6 +432,47 @@ pt: period_START_END_closed_success: "The accounting period from {{START}} to {{END}} has been successfully closed. Archive generation is running, you'll be notified when it's done." # translation_missing failed_to_close_period: "An error occurred, unable to close the accounting period" # translation_missing no_periods: "No closings for now" # translation_missing + accounting_codes: "Accounting codes" # translation_missing + accounting_journal_code: "Journal code" # translation_missing + general_journal_code: "Journal code" # translation_missing + accounting_client_code: "Customers code" # translation_missing + general_client_code: "Accounting code for all customers" # translation_missing + accounting_client_label: "Customers label" # translation_missing + general_client_label: "Account label for all customers" # translation_missing + accounting_wallet_code: "Wallet code" # translation_missing + general_wallet_code: "Accounting code for wallet credit" # translation_missing + accounting_wallet_label: "Wallet label" # translation_missing + general_wallet_label: "Account label for wallet credit" # translation_missing + accounting_vat_code: "VAT code" # translation_missing + general_vat_code: "Accounting code for VAT" # translation_missing + accounting_vat_label: "VAT label" # translation_missing + general_vat_label: "VAT account label" # translation_missing + accounting_subscription_code: "Subscriptions code" # translation_missing + general_subscription_code: "Accounting code for all subscriptions" # translation_missing + accounting_subscription_label: "Subscriptions label" # translation_missing + general_subscription_label: "Account label for all subscriptions" # translation_missing + accounting_machine_code: "Machines code" # translation_missing + general_machine_code: "Accounting code for all machines" # translation_missing + accounting_machine_label: "Machine label" # translation_missing + general_machine_label: "Account label for all machines" # translation_missing + accounting_training_code: "Trainings code" # translation_missing + general_training_code: "Accounting code for all trainings" # translation_missing + accounting_training_label: "Trainings label" # translation_missing + general_training_label: "Account label for all trainings" # translation_missing + accounting_event_code: "Events code" # translation_missing + general_event_code: "Accounting code for all events" # translation_missing + accounting_event_label: "Events label" # translation_missing + general_event_label: "Account label for all events" # translation_missing + accounting_space_code: "Space code" # translation_missing + general_space_code: "Accounting code for all spaces" # translation_missing + accounting_space_label: "Spaces label" # translation_missing + general_space_label: "Account label for all spaces" # translation_missing + accounting_coupon_code: "Coupons code" # translation_missing + general_coupon_code: "Accounting code for all coupons" # translation_missing + accounting_coupon_label: "Coupons label" # translation_missing + general_coupon_label: "Account label for all coupons" # translation_missing + customization_of_SETTING_successfully_saved: "Customization of the {{SETTING}} successfully saved." # angular interpolation # translation_missing + export_accounting_data: "Export accounting data" # translation_missing members: # management of users, labels, groups, and so on From 22d84e86f55222b379e7d4a41d77745b95b0b3e6 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 31 Jul 2019 11:10:10 +0200 Subject: [PATCH 05/46] export modal --- .../controllers/admin/invoices.js.erb | 50 +++++++++++- app/assets/stylesheets/modules/invoice.scss | 24 ++++++ .../admin/invoices/accountingExportModal.html | 77 ++++++++++++++++++- 3 files changed, 148 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/controllers/admin/invoices.js.erb b/app/assets/javascripts/controllers/admin/invoices.js.erb index c18322289..712c3dc6f 100644 --- a/app/assets/javascripts/controllers/admin/invoices.js.erb +++ b/app/assets/javascripts/controllers/admin/invoices.js.erb @@ -516,7 +516,7 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I $uibModal.open({ templateUrl: '<%= asset_path "admin/invoices/accountingExportModal.html" %>', controller: 'AccountingExportModalController', - size: 'lg' + size: 'xl' }); } @@ -907,12 +907,60 @@ Application.Controllers.controller('ClosePeriodModalController', ['$scope', '$ui Application.Controllers.controller('AccountingExportModalController', ['$scope', '$uibModalInstance', function ($scope, $uibModalInstance) { + const SETTINGS = { + acd: { + format: 'CSV', + encoding: 'ISO-8859-1', + separator: ';', + dateFormat: '%d/%m/%Y', + columns: ['journal_code', 'date', 'account_code', 'account_label', 'piece', 'line_label', 'debit_origin', 'credit_origin', 'debit_euro', 'credit_euro', 'lettering'] + } + }; + + // binding to radio button "export to" + $scope.exportTarget = { + software: null, + startDate: null, + endDate: null, + settings: null + }; + + // AngularUI-Bootstrap datepicker parameters to define export dates range + $scope.datePicker = { + format: Fablab.uibDateFormat, + opened: { // default: datePickers are not shown + start: false, + end: false + }, + options: { + startingDay: Fablab.weekStartingDay + } + }; + /** * Validate the close period creation */ $scope.ok = function () { console.log('ok'); }; + + /** + * Callback to open/close one of the datepickers + * @param event {Object} see https://docs.angularjs.org/guide/expression#-event- + * @param picker {string} start | end + */ + $scope.toggleDatePicker = function(event, picker) { + event.preventDefault(); + $scope.datePicker.opened[picker] = !$scope.datePicker.opened[picker]; + }; + + /** + * Will fill the export settings, accordint to the selected software + */ + $scope.fillSettings = function() { + $scope.exportTarget.settings = SETTINGS[$scope.exportTarget.software]; + }; + /** * Just dismiss the modal window */ diff --git a/app/assets/stylesheets/modules/invoice.scss b/app/assets/stylesheets/modules/invoice.scss index 84c4c1a21..c438d7542 100644 --- a/app/assets/stylesheets/modules/invoice.scss +++ b/app/assets/stylesheets/modules/invoice.scss @@ -284,3 +284,27 @@ input.form-control.as-writable { margin-top: 1em; } } + +.modal-xl { + width: 900px; +} + +table.export-table-template { + margin-top: 10px; + + thead td { + width: 20px; + background-color: #227447; + color: white; + border-bottom: 2px solid black; + font-size: 13px; + font-weight: bold; + padding: 10px 5px; + line-height: 12px; + } + + tbody td { + border-right: 1px solid #d4d4d4; + height: 30px; + } +} diff --git a/app/assets/templates/admin/invoices/accountingExportModal.html b/app/assets/templates/admin/invoices/accountingExportModal.html index d569e8d67..ef9a8632a 100644 --- a/app/assets/templates/admin/invoices/accountingExportModal.html +++ b/app/assets/templates/admin/invoices/accountingExportModal.html @@ -2,9 +2,82 @@

{{ 'invoices.export_accounting_data' }}

From a7f68b59dd9be6773e49c021c06e5a43851eeccd Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 31 Jul 2019 12:00:52 +0200 Subject: [PATCH 06/46] connect the export modal to the api --- .../controllers/admin/invoices.js.erb | 26 +++++++++++++++++-- .../javascripts/services/accounting_export.js | 12 +++++++++ app/assets/javascripts/services/invoice.js | 4 +++ app/assets/stylesheets/app.components.scss | 4 +++ app/assets/stylesheets/modules/invoice.scss | 4 --- .../admin/invoices/accountingExportModal.html | 4 +-- .../api/accounting_exports_controller.rb | 2 +- app/controllers/api/invoices_controller.rb | 6 +++++ ..._policy.rb => accounting_export_policy.rb} | 2 +- app/policies/invoice_policy.rb | 4 +++ app/views/api/invoices/first.json.jbuilder | 1 + config/locales/app.admin.en.yml | 21 +++++++++++++++ config/locales/app.admin.es.yml | 21 +++++++++++++++ config/locales/app.admin.fr.yml | 21 +++++++++++++++ config/locales/app.admin.pt.yml | 21 +++++++++++++++ config/routes.rb | 1 + 16 files changed, 144 insertions(+), 10 deletions(-) create mode 100644 app/assets/javascripts/services/accounting_export.js rename app/policies/{accounting_exports_policy.rb => accounting_export_policy.rb} (73%) create mode 100644 app/views/api/invoices/first.json.jbuilder diff --git a/app/assets/javascripts/controllers/admin/invoices.js.erb b/app/assets/javascripts/controllers/admin/invoices.js.erb index 712c3dc6f..5993fef8d 100644 --- a/app/assets/javascripts/controllers/admin/invoices.js.erb +++ b/app/assets/javascripts/controllers/admin/invoices.js.erb @@ -905,7 +905,7 @@ Application.Controllers.controller('ClosePeriodModalController', ['$scope', '$ui } ]); -Application.Controllers.controller('AccountingExportModalController', ['$scope', '$uibModalInstance', function ($scope, $uibModalInstance) { +Application.Controllers.controller('AccountingExportModalController', ['$scope', '$uibModalInstance', 'Invoice', 'AccountingExport', function ($scope, $uibModalInstance, Invoice, AccountingExport) { const SETTINGS = { acd: { @@ -917,6 +917,8 @@ Application.Controllers.controller('AccountingExportModalController', ['$scope', } }; + /* PUBLIC SCOPE */ + // binding to radio button "export to" $scope.exportTarget = { software: null, @@ -937,11 +939,16 @@ Application.Controllers.controller('AccountingExportModalController', ['$scope', } }; + // Date of the first invoice + $scope.firstInvoice = null; + /** * Validate the close period creation */ $scope.ok = function () { - console.log('ok'); + AccountingExport.export($scope.exportTarget, function(res) { + $uibModalInstance.close(res); + }); }; /** @@ -965,4 +972,19 @@ Application.Controllers.controller('AccountingExportModalController', ['$scope', * Just dismiss the modal window */ $scope.cancel = function () { $uibModalInstance.dismiss('cancel'); }; + + /* PRIVATE SCOPE */ + + /** + * Kind of constructor: these actions will be realized first when the controller is loaded + */ + const initialize = function () { + // if the invoice was payed with stripe, allow to refund through stripe + Invoice.first(function (data) { + $scope.firstInvoice = data.date; + }); + }; + + // !!! MUST BE CALLED AT THE END of the controller + return initialize(); }]); diff --git a/app/assets/javascripts/services/accounting_export.js b/app/assets/javascripts/services/accounting_export.js new file mode 100644 index 000000000..45785cca6 --- /dev/null +++ b/app/assets/javascripts/services/accounting_export.js @@ -0,0 +1,12 @@ +'use strict'; + +Application.Services.factory('AccountingExport', ['$resource', function ($resource) { + return $resource('/api/accounting', + {}, { + export: { + method: 'POST', + url: '/api/accounting/export' + } + } + ); +}]); diff --git a/app/assets/javascripts/services/invoice.js b/app/assets/javascripts/services/invoice.js index d2cc57795..ab38a0e6a 100644 --- a/app/assets/javascripts/services/invoice.js +++ b/app/assets/javascripts/services/invoice.js @@ -10,6 +10,10 @@ Application.Services.factory('Invoice', ['$resource', function ($resource) { url: '/api/invoices/list', method: 'POST', isArray: true + }, + first: { + url: '/api/invoices/first', + method: 'GET' } } ); diff --git a/app/assets/stylesheets/app.components.scss b/app/assets/stylesheets/app.components.scss index d58a57afb..f477fd3dc 100644 --- a/app/assets/stylesheets/app.components.scss +++ b/app/assets/stylesheets/app.components.scss @@ -65,6 +65,10 @@ height: 100%; } +.modal-xl { + width: 900px; +} + // component card .card { position: relative; diff --git a/app/assets/stylesheets/modules/invoice.scss b/app/assets/stylesheets/modules/invoice.scss index c438d7542..fb524d20e 100644 --- a/app/assets/stylesheets/modules/invoice.scss +++ b/app/assets/stylesheets/modules/invoice.scss @@ -285,10 +285,6 @@ input.form-control.as-writable { } } -.modal-xl { - width: 900px; -} - table.export-table-template { margin-top: 10px; diff --git a/app/assets/templates/admin/invoices/accountingExportModal.html b/app/assets/templates/admin/invoices/accountingExportModal.html index ef9a8632a..1c3f96c75 100644 --- a/app/assets/templates/admin/invoices/accountingExportModal.html +++ b/app/assets/templates/admin/invoices/accountingExportModal.html @@ -16,7 +16,7 @@ uib-datepicker-popup="{{datePicker.format}}" datepicker-options="datePicker.options" is-open="datePicker.opened.start" - min-date="lastClosingEnd" + min-date="firstInvoice" placeholder="{{datePicker.format}}" ng-click="toggleDatePicker($event, 'start')" required/> @@ -34,7 +34,7 @@ uib-datepicker-popup="{{datePicker.format}}" datepicker-options="datePicker.options" is-open="datePicker.opened.end" - min-date="lastClosingEnd" + min-date="exportTarget.startDate || firstInvoice" placeholder="{{datePicker.format}}" ng-click="toggleDatePicker($event, 'end')" required/> diff --git a/app/controllers/api/accounting_exports_controller.rb b/app/controllers/api/accounting_exports_controller.rb index 9efa1a321..82d14edcf 100644 --- a/app/controllers/api/accounting_exports_controller.rb +++ b/app/controllers/api/accounting_exports_controller.rb @@ -6,7 +6,7 @@ class API::AccountingExportsController < API::ApiController before_action :authenticate_user! def export - authorize :export + authorize :accounting_export export = Export.where(category: 'accounting', export_type: 'accounting-software') .where('created_at > ?', Invoice.maximum('updated_at')) diff --git a/app/controllers/api/invoices_controller.rb b/app/controllers/api/invoices_controller.rb index 8fba607db..84a18a5e0 100644 --- a/app/controllers/api/invoices_controller.rb +++ b/app/controllers/api/invoices_controller.rb @@ -51,6 +51,12 @@ class API::InvoicesController < API::ApiController end end + def first + authorize Invoice + invoice = Invoice.order(:created_at).first + @first = invoice&.created_at + end + private def avoir_params diff --git a/app/policies/accounting_exports_policy.rb b/app/policies/accounting_export_policy.rb similarity index 73% rename from app/policies/accounting_exports_policy.rb rename to app/policies/accounting_export_policy.rb index 378dbc2c7..402696dea 100644 --- a/app/policies/accounting_exports_policy.rb +++ b/app/policies/accounting_export_policy.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Check the access policies for API::AccountingExportsController -class AccountingExportsPolicy < ApplicationPolicy +class AccountingExportPolicy < ApplicationPolicy def export? user.admin? end diff --git a/app/policies/invoice_policy.rb b/app/policies/invoice_policy.rb index 1227f21cd..1f2014434 100644 --- a/app/policies/invoice_policy.rb +++ b/app/policies/invoice_policy.rb @@ -14,4 +14,8 @@ class InvoicePolicy < ApplicationPolicy def list? user.admin? end + + def first? + user.admin? + end end diff --git a/app/views/api/invoices/first.json.jbuilder b/app/views/api/invoices/first.json.jbuilder new file mode 100644 index 000000000..f0e28831b --- /dev/null +++ b/app/views/api/invoices/first.json.jbuilder @@ -0,0 +1 @@ +json.date @first diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index cfc49eb34..28d9ac04b 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -473,6 +473,27 @@ en: general_coupon_label: "Account label for all coupons" customization_of_SETTING_successfully_saved: "Customization of the {{SETTING}} successfully saved." # angular interpolation export_accounting_data: "Export accounting data" + export_to: "Export to the accounting software" + acd: "ACD" + export_form_date: "Export from" + export_to_date: "Export until" + format: "File format" + encoding: "Encoding" + separator: "Separator" + dateFormat: "Date format" + columns: "Columns" + exportColumns: + journal_code: "Journal code" + date: "Entry date" + account_code: "Account code" + account_label: "Account label" + piece: "Document" + line_label: "Entry label" + debit_origin: "Origin debit" + credit_origin: "Origin credit" + debit_euro: "Euro debit" + credit_euro: "Euro credit" + lettering: "Lettering" members: # management of users, labels, groups, and so on diff --git a/config/locales/app.admin.es.yml b/config/locales/app.admin.es.yml index b3a64fa02..4e78cc7d5 100644 --- a/config/locales/app.admin.es.yml +++ b/config/locales/app.admin.es.yml @@ -473,6 +473,27 @@ es: general_coupon_label: "Account label for all coupons" # translation_missing customization_of_SETTING_successfully_saved: "Customization of the {{SETTING}} successfully saved." # angular interpolation # translation_missing export_accounting_data: "Export accounting data" # translation_missing + export_to: "Export to the accounting software" # translation_missing + acd: "ACD" # translation_missing + export_form_date: "Export from" # translation_missing + export_to_date: "Export until" # translation_missing + format: "File format" # translation_missing + encoding: "Encoding" # translation_missing + separator: "Separator" # translation_missing + dateFormat: "Date format" # translation_missing + columns: "Columns" # translation_missing + exportColumns: # translation_missing + journal_code: "Journal code" # translation_missing + date: "Entry date" # translation_missing + account_code: "Account code" # translation_missing + account_label: "Account label" # translation_missing + piece: "Document" # translation_missing + line_label: "Entry label" # translation_missing + debit_origin: "Origin debit" # translation_missing + credit_origin: "Origin credit" # translation_missing + debit_euro: "Euro debit" # translation_missing + credit_euro: "Euro credit" # translation_missing + lettering: "Lettering" # translation_missing members: # management of users, labels, groups, and so on diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index e8b9c80af..884f204ef 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -473,6 +473,27 @@ fr: general_coupon_label: "Libellé du compte pour tous les codes promo" customization_of_SETTING_successfully_saved: "La personnalisation de {{SETTING}} a bien été enregistrée." # angular interpolation export_accounting_data: "Exporter les données comptables" + export_to: "Exporter vers le logiciel comptable" + acd: "ACD" + export_form_date: "Exporter depuis le" + export_to_date: "Exporter jusqu'au" + format: "Format de fichier" + encoding: "Encodage" + separator: "Séparateur" + dateFormat: "Format de date" + columns: "Colonnes" + exportColumns: + journal_code: "Code journal" + date: "Date écriture" + account_code: "Code compte" + account_label: "Intitulé compte" + piece: "Pièce" + line_label: "Libellé écriture" + debit_origin: "Débit origine" + credit_origin: "Crédit origine" + debit_euro: "Débit euro" + credit_euro: "Crédit euro" + lettering: "Lettrage" members: # gestion des utilisateurs, des groupes, des étiquettes, etc. diff --git a/config/locales/app.admin.pt.yml b/config/locales/app.admin.pt.yml index 026a0ea2f..3d823bff4 100755 --- a/config/locales/app.admin.pt.yml +++ b/config/locales/app.admin.pt.yml @@ -473,6 +473,27 @@ pt: general_coupon_label: "Account label for all coupons" # translation_missing customization_of_SETTING_successfully_saved: "Customization of the {{SETTING}} successfully saved." # angular interpolation # translation_missing export_accounting_data: "Export accounting data" # translation_missing + export_to: "Export to the accounting software" # translation_missing + acd: "ACD" # translation_missing + export_form_date: "Export from" # translation_missing + export_to_date: "Export until" # translation_missing + format: "File format" # translation_missing + encoding: "Encoding" # translation_missing + separator: "Separator" # translation_missing + dateFormat: "Date format" # translation_missing + columns: "Columns" # translation_missing + exportColumns: # translation_missing + journal_code: "Journal code" # translation_missing + date: "Entry date" # translation_missing + account_code: "Account code" # translation_missing + account_label: "Account label" # translation_missing + piece: "Document" # translation_missing + line_label: "Entry label" # translation_missing + debit_origin: "Origin debit" # translation_missing + credit_origin: "Origin credit" # translation_missing + debit_euro: "Euro debit" # translation_missing + credit_euro: "Euro credit" # translation_missing + lettering: "Lettering" # translation_missing members: # management of users, labels, groups, and so on diff --git a/config/routes.rb b/config/routes.rb index 2193dd9cc..3b711b22e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -106,6 +106,7 @@ Rails.application.routes.draw do resources :invoices, only: %i[index show create] do get 'download', action: 'download', on: :member post 'list', action: 'list', on: :collection + get 'first', action: 'first', on: :collection end # for admin From fa6a54a422bfbf62f37fe104483787c9ab12d518 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 31 Jul 2019 12:37:31 +0200 Subject: [PATCH 07/46] export notifications --- .../javascripts/controllers/admin/invoices.js.erb | 6 ++++-- app/controllers/api/accounting_exports_controller.rb | 10 +++++++--- .../notify_admin_export_complete.html.erb | 4 ++-- config/locales/app.admin.en.yml | 1 + config/locales/app.admin.es.yml | 1 + config/locales/app.admin.fr.yml | 1 + config/locales/app.admin.pt.yml | 3 ++- config/locales/en.yml | 6 +++++- config/locales/es.yml | 4 ++++ config/locales/fr.yml | 1 + config/locales/mails.en.yml | 4 ++++ config/locales/mails.es.yml | 4 ++++ config/locales/mails.fr.yml | 6 +++++- config/locales/mails.pt.yml | 4 ++++ config/locales/pt.yml | 4 ++++ 15 files changed, 49 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/controllers/admin/invoices.js.erb b/app/assets/javascripts/controllers/admin/invoices.js.erb index 5993fef8d..8d5dc9c66 100644 --- a/app/assets/javascripts/controllers/admin/invoices.js.erb +++ b/app/assets/javascripts/controllers/admin/invoices.js.erb @@ -905,11 +905,12 @@ Application.Controllers.controller('ClosePeriodModalController', ['$scope', '$ui } ]); -Application.Controllers.controller('AccountingExportModalController', ['$scope', '$uibModalInstance', 'Invoice', 'AccountingExport', function ($scope, $uibModalInstance, Invoice, AccountingExport) { +Application.Controllers.controller('AccountingExportModalController', ['$scope', '$uibModalInstance', 'Invoice', 'AccountingExport', 'growl', '_t', + function ($scope, $uibModalInstance, Invoice, AccountingExport, growl, _t) { const SETTINGS = { acd: { - format: 'CSV', + format: 'csv', encoding: 'ISO-8859-1', separator: ';', dateFormat: '%d/%m/%Y', @@ -947,6 +948,7 @@ Application.Controllers.controller('AccountingExportModalController', ['$scope', */ $scope.ok = function () { AccountingExport.export($scope.exportTarget, function(res) { + growl.info(_t('invoices.export_is_running')) $uibModalInstance.close(res); }); }; diff --git a/app/controllers/api/accounting_exports_controller.rb b/app/controllers/api/accounting_exports_controller.rb index 82d14edcf..2c64e294c 100644 --- a/app/controllers/api/accounting_exports_controller.rb +++ b/app/controllers/api/accounting_exports_controller.rb @@ -16,9 +16,13 @@ class API::AccountingExportsController < API::ApiController category: 'accounting', export_type: 'accounting-software', user: current_user, - extension: params[:extension], - query: params[:query], - key: params[:separator] + extension: params[:settings][:format], + query: { + columns: params[:settings][:columns], + encoding: params[:settings][:encoding], + date_format: params[:settings][:dateFormat] + }.to_json, + key: params[:settings][:separator] ) if @export.save render json: { export_id: @export.id }, status: :ok diff --git a/app/views/notifications_mailer/notify_admin_export_complete.html.erb b/app/views/notifications_mailer/notify_admin_export_complete.html.erb index 9097d833c..03731e24e 100644 --- a/app/views/notifications_mailer/notify_admin_export_complete.html.erb +++ b/app/views/notifications_mailer/notify_admin_export_complete.html.erb @@ -5,6 +5,6 @@ <%= t(".body.#{@attached_object.category}_#{@attached_object.export_type}") %>.

- <%= t('.body.click_to_download') %> + <%= t('.body.click_to_download', TYPE: t(".body.file_type.#{@attached_object.extension}")) %> <%=link_to( t('.body.here'), "#{root_url}api/exports/#{@attached_object.id}/download", target: "_blank" )%> -

\ No newline at end of file +

diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index 28d9ac04b..eb7feddaa 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -474,6 +474,7 @@ en: customization_of_SETTING_successfully_saved: "Customization of the {{SETTING}} successfully saved." # angular interpolation export_accounting_data: "Export accounting data" export_to: "Export to the accounting software" + 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" diff --git a/config/locales/app.admin.es.yml b/config/locales/app.admin.es.yml index 4e78cc7d5..a574bf95d 100644 --- a/config/locales/app.admin.es.yml +++ b/config/locales/app.admin.es.yml @@ -474,6 +474,7 @@ es: customization_of_SETTING_successfully_saved: "Customization of the {{SETTING}} successfully saved." # angular interpolation # translation_missing export_accounting_data: "Export accounting data" # translation_missing export_to: "Export to the accounting software" # translation_missing + export_is_running: "Exportando, será notificado cuando esté listo." acd: "ACD" # translation_missing export_form_date: "Export from" # translation_missing export_to_date: "Export until" # translation_missing diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 884f204ef..a1b7f1f2d 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -474,6 +474,7 @@ fr: customization_of_SETTING_successfully_saved: "La personnalisation de {{SETTING}} a bien été enregistrée." # angular interpolation export_accounting_data: "Exporter les données comptables" export_to: "Exporter vers le logiciel comptable" + export_is_running: "L'export est en cours. Vous serez notifié lorsqu'il sera prêt." acd: "ACD" export_form_date: "Exporter depuis le" export_to_date: "Exporter jusqu'au" diff --git a/config/locales/app.admin.pt.yml b/config/locales/app.admin.pt.yml index 3d823bff4..4871488c6 100755 --- a/config/locales/app.admin.pt.yml +++ b/config/locales/app.admin.pt.yml @@ -471,9 +471,10 @@ pt: general_coupon_code: "Accounting code for all coupons" # translation_missing accounting_coupon_label: "Coupons label" # translation_missing general_coupon_label: "Account label for all coupons" # translation_missing - customization_of_SETTING_successfully_saved: "Customization of the {{SETTING}} successfully saved." # angular interpolation # translation_missing + customization_of_SETTING_successfully_saved: "Customization of the {{SETTING}} successfully saved." # angular interpolation # translation_missing export_accounting_data: "Export accounting data" # translation_missing export_to: "Export to the accounting software" # translation_missing + export_is_running: "A Exportação está em andamento. Você será notificado quando terminar." acd: "ACD" # translation_missing export_form_date: "Export from" # translation_missing export_to_date: "Export until" # translation_missing diff --git a/config/locales/en.yml b/config/locales/en.yml index 92280f134..7f585140f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -113,8 +113,8 @@ en: by_cheque: "by cheque" by_transfer: "by transfer" by_cash: "by cash" - no_refund: "No refund" by_wallet: "by wallet" + no_refund: "No refund" settlement_by_debit_card: "Settlement by debit card" settlement_done_at_the_reception: "Settlement done at the reception" settlement_by_wallet: "Settlement by wallet" @@ -126,6 +126,9 @@ en: subscription_of_NAME_extended_starting_from_STARTDATE_until_ENDDATE: "Subscription of %{NAME} extended (Free days) starting from %{STARTDATE} until %{ENDDATE}" and: 'and' + accounting_export: + VAT: 'VAT' + trainings: # training availabilities i_ve_reserved: "I've reserved" @@ -307,6 +310,7 @@ en: users_subscriptions: "of the subscriptions' list" users_reservations: "of the reservations' list" availabilities_index: "of the reservations availabilities" + accounting_accounting-software: "of the accounting data" is_over: "is over." download_here: "Download here" notify_member_about_coupon: diff --git a/config/locales/es.yml b/config/locales/es.yml index 26bf513ea..acd3968da 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -126,6 +126,9 @@ es: subscription_of_NAME_extended_starting_from_STARTDATE_until_ENDDATE: "Subscripción de %{NAME} extendida (Free days) empezando desde %{STARTDATE} hasta %{ENDDATE}" and: 'y' + accounting_export: + VAT: 'IVA' + trainings: # training availabilities i_ve_reserved: "he reservado" @@ -307,6 +310,7 @@ es: users_subscriptions: "de la lista de suscripciones" users_reservations: "de la lista de reservas" availabilities_index: "de las reservas disponibles" + accounting_accounting-software: "de los datos contables" is_over: "se ha acabado." download_here: "Descargar aquí" notify_member_about_coupon: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 2110ff832..c7c017ddc 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -310,6 +310,7 @@ fr: users_subscriptions: "de la liste des abonnements" users_reservations: "de la liste des réservations" availabilities_index: "des disponibilités de réservations" + accounting_accounting-software: "des données comptables" is_over: "est terminé." download_here: "Téléchargez ici" notify_member_about_coupon: diff --git a/config/locales/mails.en.yml b/config/locales/mails.en.yml index c37b278e5..eb53b018e 100644 --- a/config/locales/mails.en.yml +++ b/config/locales/mails.en.yml @@ -267,8 +267,12 @@ en: users_subscriptions: "of the subscriptions' list" users_reservations: "of the reservations' list" availabilities_index: "of the reservations availabilities" + accounting_accounting-software: "of the accounting data" click_to_download: "Excel file generated successfully. To download it, click" here: "here" + file_type: + xlsx: "Excel" + csv: "CSV" notify_member_about_coupon: subject: "Coupon" diff --git a/config/locales/mails.es.yml b/config/locales/mails.es.yml index c001b39a1..3fe911bed 100644 --- a/config/locales/mails.es.yml +++ b/config/locales/mails.es.yml @@ -266,8 +266,12 @@ es: users_subscriptions: "de la lista de suscripciones" users_reservations: "de la lista de reservas" availabilities_index: "de las reservas disponibles" + accounting_accounting-software: "de los datos contables" click_to_download: " archivo Excel generado correctamente. Para descargarlo, haga clic " here: "aquí" + file_type: + xlsx: "Excel" + csv: "CSV" notify_member_about_coupon: subject: "Cupón" diff --git a/config/locales/mails.fr.yml b/config/locales/mails.fr.yml index f74d337e0..a21e6b179 100644 --- a/config/locales/mails.fr.yml +++ b/config/locales/mails.fr.yml @@ -267,8 +267,12 @@ fr: users_subscriptions: "de la liste des abonnements" users_reservations: "de la liste des réservations" availabilities_index: "des disponibilités de réservations" - click_to_download: "La génération est terminée. Pour télécharger le fichier Excel, cliquez" + accounting_accounting-software: "des données comptables" + click_to_download: "La génération est terminée. Pour télécharger le fichier %{TYPE}, cliquez" here: "ici" + file_type: + xlsx: "Excel" + csv: "CSV" notify_member_about_coupon: subject: "Code promo" diff --git a/config/locales/mails.pt.yml b/config/locales/mails.pt.yml index eba1cb9de..b6a6ea03c 100755 --- a/config/locales/mails.pt.yml +++ b/config/locales/mails.pt.yml @@ -267,8 +267,12 @@ pt: users_subscriptions: "da lista de assinaturas" users_reservations: "da lista de reservas" availabilities_index: "as reservas disponíveis" + accounting_accounting-software: "de dados contábeis" click_to_download: "Arquivo do Excel gerado com êxito. Para fazer o download, clique" here: "aqui" + file_type: + xlsx: "Excel" + csv: "CSV" notify_member_about_coupon: subject: "Cupom" diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 481575605..87bebdee2 100755 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -126,6 +126,9 @@ pt: subscription_of_NAME_extended_starting_from_STARTDATE_until_ENDDATE: "Assinatura de %{NAME} estendida (dias livres) a partir de% STARTDATE até %{ENDDATE}" and: 'e' + accounting_export: + VAT: 'IVA' + trainings: # training availabilities i_ve_reserved: "Eu reservei" @@ -307,6 +310,7 @@ pt: users_subscriptions: "da lista de assinaturas" users_reservations: "da lista de reservas" availabilities_index: "de reservas disponíveis" + accounting_accounting-software: "de dados contábeis" is_over: "está finalizado." download_here: "Baixe aqui" notify_member_about_coupon: From 12d8c65fa2b8bca6d3bfebde01f833e560869dd3 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 31 Jul 2019 15:47:02 +0200 Subject: [PATCH 08/46] async generation of export and download cached csv using hidden iframe --- .../controllers/admin/invoices.js.erb | 47 +++++++++++++++++-- .../javascripts/services/accounting_export.js | 12 ----- .../admin/invoices/accountingExportModal.html | 9 +++- .../templates/admin/invoices/index.html.erb | 3 +- .../api/accounting_exports_controller.rb | 10 ++-- app/controllers/api/exports_controller.rb | 8 +++- 6 files changed, 62 insertions(+), 27 deletions(-) delete mode 100644 app/assets/javascripts/services/accounting_export.js diff --git a/app/assets/javascripts/controllers/admin/invoices.js.erb b/app/assets/javascripts/controllers/admin/invoices.js.erb index 8d5dc9c66..995d9f844 100644 --- a/app/assets/javascripts/controllers/admin/invoices.js.erb +++ b/app/assets/javascripts/controllers/admin/invoices.js.erb @@ -905,8 +905,10 @@ Application.Controllers.controller('ClosePeriodModalController', ['$scope', '$ui } ]); -Application.Controllers.controller('AccountingExportModalController', ['$scope', '$uibModalInstance', 'Invoice', 'AccountingExport', 'growl', '_t', - function ($scope, $uibModalInstance, Invoice, AccountingExport, growl, _t) { +Application.Controllers.controller('AccountingExportModalController', ['$scope', '$uibModalInstance', 'Invoice', 'Export', 'CSRF', 'growl', '_t', + function ($scope, $uibModalInstance, Invoice, Export, CSRF, growl, _t) { + // Retrieve Anti-CSRF tokens from cookies + CSRF.setMetaTags(); const SETTINGS = { acd: { @@ -920,6 +922,18 @@ Application.Controllers.controller('AccountingExportModalController', ['$scope', /* PUBLIC SCOPE */ + // API URL where the form will be posted + $scope.actionUrl = '/api/accounting/export'; + + // Form action on the above URL + $scope.method = 'post'; + + // Anti-CSRF token to inject into the download form + $scope.csrfToken = angular.element('meta[name="csrf-token"]')[0].content; + + // API request body to generate the export + $scope.query = null; + // binding to radio button "export to" $scope.exportTarget = { software: null, @@ -947,8 +961,13 @@ Application.Controllers.controller('AccountingExportModalController', ['$scope', * Validate the close period creation */ $scope.ok = function () { - AccountingExport.export($scope.exportTarget, function(res) { - growl.info(_t('invoices.export_is_running')) + const statusQry = mkQuery(); + $scope.query = statusQry; + + Export.status(statusQry).then(function (res) { + if (!res.data.exists) { + growl.success(_t('invoices.export_is_running')); + } $uibModalInstance.close(res); }); }; @@ -964,7 +983,7 @@ Application.Controllers.controller('AccountingExportModalController', ['$scope', }; /** - * Will fill the export settings, accordint to the selected software + * Will fill the export settings, according to the selected software */ $scope.fillSettings = function() { $scope.exportTarget.settings = SETTINGS[$scope.exportTarget.software]; @@ -987,6 +1006,24 @@ Application.Controllers.controller('AccountingExportModalController', ['$scope', }); }; + /** + * Prepare the query for the export API + * @returns {{extension: *, query: *, category: string, type: string, key: *}} + */ + const mkQuery = function() { + return { + category: 'accounting', + type: 'accounting-software', + extension: $scope.exportTarget.settings.format, + key: $scope.exportTarget.settings.separator, + query: JSON.stringify({ + columns: $scope.exportTarget.settings.columns, + encoding: $scope.exportTarget.settings.encoding, + date_format: $scope.exportTarget.settings.dateFormat + }) + }; + } + // !!! MUST BE CALLED AT THE END of the controller return initialize(); }]); diff --git a/app/assets/javascripts/services/accounting_export.js b/app/assets/javascripts/services/accounting_export.js deleted file mode 100644 index 45785cca6..000000000 --- a/app/assets/javascripts/services/accounting_export.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -Application.Services.factory('AccountingExport', ['$resource', function ($resource) { - return $resource('/api/accounting', - {}, { - export: { - method: 'POST', - url: '/api/accounting/export' - } - } - ); -}]); diff --git a/app/assets/templates/admin/invoices/accountingExportModal.html b/app/assets/templates/admin/invoices/accountingExportModal.html index 1c3f96c75..53cac06eb 100644 --- a/app/assets/templates/admin/invoices/accountingExportModal.html +++ b/app/assets/templates/admin/invoices/accountingExportModal.html @@ -78,6 +78,13 @@ diff --git a/app/assets/templates/admin/invoices/index.html.erb b/app/assets/templates/admin/invoices/index.html.erb index 3ccfcb1de..9c3852671 100644 --- a/app/assets/templates/admin/invoices/index.html.erb +++ b/app/assets/templates/admin/invoices/index.html.erb @@ -13,6 +13,7 @@ @@ -117,7 +118,7 @@
-