diff --git a/app/assets/javascripts/controllers/admin/invoices.js.erb b/app/assets/javascripts/controllers/admin/invoices.js.erb
index cd0bd622f..33dde1fb4 100644
--- a/app/assets/javascripts/controllers/admin/invoices.js.erb
+++ b/app/assets/javascripts/controllers/admin/invoices.js.erb
@@ -676,8 +676,8 @@ Application.Controllers.controller('AvoirModalController', ['$scope', '$uibModal
/**
* Controller used in the modal window allowing an admin to close an accounting period
*/
-Application.Controllers.controller('ClosePeriodModalController', ['$scope', '$uibModalInstance', '$window', 'Invoice', 'AccountingPeriod', 'periods', 'lastClosingEnd','dialogs', 'growl', '_t',
- function ($scope, $uibModalInstance, $window, Invoice, AccountingPeriod, periods, lastClosingEnd, dialogs, growl, _t) {
+Application.Controllers.controller('ClosePeriodModalController', ['$scope', '$uibModalInstance', '$window', '$sce', 'Invoice', 'AccountingPeriod', 'periods', 'lastClosingEnd','dialogs', 'growl', '_t',
+ function ($scope, $uibModalInstance, $window, $sce, Invoice, AccountingPeriod, periods, lastClosingEnd, dialogs, growl, _t) {
const YESTERDAY = moment.utc({ h: 0, m: 0, s: 0, ms: 0 }).subtract(1, 'day').toDate();
const LAST_CLOSING = moment.utc(lastClosingEnd.last_end_date).toDate();
const MAX_END = moment.utc(lastClosingEnd.last_end_date).add(1, 'year').subtract(1, 'day').toDate();
@@ -734,9 +734,15 @@ Application.Controllers.controller('ClosePeriodModalController', ['$scope', '$ui
object () {
return {
title: _t('invoices.confirmation_required'),
- msg: _t(
- 'invoices.confirm_close_START_END',
- { START: moment.utc($scope.period.start_at).format('LL'), END: moment.utc($scope.period.end_at).format('LL') }
+ msg: $sce.trustAsHtml(
+ _t(
+ 'invoices.confirm_close_START_END',
+ { START: moment.utc($scope.period.start_at).format('LL'), END: moment.utc($scope.period.end_at).format('LL') }
+ )
+ + '
'
+ + _t('invoices.period_must_match_fiscal_year')
+ + '
'
+ + _t('invoices.this_may_take_a_while')
)
};
}
diff --git a/app/models/accounting_period.rb b/app/models/accounting_period.rb
index 5178e6255..0ee4e389c 100644
--- a/app/models/accounting_period.rb
+++ b/app/models/accounting_period.rb
@@ -14,6 +14,7 @@ class AccountingPeriod < ActiveRecord::Base
validates :start_at, :end_at, :closed_at, :closed_by, presence: true
validates_with DateRangeValidator
+ validates_with DurationValidator
validates_with PeriodOverlapValidator
validates_with PeriodIntegrityValidator
diff --git a/app/validators/duration_validator.rb b/app/validators/duration_validator.rb
new file mode 100644
index 000000000..fa30e9a37
--- /dev/null
+++ b/app/validators/duration_validator.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+# Validates that the duration between start_at and end_at is between 1 day and 1 year
+class DurationValidator < ActiveModel::Validator
+ def validate(record)
+ the_end = record.end_at
+ the_start = record.start_at
+ diff = (the_end - the_start).to_i
+ return if diff.days >= 1.day && diff.days <= 1.year
+
+ record.errors[:end_at] << I18n.t('errors.messages.invalid_duration', DAYS: diff)
+ end
+end
diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml
index 1e96f1610..c9dee138c 100644
--- a/config/locales/app.admin.en.yml
+++ b/config/locales/app.admin.en.yml
@@ -421,7 +421,9 @@ en:
perpetual_total: "Perpetual total"
integrity: "Integrity check"
confirmation_required: "Confirmation required"
- confirm_close_START_END: "Do you really want to close the accounting period between {{START}} and {{END}}? Any subsequent changes will be impossible. This operation will take some time to complete"
+ confirm_close_START_END: "Do you really want to close the accounting period between {{START}} and {{END}}? Any subsequent changes will be impossible."
+ period_must_match_fiscal_year: "A closing must occur at the end of a minimum annual period, or per financial year when it is not calendar-based."
+ this_may_take_a_while: "This operation will take some time to complete."
period_START_END_closed_success: "The accounting period from {{START}} to {{END}} has been successfully closed"
failed_to_close_period: "An error occurred, unable to close the accounting period"
no_periods: "No closings for now"
diff --git a/config/locales/app.admin.es.yml b/config/locales/app.admin.es.yml
index fe4266d0e..825a1dfdb 100644
--- a/config/locales/app.admin.es.yml
+++ b/config/locales/app.admin.es.yml
@@ -421,7 +421,9 @@ es:
perpetual_total: "Perpetual total" # translation_missing
integrity: "Verificación de integridad"
confirmation_required: "Confirmation required" # translation_missing
- confirm_close_START_END: "Do you really want to close the accounting period between {{START}} and {{END}}? Any subsequent changes will be impossible. This operation will take some time to complete" # translation_missing
+ confirm_close_START_END: "Do you really want to close the accounting period between {{START}} and {{END}}? Any subsequent changes will be impossible." # translation_missing
+ period_must_match_fiscal_year: "A closing must occur at the end of a minimum annual period, or per financial year when it is not calendar-based." # translation_missing
+ this_may_take_a_while: "This operation will take some time to complete." # translation_missing
period_START_END_closed_success: "The accounting period from {{START}} to {{END}} has been successfully closed" # 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
diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml
index 508e1c7ba..d04470e92 100644
--- a/config/locales/app.admin.fr.yml
+++ b/config/locales/app.admin.fr.yml
@@ -421,7 +421,9 @@ fr:
perpetual_total: "Total perpétuel"
integrity: "Contrôle d'intégrité"
confirmation_required: "Confirmation requise"
- confirm_close_START_END: "Êtes-vous sur de vouloir clôturer la période comptable du {{START}} au {{END}} ? Toute modification ultérieure sera impossible. Cette opération va prendre un certain temps."
+ confirm_close_START_END: "Êtes-vous sur de vouloir clôturer la période comptable du {{START}} au {{END}} ? Toute modification ultérieure sera impossible."
+ period_must_match_fiscal_year: "Une clôture doit intervenir à l'issue d'une période au minimum annuelle, ou par exercice lorsque celui-ci n'est pas calé sur l'année civile."
+ this_may_take_a_while: "Cette opération va prendre un certain temps."
period_START_END_closed_success: "La période comptable du {{START}} au {{END}} a bien été clôturé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"
diff --git a/config/locales/app.admin.pt.yml b/config/locales/app.admin.pt.yml
index 4d0f916a3..6291094bf 100755
--- a/config/locales/app.admin.pt.yml
+++ b/config/locales/app.admin.pt.yml
@@ -421,7 +421,9 @@ pt:
perpetual_total: "Perpetual total" # translation_missing
integrity: "Verificação de integridade"
confirmation_required: "Confirmation required" # translation_missing
- confirm_close_START_END: "Do you really want to close the accounting period between {{START}} and {{END}}? Any subsequent changes will be impossible. This operation will take some time to complete." # translation_missing
+ confirm_close_START_END: "Do you really want to close the accounting period between {{START}} and {{END}}? Any subsequent changes will be impossible" # translation_missing
+ period_must_match_fiscal_year: "A closing must occur at the end of a minimum annual period, or per financial year when it is not calendar-based." # translation_missing
+ this_may_take_a_while: "This operation will take some time to complete." # translation_missing
period_START_END_closed_success: "The accounting period from {{START}} to {{END}} has been successfully closed" # 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
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 0690a80e2..39ca81d2c 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -41,6 +41,7 @@ en:
in_closed_period: "can't be within a closed accounting period"
invalid_footprint: "invoice's checksum is invalid"
end_before_start: "The end date can't be before the start date. Pick a date after %{START}"
+ invalid_duration: "The allowed duration must be between 1 day and 1 year. Your period is %{DAYS} days long."
activemodel:
errors:
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 7b63df33d..3d5096355 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -41,6 +41,7 @@ es:
in_closed_period: "can't be within a closed accounting period" # missing translation
invalid_footprint: "invoice's checksum is invalid" # missing translation
end_before_start: "The end date can't be before the start date. Pick a date after %{START}" # missing translation
+ invalid_duration: "The allowed duration must be between 1 day and 1 year. Your period is %{DAYS} days long." # missing translation
activemodel:
errors:
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index aedcbcee7..cd6c3760a 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -41,6 +41,7 @@ fr:
in_closed_period: "ne peut pas être dans une période comptable fermée"
invalid_footprint: "la somme de contrôle de la facture est invalide"
end_before_start: "La date de fin ne peut pas être antérieure à la date de début. Choisissez une date après le %{START}"
+ invalid_duration: "La durée doit être comprise entre 1 jour et 1 an. Votre période dure %{DAYS} jours."
activemodel:
errors:
diff --git a/config/locales/pt.yml b/config/locales/pt.yml
index 00ef49273..298be6c00 100755
--- a/config/locales/pt.yml
+++ b/config/locales/pt.yml
@@ -41,6 +41,7 @@ pt:
in_closed_period: "can't be within a closed accounting period" # missing translation
invalid_footprint: "invoice's checksum is invalid" # missing translation
end_before_start: "The end date can't be before the start date. Pick a date after %{START}" # missing translation
+ invalid_duration: "The allowed duration must be between 1 day and 1 year. Your period is %{DAYS} days long." # missing translation
activemodel:
errors:
diff --git a/test/integration/accounting_period_test.rb b/test/integration/accounting_period_test.rb
index 1c0948673..98aaf777e 100644
--- a/test/integration/accounting_period_test.rb
+++ b/test/integration/accounting_period_test.rb
@@ -68,4 +68,44 @@ class AccountingPeriodTest < ActionDispatch::IntegrationTest
FileUtils.rm_rf(accounting_period.archive_folder)
end
+ test 'admin tries to closes a too long period' do
+ start_at = '2012-01-01T00:00:00.000Z'
+ end_at = '2014-12-31T00:00:00.000Z'
+ diff = (end_at.to_date - start_at.to_date).to_i
+
+ post '/api/accounting_periods',
+ {
+ accounting_period: {
+ start_at: start_at,
+ end_at: end_at
+ }
+ }.to_json, default_headers
+
+ # Check response format & status
+ assert_equal 422, response.status, response.body
+ assert_equal Mime::JSON, response.content_type
+
+ # check the error
+ assert_match(/#{I18n.t('errors.messages.invalid_duration', DAYS: diff)}/, response.body)
+ end
+
+ test 'admin tries to closes an overlapping period' do
+ start_at = '2014-12-01T00:00:00.000Z'
+ end_at = '2015-02-27T00:00:00.000Z'
+
+ post '/api/accounting_periods',
+ {
+ accounting_period: {
+ start_at: start_at,
+ end_at: end_at
+ }
+ }.to_json, default_headers
+
+ # Check response format & status
+ assert_equal 422, response.status, response.body
+ assert_equal Mime::JSON, response.content_type
+
+ # check the error
+ assert_match(/#{I18n.t('errors.messages.cannot_overlap')}/, response.body)
+ end
end