mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-19 13:54:25 +01:00
validates accounting periods on creation + prevent refunding on closed periods (client only)
This commit is contained in:
parent
84aa0c75ff
commit
9fac706da8
@ -17,8 +17,8 @@
|
||||
/**
|
||||
* Controller used in the admin invoices listing page
|
||||
*/
|
||||
Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'Invoice', 'AccountingPeriod', 'invoices', '$uibModal', 'growl', '$filter', 'Setting', 'settings', '_t',
|
||||
function ($scope, $state, Invoice, AccountingPeriod, invoices, $uibModal, growl, $filter, Setting, settings, _t) {
|
||||
Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'Invoice', 'AccountingPeriod', 'invoices', 'closedPeriods', '$uibModal', 'growl', '$filter', 'Setting', 'settings', '_t',
|
||||
function ($scope, $state, Invoice, AccountingPeriod, invoices, closedPeriods, $uibModal, growl, $filter, Setting, settings, _t) {
|
||||
/* PRIVATE STATIC CONSTANTS */
|
||||
|
||||
// number of invoices loaded each time we click on 'load more...'
|
||||
@ -110,7 +110,8 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
templateUrl: '<%= asset_path "admin/invoices/avoirModal.html" %>',
|
||||
controller: 'AvoirModalController',
|
||||
resolve: {
|
||||
invoice () { return invoice; }
|
||||
invoice () { return invoice; },
|
||||
closedPeriods() { return closedPeriods; }
|
||||
}
|
||||
});
|
||||
|
||||
@ -387,6 +388,10 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
return invoiceSearch(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Open a modal allowing the user to close an accounting period and to
|
||||
* view all periods already closed.
|
||||
*/
|
||||
$scope.closeAnAccountingPeriod = function() {
|
||||
// open modal
|
||||
$uibModal.open({
|
||||
@ -394,7 +399,7 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
controller: 'ClosePeriodModalController',
|
||||
size: 'lg',
|
||||
resolve: {
|
||||
periods() { return AccountingPeriod.query().$promise; },
|
||||
periods() { return closedPeriods; },
|
||||
lastClosingEnd() { return AccountingPeriod.lastClosingEnd().$promise; },
|
||||
}
|
||||
});
|
||||
@ -509,8 +514,8 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
/**
|
||||
* Controller used in the invoice refunding modal window
|
||||
*/
|
||||
Application.Controllers.controller('AvoirModalController', ['$scope', '$uibModalInstance', 'invoice', 'Invoice', 'growl', '_t',
|
||||
function ($scope, $uibModalInstance, invoice, Invoice, growl, _t) {
|
||||
Application.Controllers.controller('AvoirModalController', ['$scope', '$uibModalInstance', 'invoice', 'closedPeriods', 'Invoice', 'growl', '_t',
|
||||
function ($scope, $uibModalInstance, invoice, closedPeriods, Invoice, growl, _t) {
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
// invoice linked to the current refund
|
||||
@ -589,6 +594,20 @@ Application.Controllers.controller('AvoirModalController', ['$scope', '$uibModal
|
||||
*/
|
||||
$scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
|
||||
|
||||
/**
|
||||
* Test if the given date is within a closed accounting period
|
||||
* @param date {Date} date to test
|
||||
* @returns {boolean} true if closed, false otherwise
|
||||
*/
|
||||
$scope.isDateClosed = function(date) {
|
||||
for (const period of closedPeriods) {
|
||||
if (moment(date).isBetween(moment.utc(period.start_at).startOf('day'), moment.utc(period.end_at).endOf('day'), null, '[]')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
/**
|
||||
@ -624,11 +643,17 @@ Application.Controllers.controller('ClosePeriodModalController', ['$scope', '$ui
|
||||
const LAST_CLOSING = moment.utc(lastClosingEnd.last_end_date).toDate();
|
||||
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
// date pickers values are bound to these variables
|
||||
$scope.period = {
|
||||
start_at: LAST_CLOSING,
|
||||
end_at: YESTERDAY
|
||||
};
|
||||
|
||||
// any form errors will come here
|
||||
$scope.errors = {};
|
||||
|
||||
// existing closed periods, provided by the API
|
||||
$scope.accountingPeriods = periods;
|
||||
|
||||
// AngularUI-Bootstrap datepickers parameters to define the period to close
|
||||
@ -681,7 +706,7 @@ Application.Controllers.controller('ClosePeriodModalController', ['$scope', '$ui
|
||||
}
|
||||
, function(error) {
|
||||
growl.error(_t('invoices.failed_to_close_period'));
|
||||
console.error(error);
|
||||
$scope.errors = error.data;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -885,6 +885,7 @@ angular.module('application.router', ['ui.router'])
|
||||
query: { number: '', customer: '', date: null, order_by: '-reference', page: 1, size: 20 }
|
||||
}).$promise;
|
||||
}],
|
||||
closedPeriods: [ 'AccountingPeriod', function(AccountingPeriod) { return AccountingPeriod.query().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.admin.invoices').$promise; }]
|
||||
}
|
||||
})
|
||||
|
@ -616,4 +616,8 @@ padding: 10px;
|
||||
& > i.fileinput-exists {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.help-block.error {
|
||||
color: #ff565d;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
uib-datepicker-popup="{{datePicker.format}}"
|
||||
datepicker-options="datePicker.options"
|
||||
is-open="datePicker.opened"
|
||||
date-disabled="isDateClosed(date, mode)"
|
||||
placeholder="{{datePicker.format}}"
|
||||
ng-click="openDatePicker($event)"
|
||||
required/>
|
||||
|
@ -22,6 +22,7 @@
|
||||
required/>
|
||||
</div>
|
||||
<span class="help-block" ng-show="closePeriodForm.start_at.$dirty && closePeriodForm.start_at.$error.required" translate>{{ 'invoices.start_date_is_required' }}</span>
|
||||
<span class="help-block error" ng-show="errors.start_at">{{ errors.start_at[0] }}</span>
|
||||
</div>
|
||||
<div class="form-group col-md-6" ng-class="{'has-error': closePeriodForm.end_at.$dirty && closePeriodForm.end_at.$invalid }">
|
||||
<label translate>{{ 'invoices.close_until_date' }}</label>
|
||||
@ -42,6 +43,7 @@
|
||||
required/>
|
||||
</div>
|
||||
<span class="help-block" ng-show="closePeriodForm.end_at.$dirty && closePeriodForm.end_at.$error.required" translate>{{ 'invoices.end_date_is_required' }}</span>
|
||||
<span class="help-block error" ng-show="errors.end_at">{{ errors.end_at[0] }}</span>
|
||||
</div>
|
||||
</form>
|
||||
<div>
|
||||
|
@ -1,7 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# AccountingPeriod is a period of N days (N > 0) which as been closed by an admin
|
||||
# to prevent writing new accounting lines (invoices & refunds) during this period of time.
|
||||
class AccountingPeriod < ActiveRecord::Base
|
||||
before_destroy { false }
|
||||
before_update { false }
|
||||
|
||||
validates :start_at, :end_at, :closed_at, :closed_by, presence: true
|
||||
validates_with DateRangeValidator
|
||||
validates_with PeriodOverlapValidator
|
||||
|
||||
def delete
|
||||
false
|
||||
end
|
||||
|
12
app/validators/date_range_validator.rb
Normal file
12
app/validators/date_range_validator.rb
Normal file
@ -0,0 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Validates that start_at is same or before end_at in the given record
|
||||
class DateRangeValidator < ActiveModel::Validator
|
||||
def validate(record)
|
||||
the_end = record.start_at
|
||||
the_start = record.end_at
|
||||
return unless the_end.present? && the_end >= the_start
|
||||
|
||||
record.errors[:end_at] << "The end date can't be before the start date. Pick a date after #{the_start}"
|
||||
end
|
||||
end
|
21
app/validators/period_overlap_validator.rb
Normal file
21
app/validators/period_overlap_validator.rb
Normal file
@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Validates the current accounting period does not overlap an existing one
|
||||
class PeriodOverlapValidator < ActiveModel::Validator
|
||||
def validate(record)
|
||||
the_end = record.end_at
|
||||
the_start = record.start_at
|
||||
|
||||
AccountingPeriod.all.each do |period|
|
||||
if the_start >= period.start_at && the_start <= period.end_at
|
||||
record.errors[:start_at] << I18n.t('errors.messages.cannot_overlap')
|
||||
end
|
||||
if the_end >= period.start_at && the_end <= period.end_at
|
||||
record.errors[:end_at] << I18n.t('errors.messages.cannot_overlap')
|
||||
end
|
||||
if period.start_at >= the_start && period.end_at <= the_end
|
||||
record.errors[:end_at] << I18n.t('errors.messages.cannot_encompass')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -36,6 +36,8 @@ en:
|
||||
cannot_be_blank_at_same_time: "cannot be blank when %{field} is blank too"
|
||||
cannot_be_in_the_past: "cannot be in the past"
|
||||
cannot_be_before_previous_value: "cannot be before the previous value"
|
||||
cannot_overlap: "can't overlap an existing accounting period"
|
||||
cannot_encompass: "can't encompass an existing accounting period"
|
||||
|
||||
activemodel:
|
||||
errors:
|
||||
|
@ -36,6 +36,8 @@ es:
|
||||
cannot_be_blank_at_same_time: "no puede estar vacío cuando %{field} también está vacío"
|
||||
cannot_be_in_the_past: "no puede estar en el pasado"
|
||||
cannot_be_before_previous_value: "No puede estar antes del valor anterior."
|
||||
cannot_overlap: "can't overlap an existing accounting period" # missing translation
|
||||
cannot_encompass: "can't encompass an existing accounting period" # missing translation
|
||||
|
||||
activemodel:
|
||||
errors:
|
||||
|
@ -36,6 +36,8 @@ fr:
|
||||
cannot_be_blank_at_same_time: "ou %{field} doit être rempli(e)"
|
||||
cannot_be_in_the_past: "ne peut pas être dans le passé"
|
||||
cannot_be_before_previous_value: "ne peut pas être antérieur(e) à la valeur précédente"
|
||||
cannot_overlap: "ne peut pas chevaucher une période comptable existante"
|
||||
cannot_encompass: "ne peut pas englober une période comptable existante"
|
||||
|
||||
activemodel:
|
||||
errors:
|
||||
@ -360,4 +362,4 @@ fr:
|
||||
|
||||
group:
|
||||
# nom du groupe utilisateur pour les administrateurs
|
||||
admins: 'Administrateurs'
|
||||
admins: 'Administrateurs'
|
||||
|
@ -36,6 +36,8 @@ pt:
|
||||
cannot_be_blank_at_same_time: "Não pode ficar em branco quando %{field} estiver em branco também"
|
||||
cannot_be_in_the_past: "não pode ser no passado"
|
||||
cannot_be_before_previous_value: "não pode ser antes do valor anterior"
|
||||
cannot_overlap: "can't overlap an existing accounting period" # missing translation
|
||||
cannot_encompass: "can't encompass an existing accounting period" # missing translation
|
||||
|
||||
activemodel:
|
||||
errors:
|
||||
|
Loading…
x
Reference in New Issue
Block a user