mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-22 11:52:21 +01:00
Merge branch 'pre_inscription' into staging
This commit is contained in:
commit
57e808e992
@ -97,6 +97,7 @@ class API::EventsController < API::APIController
|
||||
event_preparams = params.required(:event).permit(:title, :description, :start_date, :start_time, :end_date, :end_time,
|
||||
:amount, :nb_total_places, :availability_id, :all_day, :recurrence,
|
||||
:recurrence_end_at, :category_id, :event_theme_ids, :age_range_id, :event_type,
|
||||
:pre_registration, :pre_registration_end_date,
|
||||
event_theme_ids: [],
|
||||
event_image_attributes: %i[id attachment],
|
||||
event_files_attributes: %i[id attachment _destroy],
|
||||
|
@ -4,7 +4,7 @@
|
||||
# Reservations are used for Training, Machine, Space and Event
|
||||
class API::ReservationsController < API::APIController
|
||||
before_action :authenticate_user!
|
||||
before_action :set_reservation, only: %i[show update]
|
||||
before_action :set_reservation, only: %i[show update confirm_payment]
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@ -34,6 +34,16 @@ class API::ReservationsController < API::APIController
|
||||
end
|
||||
end
|
||||
|
||||
def confirm_payment
|
||||
authorize @reservation
|
||||
invoice = ReservationConfirmPaymentService.new(@reservation, current_user, params[:coupon_code], params[:offered]).call
|
||||
if invoice
|
||||
render :show, status: :ok, location: @reservation
|
||||
else
|
||||
render json: @reservation.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_reservation
|
||||
|
@ -5,7 +5,7 @@
|
||||
# availability by Availability.slot_duration, or otherwise globally by Setting.get('slot_duration')
|
||||
class API::SlotsReservationsController < API::APIController
|
||||
before_action :authenticate_user!
|
||||
before_action :set_slots_reservation, only: %i[update cancel]
|
||||
before_action :set_slots_reservation, only: %i[update cancel validate]
|
||||
respond_to :json
|
||||
|
||||
def update
|
||||
@ -23,6 +23,11 @@ class API::SlotsReservationsController < API::APIController
|
||||
SlotsReservationsService.cancel(@slot_reservation)
|
||||
end
|
||||
|
||||
def validate
|
||||
authorize @slot_reservation
|
||||
SlotsReservationsService.validate(@slot_reservation)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_slots_reservation
|
||||
|
@ -55,6 +55,7 @@ export const EventForm: React.FC<EventFormProps> = ({ action, event, onError, on
|
||||
const [updatingEvent, setUpdatingEvent] = useState<Event>(null);
|
||||
const [isActiveAccounting, setIsActiveAccounting] = useState<boolean>(false);
|
||||
const [isActiveFamilyAccount, setIsActiveFamilyAccount] = useState<boolean>(false);
|
||||
const [isAcitvePreRegistration, setIsActivePreRegistration] = useState<boolean>(event?.pre_registration);
|
||||
|
||||
useEffect(() => {
|
||||
EventCategoryAPI.index()
|
||||
@ -241,6 +242,19 @@ export const EventForm: React.FC<EventFormProps> = ({ action, event, onError, on
|
||||
formState={formState}
|
||||
options={ageRangeOptions}
|
||||
label={t('app.admin.event_form.age_range')} />}
|
||||
<FormSwitch control={control}
|
||||
id="pre_registration"
|
||||
label={t('app.admin.event_form.pre_registration')}
|
||||
formState={formState}
|
||||
tooltip={t('app.admin.event_form.pre_registration_help')}
|
||||
onChange={setIsActivePreRegistration} />
|
||||
{isAcitvePreRegistration &&
|
||||
<FormInput id="pre_registration_end_date"
|
||||
type="date"
|
||||
register={register}
|
||||
formState={formState}
|
||||
label={t('app.admin.event_form.pre_registration_end_date')} />
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
@ -436,7 +436,7 @@ Application.Controllers.controller('AdminEventsController', ['$scope', '$state',
|
||||
/**
|
||||
* Controller used in the reservations listing page for a specific event
|
||||
*/
|
||||
Application.Controllers.controller('ShowEventReservationsController', ['$scope', 'eventPromise', 'reservationsPromise', function ($scope, eventPromise, reservationsPromise) {
|
||||
Application.Controllers.controller('ShowEventReservationsController', ['$scope', 'eventPromise', 'reservationsPromise', 'dialogs', 'SlotsReservation', 'growl', '_t', 'Price', 'Wallet', '$uibModal', function ($scope, eventPromise, reservationsPromise, dialogs, SlotsReservation, growl, _t, Price, Wallet, $uibModal) {
|
||||
// retrieve the event from the ID provided in the current URL
|
||||
$scope.event = eventPromise;
|
||||
|
||||
@ -451,6 +451,170 @@ Application.Controllers.controller('ShowEventReservationsController', ['$scope',
|
||||
$scope.isCancelled = function (reservation) {
|
||||
return !!(reservation.slots_reservations_attributes[0].canceled_at);
|
||||
};
|
||||
|
||||
/**
|
||||
* Test if the provided reservation has been validated
|
||||
* @param reservation {Reservation}
|
||||
* @returns {boolean}
|
||||
*/
|
||||
$scope.isValidated = function (reservation) {
|
||||
return !!(reservation.slots_reservations_attributes[0].validated_at);
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback to validate a reservation
|
||||
* @param reservation {Reservation}
|
||||
*/
|
||||
$scope.validateReservation = function (reservation) {
|
||||
dialogs.confirm({
|
||||
resolve: {
|
||||
object: function () {
|
||||
return {
|
||||
title: _t('app.admin.event_reservations.validate_the_reservation'),
|
||||
msg: _t('app.admin.event_reservations.do_you_really_want_to_validate_this_reservation_this_apply_to_all_booked_tickets')
|
||||
};
|
||||
}
|
||||
}
|
||||
}, function () { // validate confirmed
|
||||
SlotsReservation.validate({
|
||||
id: reservation.slots_reservations_attributes[0].id
|
||||
}, () => { // successfully validated
|
||||
growl.success(_t('app.admin.event_reservations.reservation_was_successfully_validated'));
|
||||
const index = $scope.reservations.indexOf(reservation);
|
||||
$scope.reservations[index].slots_reservations_attributes[0].validated_at = new Date();
|
||||
}, () => {
|
||||
growl.warning(_t('app.admin.event_reservations.validation_failed'));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const mkCartItems = function (reservation, coupon) {
|
||||
return {
|
||||
customer_id: reservation.user_id,
|
||||
items: [{
|
||||
reservation: {
|
||||
...reservation,
|
||||
slots_reservations_attributes: reservation.slots_reservations_attributes.map(sr => ({ slot_id: sr.slot_id })),
|
||||
tickets_attributes: reservation.tickets_attributes.map(t => ({ booked: t.booked, event_price_category_id: t.event_price_category.id })),
|
||||
booking_users_attributes: reservation.booking_users_attributes.map(bu => (
|
||||
{ name: bu.name, event_price_category_id: bu.event_price_category_id, booked_id: bu.booked_id, booked_type: bu.booked_type }
|
||||
))
|
||||
}
|
||||
}],
|
||||
coupon_code: ((coupon ? coupon.code : undefined)),
|
||||
payment_method: ''
|
||||
};
|
||||
};
|
||||
|
||||
$scope.payReservation = function (reservation) {
|
||||
const modalInstance = $uibModal.open({
|
||||
templateUrl: '/admin/events/pay_reservation_modal.html',
|
||||
size: 'sm',
|
||||
resolve: {
|
||||
reservation () {
|
||||
return reservation;
|
||||
},
|
||||
price () {
|
||||
return Price.compute(mkCartItems(reservation)).$promise;
|
||||
},
|
||||
wallet () {
|
||||
return Wallet.getWalletByUser({ user_id: reservation.user_id }).$promise;
|
||||
},
|
||||
cartItems () {
|
||||
return mkCartItems(reservation);
|
||||
}
|
||||
},
|
||||
controller: ['$scope', '$uibModalInstance', 'reservation', 'price', 'wallet', 'cartItems', 'helpers', '$filter', '_t', 'Reservation',
|
||||
function ($scope, $uibModalInstance, reservation, price, wallet, cartItems, helpers, $filter, _t, Reservation) {
|
||||
// User's wallet amount
|
||||
$scope.wallet = wallet;
|
||||
|
||||
// Price
|
||||
$scope.price = price;
|
||||
|
||||
// Cart items
|
||||
$scope.cartItems = cartItems;
|
||||
|
||||
// price to pay
|
||||
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount);
|
||||
|
||||
// Reservation
|
||||
$scope.reservation = reservation;
|
||||
|
||||
$scope.coupon = { applied: null };
|
||||
|
||||
$scope.offered = false;
|
||||
|
||||
$scope.payment = false;
|
||||
|
||||
// Button label
|
||||
$scope.setValidButtonName = function () {
|
||||
if ($scope.amount > 0 && !$scope.offered) {
|
||||
$scope.validButtonName = _t('app.admin.event_reservations.confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')($scope.amount) });
|
||||
} else {
|
||||
$scope.validButtonName = _t('app.shared.buttons.confirm');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute the total amount for the current reservation according to the previously set parameters
|
||||
*/
|
||||
$scope.computeEventAmount = function () {
|
||||
Price.compute(mkCartItems(reservation, $scope.coupon.applied), function (res) {
|
||||
$scope.price = res;
|
||||
$scope.amount = helpers.getAmountToPay($scope.price.price, wallet.amount);
|
||||
$scope.setValidButtonName();
|
||||
});
|
||||
};
|
||||
|
||||
// Callback to validate the payment
|
||||
$scope.ok = function () {
|
||||
$scope.attempting = true;
|
||||
return Reservation.confirm_payment({
|
||||
id: reservation.id,
|
||||
coupon_code: $scope.coupon.applied ? $scope.coupon.applied.code : null,
|
||||
offered: $scope.offered
|
||||
}, function (res) {
|
||||
$uibModalInstance.close(res);
|
||||
return $scope.attempting = true;
|
||||
}
|
||||
, function (response) {
|
||||
$scope.alerts = [];
|
||||
angular.forEach(response, function (v, k) {
|
||||
angular.forEach(v, function (err) {
|
||||
$scope.alerts.push({
|
||||
msg: k + ': ' + err,
|
||||
type: 'danger'
|
||||
});
|
||||
});
|
||||
});
|
||||
return $scope.attempting = false;
|
||||
});
|
||||
};
|
||||
|
||||
// Callback to cancel the payment
|
||||
$scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
|
||||
|
||||
$scope.$watch('coupon.applied', function (newValue, oldValue) {
|
||||
if ((newValue !== null) || (oldValue !== null)) {
|
||||
return $scope.computeEventAmount();
|
||||
}
|
||||
});
|
||||
|
||||
$scope.setValidButtonName();
|
||||
}]
|
||||
});
|
||||
modalInstance.result.then(function (reservation) {
|
||||
$scope.reservations = $scope.reservations.map((r) => {
|
||||
if (r.id === reservation.id) {
|
||||
return reservation;
|
||||
}
|
||||
return r;
|
||||
});
|
||||
}, function () {
|
||||
$log.info('Pay reservation modal dismissed at: ' + new Date());
|
||||
});
|
||||
};
|
||||
}]);
|
||||
|
||||
/**
|
||||
|
@ -298,10 +298,15 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
|
||||
};
|
||||
|
||||
$scope.isShowReserveEventButton = () => {
|
||||
return $scope.event.nb_free_places > 0 &&
|
||||
const bookable = $scope.event.nb_free_places > 0 &&
|
||||
!$scope.reserve.toReserve &&
|
||||
$scope.now.isBefore($scope.eventEndDateTime) &&
|
||||
helpers.isUserValidatedByType($scope.ctrl.member, $scope.settings, 'event');
|
||||
if ($scope.event.pre_registration) {
|
||||
return bookable && $scope.event.pre_registration_end_date && $scope.now.isSameOrBefore($scope.event.pre_registration_end_date, 'day');
|
||||
} else {
|
||||
return bookable;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -66,6 +66,8 @@ export interface Event {
|
||||
recurrence_end_at: Date,
|
||||
advanced_accounting_attributes?: AdvancedAccounting,
|
||||
event_type: EventType,
|
||||
pre_registration?: boolean,
|
||||
pre_registration_end_date?: TDateISODate | Date,
|
||||
}
|
||||
|
||||
export interface EventDecoration {
|
||||
|
@ -5,6 +5,11 @@ Application.Services.factory('Reservation', ['$resource', function ($resource) {
|
||||
{ id: '@id' }, {
|
||||
update: {
|
||||
method: 'PUT'
|
||||
},
|
||||
confirm_payment: {
|
||||
method: 'POST',
|
||||
url: '/api/reservations/confirm_payment',
|
||||
isArray: false
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -9,6 +9,10 @@ Application.Services.factory('SlotsReservation', ['$resource', function ($resour
|
||||
cancel: {
|
||||
method: 'PUT',
|
||||
url: '/api/slots_reservations/:id/cancel'
|
||||
},
|
||||
validate: {
|
||||
method: 'PUT',
|
||||
url: '/api/slots_reservations/:id/validate'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -12,8 +12,9 @@
|
||||
<tr>
|
||||
<th style="width:30%" translate>{{ 'app.admin.events.title' }}</th>
|
||||
<th style="width:30%" translate>{{ 'app.admin.events.dates' }}</th>
|
||||
<th style="width:15%" translate>{{ 'app.admin.events.types' }}</th>
|
||||
<th style="width:10%" translate>{{ 'app.admin.events.booking' }}</th>
|
||||
<th style="width:30%"></th>
|
||||
<th style="width:15%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -48,8 +49,16 @@
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<span ng-if="event.event_type === 'standard'" class="v-middle badge text-base bg-stage" translate="">{{ 'app.admin.events.event_type.standard' }}</span>
|
||||
<span ng-if="event.event_type === 'nominative'" class="v-middle badge text-base bg-event" translate="">{{ 'app.admin.events.event_type.nominative' }}</span>
|
||||
<span ng-if="event.event_type === 'family'" class="v-middle badge text-base bg-atelier" translate="">{{ 'app.admin.events.event_type.family' }}</span>
|
||||
<span ng-if="event.pre_registration" class="v-middle badge text-base bg-info" translate="">{{ 'app.admin.events.pre_registration' }}</span>
|
||||
</td>
|
||||
|
||||
<td style="vertical-align:middle">
|
||||
<span class="ng-binding" ng-if="event.nb_total_places > 0">{{ event.nb_total_places - event.nb_free_places }} / {{ event.nb_total_places }}</span>
|
||||
<div class="ng-binding" ng-if="event.pre_registration">{{'app.admin.events.NUMBER_pre_registered' | translate:{NUMBER:event.nb_places_for_pre_registration} }}</div>
|
||||
<span class="badge font-sbold cancelled" ng-if="event.nb_total_places == -1" translate>{{ 'app.admin.events.cancelled' }}</span>
|
||||
<span class="badge font-sbold" ng-if="!event.nb_total_places" translate>{{ 'app.admin.events.without_reservation' }}</span>
|
||||
</td>
|
||||
@ -57,10 +66,10 @@
|
||||
<td style="vertical-align:middle">
|
||||
<div class="buttons">
|
||||
<a class="btn btn-default" ui-sref="app.admin.event_reservations({id: event.id})">
|
||||
<i class="fa fa-bookmark"></i> {{ 'app.admin.events.view_reservations' | translate }}
|
||||
<i class="fa fa-eye"></i>
|
||||
</a>
|
||||
<a class="btn btn-default" ui-sref="app.admin.events_edit({id: event.id})">
|
||||
<i class="fa fa-edit"></i> {{ 'app.shared.buttons.edit' | translate }}
|
||||
<i class="fa fa-edit"></i>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -0,0 +1,45 @@
|
||||
<div class="modal-header">
|
||||
<img ng-src="{{logoBlack.custom_asset_file_attributes.attachment_url}}" alt="{{logo.custom_asset_file_attributes.attachment}}" class="modal-logo"/>
|
||||
<h1 translate ng-show="reservation">{{ 'app.admin.event_reservations.confirm_payment' }}</h1>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<uib-alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</uib-alert>
|
||||
<div class="row" ng-show="!offered">
|
||||
<wallet-info current-user="currentUser"
|
||||
cart="cartItems"
|
||||
price="price.price"
|
||||
wallet="wallet"/>
|
||||
</div>
|
||||
<div class="row m-b">
|
||||
<div class="col-md-12">
|
||||
<label for="offerSlot" class="control-label m-r" translate>{{ 'app.admin.event_reservations.offer_this_reservation' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="offered"
|
||||
id="offerSlot"
|
||||
type="checkbox"
|
||||
class="form-control"
|
||||
switch-on-text="{{ 'app.shared.buttons.yes' | translate }}"
|
||||
switch-off-text="{{ 'app.shared.buttons.no' | translate }}"
|
||||
switch-animate="true"
|
||||
ng-change="computeEventAmount()"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<coupon show="true" coupon="coupon.applied" total="price.price_without_coupon" user-id="{{reservation.user_id}}"></coupon>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group col-sm-12">
|
||||
<div class="checkbox-group">
|
||||
<input type="checkbox"
|
||||
name="paymentReceived"
|
||||
id="paymentReceived"
|
||||
ng-model="payment" />
|
||||
<label for="paymentReceived" translate>{{ 'app.admin.event_reservations.i_have_received_the_payment' }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-info" ng-click="ok()" ng-disabled="attempting || !payment" ng-bind-html="validButtonName"></button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
|
||||
</div>
|
@ -20,31 +20,49 @@
|
||||
<table class="table" ng-if="reservations.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:25%" translate>{{ 'app.admin.event_reservations.user' }}</th>
|
||||
<th style="width:25%" translate>{{ 'app.admin.event_reservations.payment_date' }}</th>
|
||||
<th style="width:25%" translate>{{ 'app.admin.event_reservations.booked_by' }}</th>
|
||||
<th style="width:25%" translate>{{ 'app.admin.event_reservations.reservations' }}</th>
|
||||
<th style="width:25%" translate>{{ 'app.admin.event_reservations.date' }}</th>
|
||||
<th style="width:25%" translate>{{ 'app.admin.event_reservations.reserved_tickets' }}</th>
|
||||
<th style="width:25%"></th>
|
||||
<th ng-if="event.pre_registration" style="width:25%" translate>{{ 'app.admin.event_reservations.status' }}</th>
|
||||
<th ng-if="event.pre_registration" style="width:25%" translate>{{ 'app.admin.event_reservations.gestion' }}</th>
|
||||
<th style="width:5%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="reservation in reservations" ng-class="{'disabled': isCancelled(reservation)}">
|
||||
<td class="text-c">
|
||||
<a ui-sref="app.logged.members_show({id: reservation.user_id})" ng-if="event.event_type === 'standard'">{{ reservation.user_full_name }} </a>
|
||||
<a ui-sref="app.logged.members_show({id: reservation.user_id})">{{ reservation.user_full_name }} </a>
|
||||
</td>
|
||||
<td>
|
||||
<span ng-if="event.event_type === 'standard'">{{ reservation.user_full_name }} </span>
|
||||
<div ng-repeat="bu in reservation.booking_users_attributes">
|
||||
<span ng-if="bu.booked_type !== 'User'">{{bu.name}}</span>
|
||||
<a ui-sref="app.logged.members_show({id: bu.booked_id})" ng-if="bu.booked_type === 'User'">{{bu.name}}</a>
|
||||
<span>{{bu.name}}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ reservation.created_at | amDateFormat:'LL LTS' }}</td>
|
||||
<td>
|
||||
<span ng-if="reservation.nb_reserve_places > 0">{{ 'app.admin.event_reservations.full_price_' | translate }} {{reservation.nb_reserve_places}}<br/></span>
|
||||
<span ng-repeat="ticket in reservation.tickets_attributes">{{ticket.event_price_category.price_category.name}} : {{ticket.booked}}</span>
|
||||
<div ng-show="isCancelled(reservation)" class="canceled-marker" translate>{{ 'app.admin.event_reservations.canceled' }}</div>
|
||||
</td>
|
||||
<td ng-if="event.pre_registration">
|
||||
<span ng-if="!isValidated(reservation) && !isCancelled(reservation) && !reservation.is_paid" class="v-middle badge text-base bg-info" translate="">{{ 'app.admin.event_reservations.event_status.pre_registered' }}</span>
|
||||
<span ng-if="isValidated(reservation) && !isCancelled(reservation) && !reservation.is_paid" class="v-middle badge text-base bg-stage" translate="">{{ 'app.admin.event_reservations.event_status.to_pay' }}</span>
|
||||
<span ng-if="reservation.is_paid && !isCancelled(reservation)" class="v-middle badge text-base bg-success" translate="">{{ 'app.admin.event_reservations.event_status.paid' }}</span>
|
||||
<span ng-if="isCancelled(reservation)" class="v-middle badge text-base bg-event" translate="">{{ 'app.admin.event_reservations.event_status.canceled' }}</span>
|
||||
</td>
|
||||
<td ng-if="event.pre_registration">
|
||||
<button class="btn btn-default" ng-click="validateReservation(reservation)" ng-if="!isValidated(reservation) && !isCancelled(reservation) && !reservation.is_paid" translate>
|
||||
{{ 'app.admin.event_reservations.validate' }}
|
||||
</button>
|
||||
<button class="btn btn-default" ng-click="payReservation(reservation)" ng-if="isValidated(reservation) && !isCancelled(reservation) && !reservation.is_paid" translate>
|
||||
{{ 'app.admin.event_reservations.pay' }}
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<div class="buttons">
|
||||
<button class="btn btn-default" ui-sref="app.public.events_show({id: event.id})">
|
||||
<i class="fa fa-tag"></i> {{ 'app.admin.event_reservations.show_the_event' | translate }}
|
||||
<i class="fa fa-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -89,6 +89,8 @@
|
||||
<dt><i class="fas fa-clock"></i> {{ 'app.public.events_show.opening_hours' | translate }}</dt>
|
||||
<dd ng-if="event.all_day"><span translate>{{ 'app.public.events_show.all_day' }}</span></dd>
|
||||
<dd ng-if="!event.all_day">{{ 'app.public.events_show.from_time' | translate }} <span class="text-u-l">{{event.start_time}}</span> {{ 'app.public.events_show.to_time' | translate }} <span class="text-u-l">{{event.end_time}}</span></dd>
|
||||
<dt ng-if="event.pre_registration_end_date"><i class="fa fa-calendar" aria-hidden="true"></i> {{ 'app.public.events_show.pre_registration_end_date' | translate }}</dt>
|
||||
<dd ng-if="event.pre_registration_end_date">{{ 'app.public.events_show.ending' | translate }} <span class="text-u-l">{{event.pre_registration_end_date | amDateFormat:'L'}}</span></dd>
|
||||
</dl>
|
||||
|
||||
<div class="text-sm" ng-if="event.amount">
|
||||
@ -193,7 +195,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="currentUser.role == 'admin'" class="m-t">
|
||||
<div ng-show="currentUser.role == 'admin' && !event.pre_registration" class="m-t">
|
||||
<label for="offerSlot" class="control-label m-r" translate>{{ 'app.public.events_show.make_a_gift_of_this_reservation' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="event.offered"
|
||||
@ -215,12 +217,16 @@
|
||||
<a class="pull-right m-t-xs text-u-l ng-scope" ng-click="cancelReserve($event)" ng-show="reserve.toReserve" translate>{{ 'app.shared.buttons.cancel' }}</a>
|
||||
</div>
|
||||
|
||||
<div ng-if="reserveSuccess" class="alert alert-success">{{ 'app.public.events_show.thank_you_your_payment_has_been_successfully_registered' | translate }}<br>
|
||||
<div ng-if="reserveSuccess && !event.pre_registration" class="alert alert-success">{{ 'app.public.events_show.thank_you_your_payment_has_been_successfully_registered' | translate }}<br>
|
||||
{{ 'app.public.events_show.you_can_find_your_reservation_s_details_on_your_' | translate }} <a ui-sref="app.logged.dashboard.invoices" translate>{{ 'app.public.events_show.dashboard' }}</a>
|
||||
</div>
|
||||
<div ng-if="reserveSuccess && event.pre_registration" class="alert alert-success">{{ 'app.public.events_show.thank_you_your_pre_registration_has_been_successfully_saved' | translate }}<br>
|
||||
{{ 'app.public.events_show.you_can_find_your_reservation_s_details_on_your_' | translate }} <a ui-sref="app.logged.dashboard.invoices" translate>{{ 'app.public.events_show.dashboard' }}</a>
|
||||
</div>
|
||||
<div class="m-t-sm" ng-if="reservations && !reserve.toReserve" ng-repeat="reservation in reservations">
|
||||
<div ng-hide="isCancelled(reservation)" class="well well-warning">
|
||||
<div class="font-sbold text-u-c text-sm">{{ 'app.public.events_show.you_booked_DATE' | translate:{DATE:(reservation.created_at | amDateFormat:'L LT')} }}</div>
|
||||
<div class="font-sbold text-u-c text-sm" ng-if="!event.pre_registration">{{ 'app.public.events_show.you_booked_DATE' | translate:{DATE:(reservation.created_at | amDateFormat:'L LT')} }}</div>
|
||||
<div class="font-sbold text-u-c text-sm" ng-if="event.pre_registration">{{ 'app.public.events_show.you_pre_booked_DATE' | translate:{DATE:(reservation.created_at | amDateFormat:'L LT')} }}</div>
|
||||
<div class="font-sbold text-sm" ng-if="reservation.nb_reserve_places > 0">{{ 'app.public.events_show.full_price_' | translate }} {{reservation.nb_reserve_places}} {{ 'app.public.events_show.ticket' | translate:{NUMBER:reservation.nb_reserve_places} }}</div>
|
||||
<div class="font-sbold text-sm" ng-repeat="ticket in reservation.tickets_attributes">
|
||||
{{ticket.event_price_category.price_category.name}} : {{ticket.booked}} {{ 'app.public.events_show.ticket' | translate:{NUMBER:ticket.booked} }}
|
||||
@ -243,7 +249,10 @@
|
||||
<span ng-show="reservations.length > 0" translate>{{ 'app.public.events_show.thanks_for_coming' }}</span>
|
||||
<a ui-sref="app.public.events_list" translate>{{ 'app.public.events_show.view_event_list' }}</a>
|
||||
</div>
|
||||
<button class="btn btn-warning-full rounded btn-block text-sm" ng-click="reserveEvent()" ng-show="isShowReserveEventButton()">{{ 'app.public.events_show.book' | translate }}</button>
|
||||
<button class="btn btn-warning-full rounded btn-block text-sm" ng-click="reserveEvent()" ng-show="isShowReserveEventButton()">
|
||||
<span ng-if="event.pre_registration">{{ 'app.public.events_show.pre_book' | translate }}</span>
|
||||
<span ng-if="!event.pre_registration">{{ 'app.public.events_show.book' | translate }}</span>
|
||||
</button>
|
||||
<uib-alert type="danger" ng-if="ctrl.member.id && !isUserValidatedByType()">
|
||||
<p class="text-sm">
|
||||
<i class="fa fa-warning"></i>
|
||||
@ -251,15 +260,15 @@
|
||||
</p>
|
||||
</uib-alert>
|
||||
|
||||
<coupon show="reserve.totalSeats > 0 && ctrl.member" coupon="coupon.applied" total="reserve.totalNoCoupon" user-id="{{ctrl.member.id}}"></coupon>
|
||||
<coupon show="reserve.totalSeats > 0 && ctrl.member && !event.pre_registration" coupon="coupon.applied" total="reserve.totalNoCoupon" user-id="{{ctrl.member.id}}"></coupon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-footer no-padder ng-scope" ng-if="event.amount && reservationIsValid()">
|
||||
<div class="panel-footer no-padder ng-scope" ng-if="!event.pre_registration && event.amount && reservationIsValid()">
|
||||
<button class="btn btn-valid btn-info btn-block p-l btn-lg text-u-c r-b text-base" ng-click="payEvent()" ng-if="reserve.totalSeats > 0">{{ 'app.public.events_show.confirm_and_pay' | translate }} {{reserve.amountTotal | currency}}</button>
|
||||
</div>
|
||||
|
||||
<div class="panel-footer no-padder ng-scope" ng-if="event.amount == 0 && reservationIsValid()">
|
||||
<div class="panel-footer no-padder ng-scope" ng-if="(event.pre_registration || event.amount == 0) && reservationIsValid()">
|
||||
<button class="btn btn-valid btn-info btn-block p-l btn-lg text-u-c r-b text-base" ng-click="validReserveEvent()" ng-if="reserve.totalSeats > 0" ng-disabled="attempting">{{ 'app.shared.buttons.confirm' | translate }}</button>
|
||||
</div>
|
||||
|
||||
|
@ -89,12 +89,21 @@ class Event < ApplicationRecord
|
||||
else
|
||||
reserved_places = reservations.joins(:slots_reservations)
|
||||
.where('slots_reservations.canceled_at': nil)
|
||||
.where.not('slots_reservations.validated_at': nil)
|
||||
.map(&:total_booked_seats)
|
||||
.inject(0) { |sum, t| sum + t }
|
||||
self.nb_free_places = (nb_total_places - reserved_places)
|
||||
end
|
||||
end
|
||||
|
||||
def nb_places_for_pre_registration
|
||||
reservations.joins(:slots_reservations)
|
||||
.where('slots_reservations.canceled_at': nil)
|
||||
.where('slots_reservations.validated_at': nil)
|
||||
.map(&:total_booked_seats)
|
||||
.inject(0) { |sum, t| sum + t }
|
||||
end
|
||||
|
||||
def all_day?
|
||||
availability.start_at.hour.zero?
|
||||
end
|
||||
|
@ -60,6 +60,16 @@ class ShoppingCart
|
||||
items.each do |item|
|
||||
objects.push(save_item(item))
|
||||
end
|
||||
event_reservation = objects.find { |o| o.is_a?(Reservation) && o.reservable_type == 'Event' }
|
||||
if event_reservation&.reservable&.pre_registration
|
||||
payment = Invoice.new(
|
||||
invoicing_profile: @customer.invoicing_profile,
|
||||
statistic_profile: @customer.statistic_profile,
|
||||
operator_profile_id: @operator.invoicing_profile.id,
|
||||
payment_method: @payment_method,
|
||||
total: 0
|
||||
)
|
||||
else
|
||||
update_credits(objects)
|
||||
update_packs(objects)
|
||||
|
||||
@ -70,6 +80,7 @@ class ShoppingCart
|
||||
payment.save
|
||||
payment.post_save(payment_id, payment_type)
|
||||
end
|
||||
end
|
||||
|
||||
success = !payment.nil? && objects.map(&:errors).flatten.map(&:empty?).all? && items.map(&:errors).map(&:blank?).all?
|
||||
errors = objects.map(&:errors).flatten.concat(items.map(&:errors))
|
||||
|
@ -5,7 +5,8 @@ class LocalPaymentPolicy < ApplicationPolicy
|
||||
def confirm_payment?
|
||||
# only admins and managers can offer free extensions of a subscription
|
||||
has_free_days = record.shopping_cart.items.any? { |item| item.is_a? CartItem::FreeExtension }
|
||||
event = record.shopping_cart.items.find { |item| item.is_a? CartItem::EventReservation }
|
||||
|
||||
((user.admin? || user.manager?) && record.shopping_cart.customer.id != user.id) || (record.price.zero? && !has_free_days)
|
||||
((user.admin? || user.manager?) && record.shopping_cart.customer.id != user.id) || (record.price.zero? && !has_free_days) || event&.reservable&.pre_registration
|
||||
end
|
||||
end
|
||||
|
@ -9,4 +9,8 @@ class ReservationPolicy < ApplicationPolicy
|
||||
def update?
|
||||
user.admin? || user.manager? || record.user == user
|
||||
end
|
||||
|
||||
def confirm_payment?
|
||||
user.admin? || user.manager?
|
||||
end
|
||||
end
|
||||
|
@ -15,4 +15,8 @@ class SlotsReservationPolicy < ApplicationPolicy
|
||||
def cancel?
|
||||
user.admin? || user.manager? || record.reservation.user == user
|
||||
end
|
||||
|
||||
def validate?
|
||||
user.admin? || user.manager?
|
||||
end
|
||||
end
|
||||
|
82
app/services/reservation_confirm_payment_service.rb
Normal file
82
app/services/reservation_confirm_payment_service.rb
Normal file
@ -0,0 +1,82 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# confirm payment of a pre-registration reservation
|
||||
class ReservationConfirmPaymentService
|
||||
def initialize(reservation, operator, coupon, offered)
|
||||
@reservation = reservation
|
||||
@operator = operator
|
||||
@offered = offered
|
||||
@coupon = CartItem::Coupon.new(
|
||||
customer_profile: @reservation.user.invoicing_profile,
|
||||
operator_profile: @operator.invoicing_profile,
|
||||
coupon: Coupon.find_by(code: coupon)
|
||||
)
|
||||
end
|
||||
|
||||
def total
|
||||
slots_reservations = @reservation.slots_reservations.map do |sr|
|
||||
{
|
||||
slot_id: sr.slot_id,
|
||||
offered: @offered
|
||||
}
|
||||
end
|
||||
tickets = @reservation.tickets.map do |t|
|
||||
{
|
||||
event_price_category_id: t.event_price_category_id,
|
||||
booked: t.booked
|
||||
}
|
||||
end
|
||||
booking_users = @reservation.booking_users.map do |bu|
|
||||
{
|
||||
name: bu.name,
|
||||
event_price_category_id: bu.event_price_category_id,
|
||||
booked_id: bu.booked_id,
|
||||
booked_type: bu.booked_type
|
||||
}
|
||||
end
|
||||
event_reservation = CartItem::EventReservation.new(customer_profile: @reservation.user.invoicing_profile,
|
||||
operator_profile: @operator.invoicing_profile,
|
||||
event: @reservation.reservable,
|
||||
cart_item_reservation_slots_attributes: slots_reservations,
|
||||
normal_tickets: @reservation.nb_reserve_places,
|
||||
cart_item_event_reservation_tickets_attributes: tickets,
|
||||
cart_item_event_reservation_booking_users_attributes: booking_users)
|
||||
|
||||
all_elements = {
|
||||
slots: @reservation.slots_reservations.map do |sr|
|
||||
{ start_at: sr.slot.start_at, end_at: sr.slot.end_at, price: event_reservation.price[:amount] }
|
||||
end
|
||||
}
|
||||
|
||||
total_amount = event_reservation.price[:amount]
|
||||
|
||||
coupon_info = @coupon.price(total_amount)
|
||||
|
||||
# return result
|
||||
{
|
||||
elements: all_elements,
|
||||
total: coupon_info[:total_with_coupon].to_i,
|
||||
before_coupon: coupon_info[:total_without_coupon].to_i,
|
||||
coupon: @coupon.coupon
|
||||
}
|
||||
end
|
||||
|
||||
def call
|
||||
price = total
|
||||
invoice = InvoicesService.create(
|
||||
price,
|
||||
@operator.invoicing_profile.id,
|
||||
[@reservation],
|
||||
@reservation.user
|
||||
)
|
||||
return invoice if Setting.get('prevent_invoices_zero') && price[:total].zero?
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
WalletService.debit_user_wallet(invoice, @reservation.user)
|
||||
|
||||
invoice.save
|
||||
invoice.post_save
|
||||
end
|
||||
invoice
|
||||
end
|
||||
end
|
@ -21,5 +21,23 @@ class SlotsReservationsService
|
||||
rescue Faraday::ConnectionFailed
|
||||
warn 'Unable to update data in elasticsearch'
|
||||
end
|
||||
|
||||
def validate(slot_reservation)
|
||||
if slot_reservation.update(validated_at: Time.current)
|
||||
reservable = slot_reservation.reservation.reservable
|
||||
if reservable.is_a?(Event)
|
||||
reservable.update_nb_free_places
|
||||
reservable.save
|
||||
end
|
||||
NotificationCenter.call type: 'notify_member_reservation_validated',
|
||||
receiver: slot_reservation.reservation.user,
|
||||
attached_object: slot_reservation.reservation
|
||||
NotificationCenter.call type: 'notify_admin_reservation_validated',
|
||||
receiver: User.admins_and_managers,
|
||||
attached_object: slot_reservation.reservation
|
||||
return true
|
||||
end
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,6 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
json.extract! event, :id, :title, :description, :event_type
|
||||
json.extract! event, :id, :title, :description, :event_type, :pre_registration, :pre_registration_end_date
|
||||
json.pre_registration_end_date event.pre_registration_end_date&.to_date
|
||||
json.nb_places_for_pre_registration event.nb_places_for_pre_registration
|
||||
if event.event_image
|
||||
json.event_image_attributes do
|
||||
json.id event.event_image.id
|
||||
|
@ -11,8 +11,8 @@ json.is_online_card invoice.paid_by_card?
|
||||
json.date invoice.is_a?(Avoir) ? invoice.avoir_date : invoice.created_at
|
||||
json.chained_footprint invoice.check_footprint
|
||||
json.main_object do
|
||||
json.type invoice.invoice_items.find(&:main).object_type
|
||||
json.id invoice.invoice_items.find(&:main).object_id
|
||||
json.type invoice.invoice_items.find(&:main)&.object_type
|
||||
json.id invoice.invoice_items.find(&:main)&.object_id
|
||||
end
|
||||
json.items invoice.invoice_items do |item|
|
||||
json.id item.id
|
||||
|
@ -0,0 +1,4 @@
|
||||
json.title notification.notification_type
|
||||
json.description t('.a_RESERVABLE_reservation_was_validated_html',
|
||||
RESERVABLE: notification.attached_object.reservable.name,
|
||||
NAME: notification.attached_object.user&.profile&.full_name || t('api.notifications.deleted_user'))
|
@ -0,0 +1,3 @@
|
||||
json.title notification.notification_type
|
||||
json.description t('.your_reservation_RESERVABLE_was_validated_html',
|
||||
RESERVABLE: notification.attached_object.reservable.name)
|
@ -7,6 +7,8 @@ json.message reservation.message
|
||||
json.slots_reservations_attributes reservation.slots_reservations do |sr|
|
||||
json.id sr.id
|
||||
json.canceled_at sr.canceled_at&.iso8601
|
||||
json.validated_at sr.validated_at&.iso8601
|
||||
json.slot_id sr.slot_id
|
||||
json.slot_attributes do
|
||||
json.id sr.slot_id
|
||||
json.start_at sr.slot.start_at.iso8601
|
||||
@ -39,3 +41,4 @@ json.booking_users_attributes reservation.booking_users.order(booked_type: :desc
|
||||
json.booked_id bu.booked_id
|
||||
json.booked_type bu.booked_type
|
||||
end
|
||||
json.is_paid reservation.invoice_items.count.positive?
|
||||
|
@ -0,0 +1,19 @@
|
||||
<%= render 'notifications_mailer/shared/hello', recipient: @recipient %>
|
||||
|
||||
<p>
|
||||
<%= t('.body.reservation_validated_html',
|
||||
NAME: @attached_object.user&.profile&.full_name || t('api.notifications.deleted_user'),
|
||||
RESERVABLE: @attached_object.reservable.name) %>
|
||||
</p>
|
||||
<p><%= t('.body.reserved_slots') %></p>
|
||||
<ul>
|
||||
<% @attached_object.slots.each do |slot| %>
|
||||
<% if @attached_object.reservable_type == 'Event' %>
|
||||
<% (slot.start_at.to_date..slot.end_at.to_date).each do |d| %>
|
||||
<li><%= "#{I18n.l d, format: :long} #{I18n.l slot.start_at, format: :hour_minute} - #{I18n.l slot.end_at, format: :hour_minute}" %></li>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<li><%= "#{I18n.l slot.start_at, format: :long} - #{I18n.l slot.end_at, format: :hour_minute}" %></li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ul>
|
@ -0,0 +1,16 @@
|
||||
<%= render 'notifications_mailer/shared/hello', recipient: @recipient %>
|
||||
|
||||
<p><%= t('.body.reservation_validated_html', RESERVATION: @attached_object.reservable.name) %></p>
|
||||
|
||||
<p><%= t('.body.your_reserved_slots') %> </p>
|
||||
<ul>
|
||||
<% @attached_object.slots.each do |slot| %>
|
||||
<% if @attached_object.reservable_type == 'Event' %>
|
||||
<% (slot.start_at.to_date..slot.end_at.to_date).each do |d| %>
|
||||
<li><%= "#{I18n.l d, format: :long} #{I18n.l slot.start_at, format: :hour_minute} - #{I18n.l slot.end_at, format: :hour_minute}" %></li>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<li><%= "#{I18n.l slot.start_at, format: :long} - #{I18n.l slot.end_at, format: :hour_minute}" %></li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ul>
|
@ -156,6 +156,9 @@ en:
|
||||
standard: "Event standard"
|
||||
nominative: "Event nominative"
|
||||
family: "Event family"
|
||||
pre_registration: "Pre-registration"
|
||||
pre_registration_help: "If this option is checked, administrators and managers must validate registrations before they become final."
|
||||
pre_registration_end_date: "Pre-registration end date"
|
||||
plan_form:
|
||||
ACTION_title: "{ACTION, select, create{New} other{Update the}} plan"
|
||||
tab_settings: "Settings"
|
||||
@ -592,6 +595,13 @@ en:
|
||||
do_you_really_want_to_delete_this_price_category: "Do you really want to delete this price category?"
|
||||
price_category_successfully_deleted: "Price category successfully deleted."
|
||||
price_category_deletion_failed: "Price category deletion failed."
|
||||
types: "Types"
|
||||
event_type:
|
||||
standard: "Standard"
|
||||
family: "Family"
|
||||
nominative: "Nominative"
|
||||
pre_registration: "Pre-registration"
|
||||
NUMBER_pre_registered: " {NUMBER} pre-registered"
|
||||
#add a new event
|
||||
events_new:
|
||||
add_an_event: "Add an event"
|
||||
@ -626,6 +636,26 @@ en:
|
||||
no_reservations_for_now: "No reservation for now."
|
||||
back_to_monitoring: "Back to monitoring"
|
||||
canceled: "Canceled"
|
||||
date: "Date"
|
||||
booked_by: "Booked by"
|
||||
reservations: "Reservations"
|
||||
status: "Status"
|
||||
gestion: "Gestion"
|
||||
event_status:
|
||||
pre_registered: "Pre-registered"
|
||||
to_pay: "To pay"
|
||||
paid: "Paid"
|
||||
canceled: "Canceled"
|
||||
validate: "Validate"
|
||||
pay: "Pay"
|
||||
validate_the_reservation: "Validate the reservation"
|
||||
do_you_really_want_to_validate_this_reservation_this_apply_to_all_booked_tickets: "Do you really want to validate this reservation? This apply to ALL booked tickets."
|
||||
reservation_was_successfully_validated: "Reservation was successfully validated."
|
||||
validation_failed: "Validation failed."
|
||||
confirm_payment: "Confirm payment"
|
||||
confirm_payment_of_html: "{ROLE, select, admin{Cash} other{Pay}}: {AMOUNT}" #(contexte : validate a payment of $20,00)
|
||||
offer_this_reservation: "I offer this reservation"
|
||||
i_have_received_the_payment: "I have received the payment"
|
||||
events_settings:
|
||||
title: "Settings"
|
||||
generic_text_block: "Editorial text block"
|
||||
|
@ -156,6 +156,9 @@ fr:
|
||||
standard: "Evénement standard"
|
||||
nominative: "Evénement nominatif"
|
||||
family: "Evénement famille"
|
||||
pre_registration: "Pré-inscription"
|
||||
pre_registration_help: "Si cette option est cochée, les administrateurs et les gestionnaires devent valider les inscriptions avant qu'elles ne soient définitives."
|
||||
pre_registration_end_date: "Date de fin de pré-inscription"
|
||||
plan_form:
|
||||
ACTION_title: "{ACTION, select, create{Nouvelle} other{Mettre à jour la}} formule d'abonnement"
|
||||
tab_settings: "Paramètres"
|
||||
@ -592,6 +595,13 @@ fr:
|
||||
do_you_really_want_to_delete_this_price_category: "Êtes vous sur de vouloir supprimer cette catégorie tarifaire ?"
|
||||
price_category_successfully_deleted: "Catégorie tarifaire supprimée avec succès."
|
||||
price_category_deletion_failed: "Échec de la suppression de la catégorie tarifaire."
|
||||
types: 'Types'
|
||||
event_type:
|
||||
standard: 'Standard'
|
||||
family: "Famille"
|
||||
nominative: "Nominatif"
|
||||
pre_registration: "Pré-inscription"
|
||||
NUMBER_pre_registered: " {NUMBER} pré-inscrit"
|
||||
#add a new event
|
||||
events_new:
|
||||
add_an_event: "Ajouter un événement"
|
||||
@ -626,6 +636,26 @@ fr:
|
||||
no_reservations_for_now: "Aucune réservation pour le moment."
|
||||
back_to_monitoring: "Retour au suivi"
|
||||
canceled: "Annulée"
|
||||
date: "Date"
|
||||
booked_by: "Réservé par"
|
||||
reservations: "Réservations"
|
||||
status: "Statut"
|
||||
gestion: "Gestion"
|
||||
event_status:
|
||||
pre_registered: "Pré-inscrit"
|
||||
to_pay: "À payer"
|
||||
paid: "Payé"
|
||||
canceled: "Annulée"
|
||||
validate: "Valider"
|
||||
pay: "Payer"
|
||||
validate_the_reservation: "Valider la réservation"
|
||||
do_you_really_want_to_validate_this_reservation_this_apply_to_all_booked_tickets: "Êtes vous sur de vouloir valider cette réservation? Ceci s'applique à TOUTES les places réservées."
|
||||
reservation_was_successfully_validated: "La réservation a bien été validé."
|
||||
validation_failed: "La validation a échoué."
|
||||
confirm_payment: "Confirmer le paiement"
|
||||
confirm_payment_of_html: "{ROLE, select, admin{Encaisser} other{Payer}} : {AMOUNT}" #(contexte : validate a payment of $20,00)
|
||||
offer_this_reservation: "J'offre cette réservation"
|
||||
i_have_received_the_payment: "J'ai reçu le paiement"
|
||||
events_settings:
|
||||
title: "Paramètres"
|
||||
generic_text_block: "Bloc de texte rédactionnel"
|
||||
|
@ -330,9 +330,11 @@ en:
|
||||
ticket: "{NUMBER, plural, one{ticket} other{tickets}}"
|
||||
make_a_gift_of_this_reservation: "Make a gift of this reservation"
|
||||
thank_you_your_payment_has_been_successfully_registered: "Thank you. Your payment has been successfully registered!"
|
||||
thank_you_your_pre_registration_has_been_successfully_saved: "Thank you. Your pre-registration has been successfully saved!"
|
||||
you_can_find_your_reservation_s_details_on_your_: "You can find your reservation's details on your"
|
||||
dashboard: "dashboard"
|
||||
you_booked_DATE: "You booked ({DATE}):"
|
||||
you_pre_booked_DATE: "You pre-booked ({DATE}):"
|
||||
canceled_reservation_SEATS: "Reservation canceled ({SEATS} seats)"
|
||||
book: "Book"
|
||||
confirm_and_pay: "Confirm and pay"
|
||||
@ -361,6 +363,8 @@ en:
|
||||
share_on_facebook: "Share on Facebook"
|
||||
share_on_twitter: "Share on Twitter"
|
||||
last_name_and_first_name: "Last name and first name"
|
||||
pre_book: "Pre-book"
|
||||
pre_registration_end_date: "Pre-registration end date"
|
||||
#public calendar
|
||||
calendar:
|
||||
calendar: "Calendar"
|
||||
|
@ -330,9 +330,11 @@ fr:
|
||||
ticket: "{NUMBER, plural, =0{place} one{place} other{places}}"
|
||||
make_a_gift_of_this_reservation: "Offrir cette réservation"
|
||||
thank_you_your_payment_has_been_successfully_registered: "Merci. Votre paiement a bien été pris en compte !"
|
||||
thank_you_your_pre_registration_has_been_successfully_saved: "Merci. Votre pré-inscription a bien été pris en compte !"
|
||||
you_can_find_your_reservation_s_details_on_your_: "Vous pouvez retrouver le détail de votre réservation sur votre"
|
||||
dashboard: "tableau de bord"
|
||||
you_booked_DATE: "Vous avez réservé ({DATE}) :"
|
||||
you_pre_booked_DATE: "Vous avez pré-réservé ({DATE}) :"
|
||||
canceled_reservation_SEATS: "Réservation annulée ({SEATS} places)"
|
||||
book: "Réserver"
|
||||
confirm_and_pay: "Valider et payer"
|
||||
@ -361,6 +363,8 @@ fr:
|
||||
share_on_facebook: "Partager sur Facebook"
|
||||
share_on_twitter: "Partager sur Twitter"
|
||||
last_name_and_first_name: "Nom et prénom"
|
||||
pre_book: "Pré-réserver"
|
||||
pre_registration_end_date: "Date de fin de pré-réservation"
|
||||
#public calendar
|
||||
calendar:
|
||||
calendar: "Calendrier"
|
||||
|
@ -469,6 +469,10 @@ en:
|
||||
order_canceled: "Your command %{REFERENCE} is canceled"
|
||||
notify_user_order_is_refunded:
|
||||
order_refunded: "Your command %{REFERENCE} is refunded"
|
||||
notify_member_reservation_validated:
|
||||
your_reservation_RESERVABLE_was_validated_html: "Your reservation <strong><em>%{RESERVABLE}</em></strong> was successfully validated."
|
||||
notify_admin_reservation_validated:
|
||||
a_RESERVABLE_reservation_was_validated_html: "A <strong><em>%{RESERVABLE}</em></strong> reservation of <strong><em>%{USER}</em></strong> was validated."
|
||||
#statistics tools for admins
|
||||
statistics:
|
||||
subscriptions: "Subscriptions"
|
||||
|
@ -469,6 +469,10 @@ fr:
|
||||
order_canceled: "Votre commande %{REFERENCE} est annulée"
|
||||
notify_user_order_is_refunded:
|
||||
order_refunded: "Votre commande %{REFERENCE} est remboursée"
|
||||
notify_member_reservation_validated:
|
||||
your_reservation_RESERVABLE_was_validated_html: "Votre réservation de <strong><em>%{RESERVABLE}</em></strong> a été validée."
|
||||
notify_admin_reservation_validated:
|
||||
a_RESERVABLE_reservation_was_validated_html: "La réservation de <strong><em>%{RESERVABLE}</em></strong> de <strong><em>%{NAME}</em></strong> a été validée."
|
||||
#statistics tools for admins
|
||||
statistics:
|
||||
subscriptions: "Abonnements"
|
||||
|
@ -451,3 +451,13 @@ en:
|
||||
subject: "Your command was refunded"
|
||||
body:
|
||||
notify_user_order_is_refunded: "Your command %{REFERENCE} was refunded."
|
||||
notify_member_reservation_validated:
|
||||
subject: "Your reservation was validated"
|
||||
body:
|
||||
reservation_validated_html: "<strong><em>%{RESERVABLE}</em></strong> was validated."
|
||||
your_reserved_slots: "Your reserved slots are:"
|
||||
notify_admin_reservation_validated:
|
||||
subject: "Réservation a bien été validé"
|
||||
body:
|
||||
reservation_validated_html: "<strong><em>%{RESERVABLE}</em></strong> of %{NAME} was validated."
|
||||
reserved_slots: "Reserved slots are:"
|
||||
|
@ -451,3 +451,13 @@ fr:
|
||||
subject: "Votre commande est remboursée"
|
||||
body:
|
||||
notify_user_order_is_refunded: "Votre commande %{REFERENCE} est remboursée :"
|
||||
notify_member_reservation_validated:
|
||||
subject: "Votre réservation a bien été validé"
|
||||
body:
|
||||
reservation_validated_html: "Votre réservation <strong><em>%{RESERVATION}</em></strong> a bien été validé."
|
||||
your_reserved_slots: "Les créneaux que vous avez réservés sont :"
|
||||
notify_admin_reservation_validated:
|
||||
subject: "Réservation a bien été validé"
|
||||
body:
|
||||
reservation_validated_html: "<strong><em>%{RESERVABLE}</em></strong> du membre %{NAME} a bien été validé."
|
||||
reserved_slots: "Les créneaux réservés sont :"
|
||||
|
@ -66,7 +66,9 @@ Rails.application.routes.draw do
|
||||
patch ':id/update_role', action: 'update_role', on: :collection
|
||||
patch ':id/validate', action: 'validate', on: :collection
|
||||
end
|
||||
resources :reservations, only: %i[show index update]
|
||||
resources :reservations, only: %i[show index update] do
|
||||
post :confirm_payment, on: :collection
|
||||
end
|
||||
resources :notifications, only: %i[index show update] do
|
||||
match :update_all, path: '/', via: %i[put patch], on: :collection
|
||||
get 'polling', action: 'polling', on: :collection
|
||||
@ -121,6 +123,7 @@ Rails.application.routes.draw do
|
||||
end
|
||||
resources :slots_reservations, only: [:update] do
|
||||
put 'cancel', on: :member
|
||||
put 'validate', on: :member
|
||||
end
|
||||
|
||||
resources :events do
|
||||
|
@ -0,0 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Add pre-registration and pre_registration_end_date to event
|
||||
class AddPreRegistrationToEvent < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :events, :pre_registration, :boolean, default: false
|
||||
add_column :events, :pre_registration_end_date, :datetime
|
||||
end
|
||||
end
|
@ -0,0 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# add validated_at to slots_reservations
|
||||
class AddValidatedAtToSlotsReservations < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :slots_reservations, :validated_at, :datetime
|
||||
end
|
||||
end
|
@ -1,89 +1,104 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
unless NotificationType.find_by(name: 'notify_member_training_authorization_expired')
|
||||
NotificationType.create!(
|
||||
name: 'notify_member_training_authorization_expired',
|
||||
category: 'trainings',
|
||||
is_configurable: false
|
||||
)
|
||||
end
|
||||
NOTIFICATIONS_TYPES = [
|
||||
{ name: 'notify_admin_when_project_published', category: 'projects', is_configurable: true },
|
||||
{ name: 'notify_project_collaborator_to_valid', category: 'projects', is_configurable: false },
|
||||
{ name: 'notify_project_author_when_collaborator_valid', category: 'projects', is_configurable: true },
|
||||
{ name: 'notify_user_training_valid', category: 'trainings', is_configurable: false },
|
||||
{ name: 'notify_member_subscribed_plan', category: 'subscriptions', is_configurable: false },
|
||||
{ name: 'notify_member_create_reservation', category: 'agenda', is_configurable: false },
|
||||
{ name: 'notify_member_subscribed_plan_is_changed', category: 'deprecated', is_configurable: false },
|
||||
{ name: 'notify_admin_member_create_reservation', category: 'agenda', is_configurable: true },
|
||||
{ name: 'notify_member_slot_is_modified', category: 'agenda', is_configurable: false },
|
||||
{ name: 'notify_admin_slot_is_modified', category: 'agenda', is_configurable: true },
|
||||
|
||||
unless NotificationType.find_by(name: 'notify_member_training_invalidated')
|
||||
NotificationType.create!(
|
||||
name: 'notify_member_training_invalidated',
|
||||
category: 'trainings',
|
||||
is_configurable: false
|
||||
)
|
||||
end
|
||||
{ name: 'notify_admin_when_user_is_created', category: 'users_accounts', is_configurable: true },
|
||||
{ name: 'notify_admin_subscribed_plan', category: 'subscriptions', is_configurable: true },
|
||||
{ name: 'notify_user_when_invoice_ready', category: 'payments', is_configurable: true },
|
||||
{ name: 'notify_member_subscription_will_expire_in_7_days', category: 'subscriptions', is_configurable: false },
|
||||
{ name: 'notify_member_subscription_is_expired', category: 'subscriptions', is_configurable: false },
|
||||
{ name: 'notify_admin_subscription_will_expire_in_7_days', category: 'subscriptions', is_configurable: true },
|
||||
{ name: 'notify_admin_subscription_is_expired', category: 'subscriptions', is_configurable: true },
|
||||
{ name: 'notify_admin_subscription_canceled', category: 'subscriptions', is_configurable: true },
|
||||
{ name: 'notify_member_subscription_canceled', category: 'subscriptions', is_configurable: false },
|
||||
{ name: 'notify_user_when_avoir_ready', category: 'wallet', is_configurable: false },
|
||||
|
||||
unless NotificationType.find_by(name: 'notify_admin_order_is_paid')
|
||||
NotificationType.create!(
|
||||
name: 'notify_admin_order_is_paid',
|
||||
category: 'shop',
|
||||
is_configurable: true
|
||||
)
|
||||
end
|
||||
{ name: 'notify_member_slot_is_canceled', category: 'agenda', is_configurable: false },
|
||||
{ name: 'notify_admin_slot_is_canceled', category: 'agenda', is_configurable: true },
|
||||
{ name: 'notify_partner_subscribed_plan', category: 'subscriptions', is_configurable: false },
|
||||
{ name: 'notify_member_subscription_extended', category: 'subscriptions', is_configurable: false },
|
||||
{ name: 'notify_admin_subscription_extended', category: 'subscriptions', is_configurable: true },
|
||||
{ name: 'notify_admin_user_group_changed', category: 'users_accounts', is_configurable: true },
|
||||
{ name: 'notify_user_user_group_changed', category: 'users_accounts', is_configurable: false },
|
||||
{ name: 'notify_admin_when_user_is_imported', category: 'users_accounts', is_configurable: true },
|
||||
{ name: 'notify_user_profile_complete', category: 'users_accounts', is_configurable: false },
|
||||
{ name: 'notify_user_auth_migration', category: 'user', is_configurable: false },
|
||||
|
||||
unless NotificationType.find_by(name: 'notify_member_reservation_limit_reached')
|
||||
NotificationType.create!(
|
||||
name: 'notify_member_reservation_limit_reached',
|
||||
category: 'agenda',
|
||||
is_configurable: false
|
||||
)
|
||||
end
|
||||
{ name: 'notify_admin_user_merged', category: 'users_accounts', is_configurable: true },
|
||||
{ name: 'notify_admin_profile_complete', category: 'users_accounts', is_configurable: true },
|
||||
{ name: 'notify_admin_abuse_reported', category: 'projects', is_configurable: true },
|
||||
{ name: 'notify_admin_invoicing_changed', category: 'deprecated', is_configurable: false },
|
||||
{ name: 'notify_user_wallet_is_credited', category: 'wallet', is_configurable: false },
|
||||
{ name: 'notify_admin_user_wallet_is_credited', category: 'wallet', is_configurable: true },
|
||||
{ name: 'notify_admin_export_complete', category: 'exports', is_configurable: false },
|
||||
{ name: 'notify_member_about_coupon', category: 'agenda', is_configurable: false },
|
||||
{ name: 'notify_member_reservation_reminder', category: 'agenda', is_configurable: false },
|
||||
|
||||
unless NotificationType.find_by(name: 'notify_admin_user_child_supporting_document_refusal')
|
||||
NotificationType.create!(
|
||||
name: 'notify_admin_user_child_supporting_document_refusal',
|
||||
category: 'supporting_documents',
|
||||
is_configurable: true
|
||||
)
|
||||
end
|
||||
{ name: 'notify_admin_free_disk_space', category: 'app_management', is_configurable: false },
|
||||
{ name: 'notify_admin_close_period_reminder', category: 'accountings', is_configurable: true },
|
||||
{ name: 'notify_admin_archive_complete', category: 'accountings', is_configurable: true },
|
||||
{ name: 'notify_privacy_policy_changed', category: 'app_management', is_configurable: false },
|
||||
{ name: 'notify_admin_import_complete', category: 'app_management', is_configurable: false },
|
||||
{ name: 'notify_admin_refund_created', category: 'wallet', is_configurable: true },
|
||||
{ name: 'notify_admins_role_update', category: 'users_accounts', is_configurable: true },
|
||||
{ name: 'notify_user_role_update', category: 'users_accounts', is_configurable: false },
|
||||
{ name: 'notify_admin_objects_stripe_sync', category: 'payments', is_configurable: false },
|
||||
{ name: 'notify_user_when_payment_schedule_ready', category: 'payments', is_configurable: false },
|
||||
|
||||
unless NotificationType.find_by(name: 'notify_user_child_supporting_document_refusal')
|
||||
NotificationType.create!(
|
||||
name: 'notify_user_child_supporting_document_refusal',
|
||||
category: 'supporting_documents',
|
||||
is_configurable: false
|
||||
)
|
||||
end
|
||||
{ name: 'notify_admin_payment_schedule_failed', category: 'payments', is_configurable: true },
|
||||
{ name: 'notify_member_payment_schedule_failed', category: 'payments', is_configurable: false },
|
||||
{ name: 'notify_admin_payment_schedule_check_deadline', category: 'payments', is_configurable: true },
|
||||
{ name: 'notify_admin_payment_schedule_transfer_deadline', category: 'payments', is_configurable: true },
|
||||
{ name: 'notify_admin_payment_schedule_error', category: 'payments', is_configurable: true },
|
||||
{ name: 'notify_member_payment_schedule_error', category: 'payments', is_configurable: false },
|
||||
{ name: 'notify_admin_payment_schedule_gateway_canceled', category: 'payments', is_configurable: true },
|
||||
{ name: 'notify_member_payment_schedule_gateway_canceled', category: 'payments', is_configurable: false },
|
||||
{ name: 'notify_admin_user_supporting_document_files_created', category: 'supporting_documents', is_configurable: true },
|
||||
{ name: 'notify_admin_user_supporting_document_files_updated', category: 'supporting_documents', is_configurable: true },
|
||||
|
||||
unless NotificationType.find_by(name: 'notify_admin_child_created')
|
||||
NotificationType.create!(
|
||||
name: 'notify_admin_child_created',
|
||||
category: 'users_accounts',
|
||||
is_configurable: true
|
||||
)
|
||||
end
|
||||
{ name: 'notify_user_is_validated', category: 'users_accounts', is_configurable: false },
|
||||
{ name: 'notify_user_is_invalidated', category: 'users_accounts', is_configurable: false },
|
||||
{ name: 'notify_user_supporting_document_refusal', category: 'supporting_documents', is_configurable: false },
|
||||
{ name: 'notify_admin_user_supporting_document_refusal', category: 'supporting_documents', is_configurable: true },
|
||||
{ name: 'notify_user_order_is_ready', category: 'shop', is_configurable: false },
|
||||
{ name: 'notify_user_order_is_canceled', category: 'shop', is_configurable: false },
|
||||
{ name: 'notify_user_order_is_refunded', category: 'shop', is_configurable: false },
|
||||
{ name: 'notify_admin_low_stock_threshold', category: 'shop', is_configurable: true },
|
||||
{ name: 'notify_admin_training_auto_cancelled', category: 'trainings', is_configurable: true },
|
||||
{ name: 'notify_member_training_auto_cancelled', category: 'trainings', is_configurable: false },
|
||||
|
||||
unless NotificationType.find_by(name: 'notify_user_child_is_validated')
|
||||
NotificationType.create!(
|
||||
name: 'notify_user_child_is_validated',
|
||||
category: 'users_accounts',
|
||||
is_configurable: false
|
||||
)
|
||||
end
|
||||
{ name: 'notify_member_training_authorization_expired', category: 'trainings', is_configurable: false },
|
||||
{ name: 'notify_member_training_invalidated', category: 'trainings', is_configurable: false },
|
||||
{ name: 'notify_admin_order_is_paid', category: 'shop', is_configurable: true },
|
||||
{ name: 'notify_member_reservation_limit_reached', category: 'agenda', is_configurable: false },
|
||||
{ name: 'notify_admin_user_child_supporting_document_refusal', category: 'supporting_documents', is_configurable: true },
|
||||
{ name: 'notify_user_child_supporting_document_refusal', category: 'supporting_documents', is_configurable: false },
|
||||
{ name: 'notify_admin_child_created', category: 'users_accounts', is_configurable: true },
|
||||
{ name: 'notify_user_child_is_validated', category: 'users_accounts', is_configurable: false },
|
||||
{ name: 'notify_user_child_is_invalidated', category: 'users_accounts', is_configurable: false },
|
||||
{ name: 'notify_admin_user_child_supporting_document_files_updated', category: 'supporting_documents', is_configurable: true },
|
||||
{ name: 'notify_admin_user_child_supporting_document_files_created', category: 'supporting_documents', is_configurable: true },
|
||||
|
||||
unless NotificationType.find_by(name: 'notify_user_child_is_invalidated')
|
||||
NotificationType.create!(
|
||||
name: 'notify_user_child_is_invalidated',
|
||||
category: 'users_accounts',
|
||||
is_configurable: false
|
||||
)
|
||||
end
|
||||
{ name: 'notify_member_reservation_validated', category: 'agenda', is_configurable: false },
|
||||
{ name: 'notify_admin_reservation_validated', category: 'agenda', is_configurable: true }
|
||||
].freeze
|
||||
|
||||
unless NotificationType.find_by(name: 'notify_admin_user_child_supporting_document_files_updated')
|
||||
NotificationType.create!(
|
||||
name: 'notify_admin_user_child_supporting_document_files_updated',
|
||||
category: 'supporting_documents',
|
||||
is_configurable: true
|
||||
)
|
||||
end
|
||||
NOTIFICATIONS_TYPES.each do |notification_type|
|
||||
next if NotificationType.find_by(name: notification_type[:name])
|
||||
|
||||
unless NotificationType.find_by(name: 'notify_admin_user_child_supporting_document_files_created')
|
||||
NotificationType.create!(
|
||||
name: 'notify_admin_user_child_supporting_document_files_created',
|
||||
category: 'supporting_documents',
|
||||
is_configurable: true
|
||||
name: notification_type[:name],
|
||||
category: notification_type[:category],
|
||||
is_configurable: notification_type[:is_configurable]
|
||||
)
|
||||
end
|
||||
|
@ -1244,7 +1244,9 @@ CREATE TABLE public.events (
|
||||
age_range_id integer,
|
||||
category_id integer,
|
||||
deleted_at timestamp without time zone,
|
||||
event_type character varying DEFAULT 'standard'::character varying
|
||||
event_type character varying DEFAULT 'standard'::character varying,
|
||||
pre_registration boolean DEFAULT false,
|
||||
pre_registration_end_date timestamp(6) without time zone
|
||||
);
|
||||
|
||||
|
||||
@ -3214,7 +3216,8 @@ CREATE TABLE public.slots_reservations (
|
||||
ex_start_at timestamp without time zone,
|
||||
ex_end_at timestamp without time zone,
|
||||
canceled_at timestamp without time zone,
|
||||
offered boolean DEFAULT false
|
||||
offered boolean DEFAULT false,
|
||||
validated_at timestamp(6) without time zone
|
||||
);
|
||||
|
||||
|
||||
@ -8937,6 +8940,8 @@ INSERT INTO "schema_migrations" (version) VALUES
|
||||
('20230524080448'),
|
||||
('20230524083558'),
|
||||
('20230524110215'),
|
||||
('20230525101006');
|
||||
('20230525101006'),
|
||||
('20230612123250'),
|
||||
('20230626103314');
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user