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 @@
+
+
+
hello
+
+
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 @@