From 73e4439036160f868d42d6b721c2d82ae5358d5a Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 9 Nov 2016 17:07:48 +0100 Subject: [PATCH 001/135] front-end modifications to cancel a booked event --- .../javascripts/controllers/events.coffee.erb | 58 ++++++++++++++++--- app/assets/javascripts/router.coffee.erb | 2 +- app/assets/templates/events/show.html.erb | 3 + app/models/reservation.rb | 8 ++- config/locales/app.logged.en.yml | 2 +- config/locales/app.public.en.yml | 3 + config/locales/app.public.fr.yml | 3 + 7 files changed, 66 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/controllers/events.coffee.erb b/app/assets/javascripts/controllers/events.coffee.erb index 39083272f..88408a28b 100644 --- a/app/assets/javascripts/controllers/events.coffee.erb +++ b/app/assets/javascripts/controllers/events.coffee.erb @@ -132,8 +132,8 @@ Application.Controllers.controller "EventsController", ["$scope", "$state", 'Eve -Application.Controllers.controller "ShowEventController", ["$scope", "$state", "$stateParams", "Event", '$uibModal', 'Member', 'Reservation', 'Price', 'CustomAsset', 'eventPromise', 'growl', '_t', 'Wallet', 'helpers', 'priceCategoriesPromise', 'settingsPromise', -($scope, $state, $stateParams, Event, $uibModal, Member, Reservation, Price, CustomAsset, eventPromise, growl, _t, Wallet, helpers, priceCategoriesPromise, settingsPromise) -> +Application.Controllers.controller "ShowEventController", ["$scope", "$state", "$stateParams", "Event", '$uibModal', 'Member', 'Reservation', 'Price', 'CustomAsset', 'Slot', 'eventPromise', 'growl', '_t', 'Wallet', 'helpers', 'priceCategoriesPromise', 'settingsPromise', 'dialogs', +($scope, $state, $stateParams, Event, $uibModal, Member, Reservation, Price, CustomAsset, Slot, eventPromise, growl, _t, Wallet, helpers, priceCategoriesPromise, settingsPromise, dialogs) -> @@ -169,9 +169,15 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", " ## Global config: is the user authorized to change his bookings slots? $scope.enableBookingMove = (settingsPromise.booking_move_enable == "true") - ## Global config: delay in hours before a booking while changing the booking slot is forbidden + ## Global config: delay in hours from now while changing the booking slot is forbidden $scope.moveBookingDelay = parseInt(settingsPromise.booking_move_delay) + ## Global config: is the user authorized to cancel his booking slots? + $scope.enableBookingCancel = (settingsPromise.booking_cancel_enable == "true") + + ## Global config: delay in hours from now when rectrictions occurs about cancelling reservations + $scope.cancelBookingDelay = parseInt(settingsPromise.booking_cancel_delay) + ## @@ -236,7 +242,7 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", " - ## + ##reservationCanCan # Callback to deal with the reservations of the user selected in the dropdown list instead of the current user's # reservations. (admins only) ## @@ -311,6 +317,29 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", " + ## + # + # @param reservation {{id:number, reservable_id:number, nb_reserve_places:number}} + # @param e {Object} see https://docs.angularjs.org/guide/expression#-event- + ## + $scope.cancelReservation = (reservation, e)-> + e.preventDefault() + e.stopPropagation() + + dialogs.confirm + resolve: + object: -> + title: _t('cancel_the_reservation') + msg: _t('do_you_really_want_to_cancel_this_reservation_this_apply_to_all_booked_tickets') + , -> # cancel confirmed + Slot.cancel {id: reservation.slots[0].id}, -> # successfully canceled + growl.success _t('reservation_was_successfully_cancelled') + index = $scope.reservations.indexOf(reservation) + $scope.event.nb_free_places = $scope.event.nb_free_places + reservation.total_booked_seats + $scope.reservations.splice(index, 1) + , (error)-> + growl.warning(_t('cancellation_failed')) + ## # Callback to alter an already booked reservation date. A modal window will be opened to allow the user to choose # a new date for his reservation (if any available) @@ -333,9 +362,9 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", " $scope.reservation = angular.copy reservation # set the reservable_id to the first available event - for e in event.recurrence_events - if e.nb_free_places > reservation.total_booked_seats - $scope.reservation.reservable_id = e.id + for evt in event.recurrence_events + if evt.nb_free_places > reservation.total_booked_seats + $scope.reservation.reservable_id = evt.id break # Callback to validate the new reservation's date @@ -377,7 +406,7 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", " ## # Checks if the provided reservation is able to be moved (date change) - # @param reservation {{total_booked_seats:number}} + # @param reservation {{slots:[], total_booked_seats:number}} ## $scope.reservationCanModify = (reservation)-> slotStart = moment(reservation.slots[0].start_at) @@ -390,6 +419,17 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", " + ## + # Checks if the provided reservation is able to be cancelled + # @param reservation {{slots:[]}} + ## + $scope.reservationCanCancel = (reservation)-> + slotStart = moment(reservation.slots[0].start_at) + now = moment() + + return ($scope.enableBookingCancel and slotStart.diff(now, "hours") >= $scope.cancelBookingDelay) + + ## # Compute the total amount for the current reservation according to the previously set parameters # and assign the result in $scope.reserve.amountTotal @@ -516,7 +556,7 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", " ## - # Set the current reservation to the default values. This implies to reservation form to be hidden. + # Set the current reservation to the default values. This implies the reservation form to be hidden. ## resetEventReserve = -> if $scope.event diff --git a/app/assets/javascripts/router.coffee.erb b/app/assets/javascripts/router.coffee.erb index 5d7f16cc0..85bc1d820 100644 --- a/app/assets/javascripts/router.coffee.erb +++ b/app/assets/javascripts/router.coffee.erb @@ -522,7 +522,7 @@ angular.module('application.router', ['ui.router']). PriceCategory.query().$promise ] settingsPromise: ['Setting', (Setting)-> - Setting.query(names: "['booking_move_enable', 'booking_move_delay']").$promise + Setting.query(names: "['booking_move_enable', 'booking_move_delay', 'booking_cancel_enable', 'booking_cancel_delay']").$promise ] translations: [ 'Translations', (Translations) -> Translations.query(['app.public.events_show', 'app.shared.member_select', 'app.shared.stripe', diff --git a/app/assets/templates/events/show.html.erb b/app/assets/templates/events/show.html.erb index 6ccf877e7..858c46182 100644 --- a/app/assets/templates/events/show.html.erb +++ b/app/assets/templates/events/show.html.erb @@ -159,6 +159,9 @@
{{ 'change' }}
+
+ {{ 'cancel' }} +
diff --git a/app/models/reservation.rb b/app/models/reservation.rb index 61f0713b6..c2d480430 100644 --- a/app/models/reservation.rb +++ b/app/models/reservation.rb @@ -327,7 +327,11 @@ class Reservation < ActiveRecord::Base end def total_booked_seats - total = nb_reserve_places + total = 0 + unless slots.first.canceled_at + total = nb_reserve_places + end + if tickets.count > 0 total += tickets.map(&:booked).map(&:to_i).reduce(:+) end @@ -356,7 +360,7 @@ class Reservation < ActiveRecord::Base def training_not_fully_reserved slot = self.slots.first - errors.add(:training, "already fully reserved") if Availability.find(slot.availability_id).is_completed + errors.add(:training, 'already fully reserved') if Availability.find(slot.availability_id).is_completed end private diff --git a/config/locales/app.logged.en.yml b/config/locales/app.logged.en.yml index b0195a92f..bfa3c7f2d 100644 --- a/config/locales/app.logged.en.yml +++ b/config/locales/app.logged.en.yml @@ -119,7 +119,7 @@ en: i_reserve: "I reserve" i_shift: "I shift" i_change: "I change" - do_you_really_want_to_cancel_this_reservation: "So you really want to cancel this reservation?" + do_you_really_want_to_cancel_this_reservation: "Do you really want to cancel this reservation?" reservation_was_cancelled_successfully: "Reservation was cancelled successfully." cancellation_failed: "Cancellation failed." a_problem_occured_during_the_payment_process_please_try_again_later: "A problem occurred during the payment process. Please try again later." diff --git a/config/locales/app.public.en.yml b/config/locales/app.public.en.yml index e85c74001..00ae95794 100644 --- a/config/locales/app.public.en.yml +++ b/config/locales/app.public.en.yml @@ -241,6 +241,9 @@ en: book: "Book" change_the_reservation: "Change the reservation" you_can_shift_this_reservation_on_the_following_slots: "You can shift this reservation on the following slots:" + cancel_the_reservation: "Cancel the reservation" + do_you_really_want_to_cancel_this_reservation_this_apply_to_all_booked_tickets: "Do you really want to cancel this reservation? This apply to ALL booked tickets." + cancellation_failed: "Cancellation failed." calendar: calendar: "Calendar" diff --git a/config/locales/app.public.fr.yml b/config/locales/app.public.fr.yml index 0a3d8b083..3c1a479c1 100644 --- a/config/locales/app.public.fr.yml +++ b/config/locales/app.public.fr.yml @@ -242,6 +242,9 @@ fr: book: "Réserver" change_the_reservation: "Modifier la réservation" you_can_shift_this_reservation_on_the_following_slots: "Vous pouvez déplacer cette réservation sur les créneaux suivants :" + cancel_the_reservation: "Annuler la réservation" + do_you_really_want_to_cancel_this_reservation_this_apply_to_all_booked_tickets: "Êtes vous sur de vouloir annuler cette réservation? Ceci s'applique à TOUTES les places réservées." + cancellation_failed: "L'annulation a échoué." calendar: calendar: "Calendrier" From c0acfe0f1ca8006f708c4099e0471b7b99fc8ef7 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 22 Oct 2019 16:46:40 +0200 Subject: [PATCH 002/135] handle ctrl-c in upgrade scripts --- scripts/elastic-upgrade.sh | 10 ++++++++-- scripts/postgre-upgrade.sh | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/scripts/elastic-upgrade.sh b/scripts/elastic-upgrade.sh index 9d856c6c2..9a06fc4e4 100755 --- a/scripts/elastic-upgrade.sh +++ b/scripts/elastic-upgrade.sh @@ -629,13 +629,19 @@ start_upgrade() esac } +function trap_ctrlc() +{ + echo "Ctrl^C, exiting..." + exit 2 +} + upgrade_elastic() { config detect_installation read -rp "Continue with upgrading? (y/n) " confirm Date: Tue, 22 Oct 2019 16:47:22 +0200 Subject: [PATCH 003/135] updated dev version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 506e35655..6941783fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fab-manager", - "version": "4.2.3", + "version": "4.2.4-dev", "description": "FabManager is the FabLab management solution. It provides a comprehensive, web-based, open-source tool to simplify your administrative tasks and your marker's projects.", "keywords": [ "fablab", From 398af2e8e08fad6e6592431f24f1f0cba5804b2c Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 23 Oct 2019 10:20:07 +0200 Subject: [PATCH 004/135] updated moment-timezone --- CHANGELOG.md | 3 +++ yarn.lock | 13 +++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfd8da9f7..0502791a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog Fab Manager +- Handle Ctrl^C in upgrade scripts +- Updated moment-timezone + ## v4.2.3 2019 October 22 - Ability to set the default view in project gallery: openLab or local diff --git a/yarn.lock b/yarn.lock index b1eecf9a4..0869938f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -994,17 +994,22 @@ mkdirp@~0.3.5: integrity sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc= moment-timezone@0.5: - version "0.5.23" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.23.tgz#7cbb00db2c14c71b19303cb47b0fb0a6d8651463" - integrity sha512-WHFH85DkCfiNMDX5D3X7hpNH3/PUhjTGcD0U1SgfBGZxJ3qUmJh5FdvaFjcClxOvB3rzdfj4oRffbI38jEnC1w== + version "0.5.27" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.27.tgz#73adec8139b6fe30452e78f210f27b1f346b8877" + integrity sha512-EIKQs7h5sAsjhPCqN6ggx6cEbs94GK050254TIJySD1bzoM5JTYDwAU1IoVOeTOL6Gm27kYJ51/uuvq1kIlrbw== dependencies: moment ">= 2.9.0" -moment@2.22, "moment@>= 2.9.0", moment@>=2.5.0, "moment@>=2.8.0 <3.0.0": +moment@2.22, moment@>=2.5.0, "moment@>=2.8.0 <3.0.0": version "2.22.2" resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y= +"moment@>= 2.9.0": + version "2.24.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" + integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" From d7aed3fa1f66cc45d316e9ffb75cd4ccfe3b4bff Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 23 Oct 2019 11:43:42 +0200 Subject: [PATCH 005/135] Improved notification email to the member when a rolling subscription is taken --- CHANGELOG.md | 1 + .../notify_member_subscribed_plan.html.erb | 7 ++++++- config/locales/mails.en.yml | 3 ++- config/locales/mails.es.yml | 3 ++- config/locales/mails.fr.yml | 3 ++- config/locales/mails.pt.yml | 3 ++- 6 files changed, 15 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0502791a2..463df1220 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog Fab Manager +- Improved notification email to the member when a rolling subscription is taken - Handle Ctrl^C in upgrade scripts - Updated moment-timezone diff --git a/app/views/notifications_mailer/notify_member_subscribed_plan.html.erb b/app/views/notifications_mailer/notify_member_subscribed_plan.html.erb index 327a0e9f2..808231770 100644 --- a/app/views/notifications_mailer/notify_member_subscribed_plan.html.erb +++ b/app/views/notifications_mailer/notify_member_subscribed_plan.html.erb @@ -2,4 +2,9 @@

<%= t('.body.plan_subscribed_html', PLAN: @attached_object.plan.human_readable_name) %>

-

<%= t('.body.subscription_stops_on', DATE: I18n.l(@attached_object.expired_at.to_date)) %>

+<% if @attached_object.plan.is_rolling && @recipient.trainings.count.zero? %> + <% duration = I18n.t("duration.#{@attached_object.plan.interval}", count: @attached_object.plan.interval_count) %> +

<%= t('.body.rolling_subscription_stops_on', DURATION: duration, DATE: I18n.l(@attached_object.expired_at.to_date)) %>

+<% else %> +

<%= t('.body.subscription_stops_on', DATE: I18n.l(@attached_object.expired_at.to_date)) %>

+<% end %> diff --git a/config/locales/mails.en.yml b/config/locales/mails.en.yml index 9aea143be..e50ef8934 100644 --- a/config/locales/mails.en.yml +++ b/config/locales/mails.en.yml @@ -80,7 +80,8 @@ en: subject: "Your subscription has been successfully purchased" body: plan_subscribed_html: "You have subscribed the plan: %{PLAN}." - subscription_stops_on: "Your subscription plan will end on %{DATE}" + rolling_subscription_stops_on: "Your subscription will end %{DURATION} after your first training. Otherwise, it will stop on %{DATE}." + subscription_stops_on: "Your subscription will end on %{DATE}." notify_member_create_reservation: subject: "Your reservation has been successfully saved" diff --git a/config/locales/mails.es.yml b/config/locales/mails.es.yml index 98c80b22a..0341c8b66 100644 --- a/config/locales/mails.es.yml +++ b/config/locales/mails.es.yml @@ -80,7 +80,8 @@ es: subject: "Su suscripción ha sido correctamente comprada" body: plan_subscribed_html: "Se ha suscrito al plan: %{PLAN}." - subscription_stops_on: "Su suscripción terminará el %{DATE}" + rolling_subscription_stops_on: "Su suscripción terminará %{DURATION} después de su primer entrenamiento. De lo contrario, se detendrá el %{DATE}." + subscription_stops_on: "Su suscripción terminará el %{DATE}." notify_member_create_reservation: subject: "Su reserva se ha registrado correctamente" diff --git a/config/locales/mails.fr.yml b/config/locales/mails.fr.yml index b70eaa751..5f1601c6a 100644 --- a/config/locales/mails.fr.yml +++ b/config/locales/mails.fr.yml @@ -80,7 +80,8 @@ fr: subject: "Votre abonnement a bien été souscrit" body: plan_subscribed_html: "Vous avez souscrit à l'abonnement : %{PLAN}." - subscription_stops_on: "Votre abonnement s'arrêtera automatiquement le %{DATE}" + rolling_subscription_stops_on: "Votre abonnement s'arrêtera automatiquement %{DURATION} après votre première formation. À défaut, il s'arrêtera le %{DATE}." + subscription_stops_on: "Votre abonnement s'arrêtera automatiquement le %{DATE}." notify_member_create_reservation: subject: "Votre réservation a bien été enregistrée" diff --git a/config/locales/mails.pt.yml b/config/locales/mails.pt.yml index 20db2994f..ae2c14662 100755 --- a/config/locales/mails.pt.yml +++ b/config/locales/mails.pt.yml @@ -80,7 +80,8 @@ pt: subject: "Sua assinatura foi adquirida com êxito" body: plan_subscribed_html: "Você assinou o plano: %{PLAN}." - subscription_stops_on: "Seu plano de assinatura termina em %{DATE}" + rolling_subscription_stops_on: "Sua assinatura encerrará %{DURATION} após seu primeiro treinamento. Caso contrário, ele será interrompido em %{DATE}." + subscription_stops_on: "Sua assinatura será encerrada em %{DATE}." notify_member_create_reservation: subject: "Sua reserva foi salva com sucesso" From 35fb991cdbc0592b00d62dea373f6dac32842d9d Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 23 Oct 2019 17:48:31 +0200 Subject: [PATCH 006/135] Improved user experience in defining slots in the calendar management --- CHANGELOG.md | 1 + .../controllers/admin/calendar.js.erb | 86 +++++++++++-------- 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 | 1 + 6 files changed, 53 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 463df1220..81a724711 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog Fab Manager +- Improved user experience in defining slots in the calendar management - Improved notification email to the member when a rolling subscription is taken - Handle Ctrl^C in upgrade scripts - Updated moment-timezone diff --git a/app/assets/javascripts/controllers/admin/calendar.js.erb b/app/assets/javascripts/controllers/admin/calendar.js.erb index 6e35d5a91..ee4620858 100644 --- a/app/assets/javascripts/controllers/admin/calendar.js.erb +++ b/app/assets/javascripts/controllers/admin/calendar.js.erb @@ -275,46 +275,56 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state var calendarSelectCb = function (start, end, jsEvent, view) { start = moment.tz(start.toISOString(), Fablab.timezone); end = moment.tz(end.toISOString(), Fablab.timezone); - // first we check that the selected slot is an N-hours multiple (ie. not decimal) - if (Number.isInteger(parseInt((end.valueOf() - start.valueOf()) / (SLOT_MULTIPLE * 1000), 10) / SLOT_MULTIPLE)) { - const today = new Date(); - if (parseInt((start.valueOf() - today) / (60 * 1000), 10) >= 0) { - // then we open a modal window to let the admin specify the slot type - const modalInstance = $uibModal.open({ - templateUrl: '<%= asset_path "admin/calendar/eventModal.html" %>', - controller: 'CreateEventModalController', - resolve: { - start () { return start; }, - end () { return end; }, - machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }], - trainingsPromise: ['Training', function (Training) { return Training.query().$promise; }], - spacesPromise: ['Space', function (Space) { return Space.query().$promise; }] - } }); - // when the modal is closed, we send the slot to the server for saving - modalInstance.result.then( - function (availability) { - uiCalendarConfig.calendars.calendar.fullCalendar( - 'renderEvent', - { - id: availability.id, - title: availability.title, - start: availability.start_at, - end: availability.end_at, - textColor: 'black', - backgroundColor: availability.backgroundColor, - borderColor: availability.borderColor, - tag_ids: availability.tag_ids, - tags: availability.tags, - machine_ids: availability.machine_ids - }, - true - ); - }, - function () { uiCalendarConfig.calendars.calendar.fullCalendar('unselect'); } - ); - } + + // check if slot is not in the past + const today = new Date(); + if (Math.trunc((start.valueOf() - today) / (60 * 1000)) < 0) { + growl.warning(_t('admin_calendar.event_in_the_past')); + return uiCalendarConfig.calendars.calendar.fullCalendar('unselect'); } + // check that the selected slot is an N-hours multiple (ie. not decimal) + const slots = Math.trunc((end.valueOf() - start.valueOf()) / (60 * 1000)) / SLOT_MULTIPLE; + if (!Number.isInteger(slots)) { + // otherwise, round it to nearest decimal + const nearest = Math.round(slots) * SLOT_MULTIPLE; + end = moment(start).add(nearest, 'minutes'); + } + + // then we open a modal window to let the admin specify the slot type + const modalInstance = $uibModal.open({ + templateUrl: '<%= asset_path "admin/calendar/eventModal.html" %>', + controller: 'CreateEventModalController', + resolve: { + start () { return start; }, + end () { return end; }, + machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }], + trainingsPromise: ['Training', function (Training) { return Training.query().$promise; }], + spacesPromise: ['Space', function (Space) { return Space.query().$promise; }] + } }); + // when the modal is closed, we send the slot to the server for saving + modalInstance.result.then( + function (availability) { + uiCalendarConfig.calendars.calendar.fullCalendar( + 'renderEvent', + { + id: availability.id, + title: availability.title, + start: availability.start_at, + end: availability.end_at, + textColor: 'black', + backgroundColor: availability.backgroundColor, + borderColor: availability.borderColor, + tag_ids: availability.tag_ids, + tags: availability.tags, + machine_ids: availability.machine_ids + }, + true + ); + }, + function () { uiCalendarConfig.calendars.calendar.fullCalendar('unselect'); } + ); + return uiCalendarConfig.calendars.calendar.fullCalendar('unselect'); }; diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index eb9ee6fcd..2074af2a8 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -61,6 +61,7 @@ en: unlockable_because_reservations: "Unable to block booking on this slot because some uncancelled reservations exist on it." delete_slot: "Delete this slot" do_you_really_want_to_delete_this_slot: "Do you really want to delete this slot?" + event_in_the_past: "Unable to create a slot in the past." project_elements: # management of the projects' components diff --git a/config/locales/app.admin.es.yml b/config/locales/app.admin.es.yml index 6e8bcadb2..9c498fe3d 100644 --- a/config/locales/app.admin.es.yml +++ b/config/locales/app.admin.es.yml @@ -61,6 +61,7 @@ es: unlockable_because_reservations: "No se puede bloquear la reserva en esta ranura porque existen algunas reservas no canceladas." delete_slot: "Delete this slot" # translation_missing do_you_really_want_to_delete_this_slot: "Do you really want to delete this slot?" + event_in_the_past: "Unable to create a slot in the past." # translation_missing project_elements: # management of the projects' components diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 3e8d9c517..7bb2a932f 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -61,6 +61,7 @@ fr: unlockable_because_reservations: "Impossible de bloquer les réservations sur ce créneau car il existe des réservations non annulées sur celui-ci." delete_slot: "Supprimer le créneau" do_you_really_want_to_delete_this_slot: "Êtes vous sur de vouloir supprimer ce créneau ?" + event_in_the_past: "Impossible de créer un créneau dans le passé." project_elements: # gestion des éléments constituant les projets diff --git a/config/locales/app.admin.pt.yml b/config/locales/app.admin.pt.yml index 4958bcf95..75f0b58a6 100755 --- a/config/locales/app.admin.pt.yml +++ b/config/locales/app.admin.pt.yml @@ -61,6 +61,7 @@ pt: unlockable_because_reservations: "Não é possível bloquear a reserva neste slot porque existem algumas reservas não cancelados nele." delete_slot: "Exclua o slot" do_you_really_want_to_delete_this_slot: "Você realmente quer excluir esse slot?" + event_in_the_past: "Unable to create a slot in the past." # translation_missing project_elements: # management of the projects' components From f94d8feba6f793c5d3cb5271d37c96b01f086023 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 29 Oct 2019 09:59:21 +0100 Subject: [PATCH 007/135] [feature] admin can delete an user --- CHANGELOG.md | 1 + .../controllers/admin/members.js.erb | 43 ++++++++++++++++--- .../templates/admin/members/members.html.erb | 5 ++- app/controllers/api/members_controller.rb | 2 +- app/policies/user_policy.rb | 6 +-- config/locales/app.admin.en.yml | 3 ++ config/locales/app.admin.es.yml | 3 ++ config/locales/app.admin.fr.yml | 3 ++ config/locales/app.admin.pt.yml | 3 ++ 9 files changed, 57 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81a724711..0d40ef7fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog Fab Manager +- An administrator can delete a member - Improved user experience in defining slots in the calendar management - Improved notification email to the member when a rolling subscription is taken - Handle Ctrl^C in upgrade scripts diff --git a/app/assets/javascripts/controllers/admin/members.js.erb b/app/assets/javascripts/controllers/admin/members.js.erb index 023f53dfe..2e95fd0c1 100644 --- a/app/assets/javascripts/controllers/admin/members.js.erb +++ b/app/assets/javascripts/controllers/admin/members.js.erb @@ -182,6 +182,35 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce', } }; + /** + * Ask for confirmation then delete the specified user + * @param memberId {number} identifier of the user to delete + */ + $scope.deleteMember = function(memberId) { + dialogs.confirm( + { + resolve: { + object () { + return { + title: _t('confirmation_required'), + msg: $sce.trustAsHtml(_t('confirm_delete_member') + '

' + _t('this_may_take_a_while_please_wait')) + }; + } + } + }, + function () { // cancel confirmed + Member.delete( + { id: memberId }, + function () { + $scope.members.splice(findItemIdxById($scope.members, memberId), 1); + return growl.success(_t('member_successfully_deleted')); + }, + function (error) { growl.error(_t('unable_to_delete_the_member')); } + ); + } + ); + } + /** * Ask for confirmation then delete the specified administrator * @param admins {Array} full list of administrators @@ -203,7 +232,7 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce', Admin.delete( { id: admin.id }, function () { - admins.splice(findAdminIdxById(admins, admin.id), 1); + admins.splice(findItemIdxById(admins, admin.id), 1); return growl.success(_t('administrator_successfully_deleted')); }, function (error) { growl.error(_t('unable_to_delete_the_administrator')); } @@ -261,13 +290,13 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce', var searchTimeout = null; /** - * Iterate through the provided array and return the index of the requested admin - * @param admins {Array} full list of users with role 'admin' - * @param id {Number} user id of the admin to retrieve in the list - * @returns {Number} index of the requested admin, in the provided array + * Iterate through the provided array and return the index of the requested item + * @param items {Array} full list of users with role 'admin' + * @param id {Number} id of the item to retrieve in the list + * @returns {Number} index of the requested item, in the provided array */ - var findAdminIdxById = function (admins, id) { - return (admins.map(function (admin) { return admin.id; })).indexOf(id); + var findItemIdxById = function (items, id) { + return (items.map(function (item) { return item.id; })).indexOf(id); }; /** diff --git a/app/assets/templates/admin/members/members.html.erb b/app/assets/templates/admin/members/members.html.erb index 8da4bf427..150b4d5e6 100644 --- a/app/assets/templates/admin/members/members.html.erb +++ b/app/assets/templates/admin/members/members.html.erb @@ -45,7 +45,10 @@
+ {{ 'incomplete_profile' }}
diff --git a/app/controllers/api/members_controller.rb b/app/controllers/api/members_controller.rb index 039280418..08cdce20b 100644 --- a/app/controllers/api/members_controller.rb +++ b/app/controllers/api/members_controller.rb @@ -66,7 +66,7 @@ class API::MembersController < API::ApiController def destroy authorize @member @member.destroy - sign_out(@member) + sign_out(@member) if @member.id == current_user.id head :no_content end diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb index a794ee3dd..18b3c58cf 100644 --- a/app/policies/user_policy.rb +++ b/app/policies/user_policy.rb @@ -16,15 +16,15 @@ class UserPolicy < ApplicationPolicy end def show? - user.admin? or (record.is_allow_contact and record.member?) or (user.id == record.id) + user.admin? || (record.is_allow_contact && record.member?) || (user.id == record.id) end def update? - user.admin? or (user.id == record.id) + user.admin? || (user.id == record.id) end def destroy? - user.id == record.id + user.admin? || (user.id == record.id) end def merge? diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index 2074af2a8..b37d2a823 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -521,6 +521,9 @@ en: add_a_new_administrator: "Add a new administrator" groups: "Groups" authentication: "Authentication" + confirm_delete_member: "Do you really want to delete this member? This cannot be undone." + member_successfully_deleted: "Member successfully deleted." + unable_to_delete_the_member: "Unable to delete the member." do_you_really_want_to_delete_this_administrator_this_cannot_be_undone: "Do you really want to delete this administrator? This cannot be undone." this_may_take_a_while_please_wait: "Warning: this may take a while, please be patient." administrator_successfully_deleted: "Administrator successfully deleted." diff --git a/config/locales/app.admin.es.yml b/config/locales/app.admin.es.yml index 9c498fe3d..bd7b2c14d 100644 --- a/config/locales/app.admin.es.yml +++ b/config/locales/app.admin.es.yml @@ -521,6 +521,9 @@ es: add_a_new_administrator: "Agregar un nuevo administrador" groups: "Grupos" authentication: "Autenticación" + confirm_delete_member: "¿Desea realmente eliminar este usario? Esto no se puede deshacer." + member_successfully_deleted: "Usario eliminado correctamente." + unable_to_delete_the_member: "No se puede eliminar el usario." do_you_really_want_to_delete_this_administrator_this_cannot_be_undone: "¿Desea realmente eliminar este administrador? Esto no se puede deshacer." this_may_take_a_while_please_wait: "Advertencia: esto puede tomar un tiempo, por favor, tenga paciencia." administrator_successfully_deleted: "Administrador eliminado correctamente." diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 7bb2a932f..c94baefa1 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -522,6 +522,9 @@ fr: add_a_new_administrator: "Ajouter un nouvel administrateur" groups: "Groupes" authentication: "Authentification" + confirm_delete_member: "Êtes-vous sûr de vouloir supprimer ce membre ? Cette opération est irréversible." + member_successfully_deleted: "Le membre a bien été supprimé." + unable_to_delete_the_member: "Le membre n'a pas pu être supprimé." do_you_really_want_to_delete_this_administrator_this_cannot_be_undone: "Êtes-vous sûr de vouloir supprimer cet administrateur ? Cette opération est irréversible." this_may_take_a_while_please_wait: "Attention : ceci peut prendre un certain temps, merci de patienter." administrator_successfully_deleted: "L'administrateur a bien été supprimé." diff --git a/config/locales/app.admin.pt.yml b/config/locales/app.admin.pt.yml index 75f0b58a6..ad46a610d 100755 --- a/config/locales/app.admin.pt.yml +++ b/config/locales/app.admin.pt.yml @@ -521,6 +521,9 @@ pt: add_a_new_administrator: "Adicionar novo administrador" groups: "Grupos" authentication: "Autenticação" + confirm_delete_member: "Você realmente deseja excluir este usuário? Isso não pode ser revertido." + member_successfully_deleted: "Usuário excluído com sucesso." + unable_to_delete_the_member: "Impossível excluir membro." do_you_really_want_to_delete_this_administrator_this_cannot_be_undone: "Você realmente deseja excluir este administrador? Isso não pode ser revertido." this_may_take_a_while_please_wait: "Atenção: Isso pode demorar um pouco, por favor, seja paciente." administrator_successfully_deleted: "Administrator excluído com sucesso." From 1ed3eba1295d7bfa7ba20b8966787c2cc66bd45a Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 29 Oct 2019 11:09:17 +0100 Subject: [PATCH 008/135] [feature] export last connection date to members.xlsx --- README.md | 2 ++ app/controllers/api/members_controller.rb | 10 +++++++++- app/views/exports/users_members.xlsx.axlsx | 6 ++++-- config/locales/en.yml | 1 + config/locales/es.yml | 1 + config/locales/fr.yml | 1 + config/locales/pt.yml | 1 + 7 files changed, 19 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d3fbc31cc..458c69baf 100644 --- a/README.md +++ b/README.md @@ -435,6 +435,8 @@ Developers may find information on how to implement their own authentication pro - In some cases, the invoices won't be generated. This can be due to the image included in the invoice header not being supported. To fix this issue, change the image in the administrator interface (manage the invoices / invoices settings). See [this thread](https://forum.fab-manager.com/t/resolu-erreur-generation-facture/428) for more info. + +- In the excel exports, if the cells expected to contain dates are showing strange numbers, check that you have correctly configured the [EXCEL_DATE_FORMAT](doc/environment.md#EXCEL_DATE_FORMAT) variable. ## Related Documentation diff --git a/app/controllers/api/members_controller.rb b/app/controllers/api/members_controller.rb index 08cdce20b..4949158fd 100644 --- a/app/controllers/api/members_controller.rb +++ b/app/controllers/api/members_controller.rb @@ -115,8 +115,16 @@ class API::MembersController < API::ApiController def export_members authorize :export + last_update = [ + User.with_role(:member).maximum('updated_at'), + Profile.where(user_id: User.with_role(:member)).maximum('updated_at'), + InvoicingProfile.where(user_id: User.with_role(:member)).maximum('updated_at'), + StatisticProfile.where(user_id: User.with_role(:member)).maximum('updated_at'), + Subscription.maximum('updated_at') + ].max + export = Export.where(category: 'users', export_type: 'members') - .where('created_at > ?', User.with_role(:member).maximum('updated_at')) + .where('created_at > ?', last_update) .last if export.nil? || !FileTest.exist?(export.file) @export = Export.new(category: 'users', export_type: 'members', user: current_user) diff --git a/app/views/exports/users_members.xlsx.axlsx b/app/views/exports/users_members.xlsx.axlsx index 2c4b2588e..bd170e5ea 100644 --- a/app/views/exports/users_members.xlsx.axlsx +++ b/app/views/exports/users_members.xlsx.axlsx @@ -14,6 +14,7 @@ wb.add_worksheet(name: t('export_members.members')) do |sheet| t('export_members.first_name'), t('export_members.email'), t('export_members.newsletter'), + t('export_members.last_login'), t('export_members.gender'), t('export_members.age'), t('export_members.address'), @@ -45,6 +46,7 @@ wb.add_worksheet(name: t('export_members.members')) do |sheet| member.profile.first_name, member.email, member.is_allow_newsletter, + member.last_sign_in_at&.to_date, member.statistic_profile.gender ? t('export_members.man') : t('export_members.woman'), member.statistic_profile.age, member.invoicing_profile&.address&.address || '', @@ -66,10 +68,10 @@ wb.add_worksheet(name: t('export_members.members')) do |sheet| member.invoicing_profile&.organization&.name || '', member.invoicing_profile&.organization&.address&.address || '' ] - styles = [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + styles = [nil, nil, nil, nil, nil, date, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, date, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil] - types = %i[integer string string string boolean string integer string string string string string + types = %i[integer string string string boolean date string integer string string string string string string string string date string string integer string string string string string string string] sheet.add_row data, style: styles, types: types diff --git a/config/locales/en.yml b/config/locales/en.yml index 022a2625e..1bf554024 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -167,6 +167,7 @@ en: first_name: "First name" email: "E-mail" newsletter: "Newsletter" + last_login: "Last login" gender: "Gender" age: "Age" address: "Address" diff --git a/config/locales/es.yml b/config/locales/es.yml index 6e6d2ae60..4dc9dcc30 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -167,6 +167,7 @@ es: first_name: "Nombre" email: "E-mail" newsletter: "Hoja informativa" + last_login: "Último acceso" gender: "Genero" age: "Edad" address: "Dirección" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 533802553..120a624d7 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -167,6 +167,7 @@ fr: first_name: "Prénom" email: "Courriel" newsletter: "Lettre d'informations" + last_login: "Dernière connexion" gender: "Genre" age: "Âge" address: "Adresse" diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 155a3aaa6..813e004ff 100755 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -167,6 +167,7 @@ pt: first_name: "Primeiro nome" email: "E-mail" newsletter: "Newsletter" + last_login: "Último login" gender: "Gênero" age: "Idade" address: "Endereço" From 12b9155b43dc182e0ba51c03705b4bd0b51be340 Mon Sep 17 00:00:00 2001 From: Nicolas Florentin Date: Tue, 29 Oct 2019 15:20:25 +0100 Subject: [PATCH 009/135] try to fix edge cases of VatHistoryService, related to issue #156 --- app/services/vat_history_service.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/services/vat_history_service.rb b/app/services/vat_history_service.rb index 320f62d69..ef8cf60d4 100644 --- a/app/services/vat_history_service.rb +++ b/app/services/vat_history_service.rb @@ -32,9 +32,10 @@ class VatHistoryService chronology.push(start: v.created_at, end: end_date, enabled: v.value == 'true') end_date = v.created_at end + chronology.push(start: DateTime.new(0), end: end_date, enabled: false) date_rates = [] Setting.find_by(name: 'invoice_VAT-rate').history_values.order(created_at: 'ASC').each do |rate| - range = chronology.select { |p| rate.created_at.between?(p[:start], p[:end]) }.first + range = chronology.select { |p| rate.created_at.to_i.between?(p[:start].to_i, p[:end].to_i) }.first date = range[:enabled] ? rate.created_at : range[:end] date_rates.push(date: date, rate: rate.value.to_i) end From cdc30e0da229aac2aac4be29c9ea60d3cc2f648c Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 29 Oct 2019 16:52:52 +0100 Subject: [PATCH 010/135] configure the slots duration with an environment variable --- app/assets/javascripts/controllers/admin/calendar.js.erb | 2 +- app/helpers/application_helper.rb | 2 +- app/views/application/index.html.erb | 1 + config/application.yml.default | 2 ++ config/secrets.yml | 4 ++++ doc/environment.md | 6 ++++++ docker/env.example | 2 ++ 7 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/controllers/admin/calendar.js.erb b/app/assets/javascripts/controllers/admin/calendar.js.erb index ee4620858..dba9d8ef1 100644 --- a/app/assets/javascripts/controllers/admin/calendar.js.erb +++ b/app/assets/javascripts/controllers/admin/calendar.js.erb @@ -30,7 +30,7 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state const BOOKING_SNAP = '00:30:00'; // We do not allow the creation of slots that are not a multiple of 60 minutes - const SLOT_MULTIPLE = 60; + const SLOT_MULTIPLE = Fablab.slotDuration; /* PUBLIC SCOPE */ diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index ea6e2ab0b..88473e6e6 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -7,7 +7,7 @@ module ApplicationHelper require 'message_format' ## machine/spaces availabilities are divided in multiple slots of 60 minutes - SLOT_DURATION ||= 60 + SLOT_DURATION ||= Rails.application.secrets.slot_duration || 60 ## # Verify if the provided attribute is in the provided attributes array, whatever it exists or not diff --git a/app/views/application/index.html.erb b/app/views/application/index.html.erb index 74a5210a0..123ad711b 100644 --- a/app/views/application/index.html.erb +++ b/app/views/application/index.html.erb @@ -20,6 +20,7 @@ Fablab.withoutSpaces = ('<%= Rails.application.secrets.fablab_without_spaces %>' !== 'false'); Fablab.withoutOnlinePayment = ('<%= Rails.application.secrets.fablab_without_online_payments %>' === 'true'); Fablab.withoutInvoices = ('<%= Rails.application.secrets.fablab_without_invoices %>' === 'true'); + Fablab.slotDuration = parseInt("<%= ApplicationHelper::SLOT_DURATION %>", 10); Fablab.disqusShortname = "<%= Rails.application.secrets.disqus_shortname %>"; Fablab.defaultHost = "<%= Rails.application.secrets.default_host %>"; Fablab.gaId = "<%= Rails.application.secrets.google_analytics_id %>"; diff --git a/config/application.yml.default b/config/application.yml.default index 96bbc8238..e8775cb29 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -21,6 +21,8 @@ FABLAB_WITHOUT_SPACES: 'true' FABLAB_WITHOUT_ONLINE_PAYMENT: 'false' FABLAB_WITHOUT_INVOICES: 'false' +SLOT_DURATION: '60' + DEFAULT_MAIL_FROM: Fab Manager Demo # Configure carefully! diff --git a/config/secrets.yml b/config/secrets.yml index d647b149f..87e05bab2 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -20,6 +20,7 @@ development: fablab_without_spaces: <%= ENV["FABLAB_WITHOUT_SPACES"] %> fablab_without_online_payments: <%= ENV["FABLAB_WITHOUT_ONLINE_PAYMENT"] %> fablab_without_invoices: <%= ENV["FABLAB_WITHOUT_INVOICES"] %> + slot_duration: <%= ENV["SLOT_DURATION"] %> default_host: <%= ENV["DEFAULT_HOST"] %> default_protocol: <%= ENV["DEFAULT_PROTOCOL"] %> time_zone: <%= ENV["TIME_ZONE"] %> @@ -62,6 +63,7 @@ test: fablab_without_spaces: false fablab_without_online_payments: false fablab_without_invoices: false + slot_duration: <%= ENV["SLOT_DURATION"] %> default_host: <%= ENV["DEFAULT_HOST"] %> default_protocol: <%= ENV["DEFAULT_PROTOCOL"] %> time_zone: Paris @@ -104,6 +106,7 @@ staging: fablab_without_spaces: <%= ENV["FABLAB_WITHOUT_SPACES"] %> fablab_without_online_payments: <%= ENV["FABLAB_WITHOUT_ONLINE_PAYMENT"] %> fablab_without_invoices: <%= ENV["FABLAB_WITHOUT_INVOICES"] %> + slot_duration: <%= ENV["SLOT_DURATION"] %> default_host: <%= ENV["DEFAULT_HOST"] %> default_protocol: <%= ENV["DEFAULT_PROTOCOL"] %> delivery_method: <%= ENV['DELIVERY_METHOD'] %> @@ -157,6 +160,7 @@ production: fablab_without_spaces: <%= ENV["FABLAB_WITHOUT_SPACES"] %> fablab_without_online_payments: <%= ENV["FABLAB_WITHOUT_ONLINE_PAYMENT"] %> fablab_without_invoices: <%= ENV["FABLAB_WITHOUT_INVOICES"] %> + slot_duration: <%= ENV["SLOT_DURATION"] %> default_host: <%= ENV["DEFAULT_HOST"] %> default_protocol: <%= ENV["DEFAULT_PROTOCOL"] %> delivery_method: <%= ENV['DELIVERY_METHOD'] %> diff --git a/doc/environment.md b/doc/environment.md index ccfd6a922..594c9cf17 100644 --- a/doc/environment.md +++ b/doc/environment.md @@ -102,6 +102,12 @@ Valid stripe API keys are still required, even if you don't require online payme If set to 'true', the invoices will be disabled. This is useful if you have your own invoicing system and you want to prevent Fab-manager from generating and sending invoices to members. **Very important**: if you disable invoices, you still have to configure VAT in the interface to prevent errors in accounting and prices. + + + SLOT_DURATION + +Machine and space availabilities are divided in multiple slots of the duration set by this variable. +Default value is 60 minutes (1 hour). DEFAULT_MAIL_FROM diff --git a/docker/env.example b/docker/env.example index 8f8333811..5460ef28c 100644 --- a/docker/env.example +++ b/docker/env.example @@ -14,6 +14,8 @@ FABLAB_WITHOUT_SPACES=true FABLAB_WITHOUT_ONLINE_PAYMENT=true FABLAB_WITHOUT_INVOICES=false +SLOT_DURATION=60 + DEFAULT_MAIL_FROM=Fab Manager Demo DEFAULT_HOST=demo.fab-manager.com DEFAULT_PROTOCOL=http From 96a2f6e426d84d0255acd6450ea9dd92af53ece6 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 30 Oct 2019 15:09:44 +0100 Subject: [PATCH 011/135] create availablities with slots duration ne 60 --- .../controllers/admin/calendar.js.erb | 24 +++++++++++-------- doc/environment.md | 2 ++ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/controllers/admin/calendar.js.erb b/app/assets/javascripts/controllers/admin/calendar.js.erb index dba9d8ef1..be492e2a0 100644 --- a/app/assets/javascripts/controllers/admin/calendar.js.erb +++ b/app/assets/javascripts/controllers/admin/calendar.js.erb @@ -283,12 +283,12 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state return uiCalendarConfig.calendars.calendar.fullCalendar('unselect'); } - // check that the selected slot is an N-hours multiple (ie. not decimal) + // check that the selected slot is an multiple of SLOT_MULTIPLE (ie. not decimal) const slots = Math.trunc((end.valueOf() - start.valueOf()) / (60 * 1000)) / SLOT_MULTIPLE; if (!Number.isInteger(slots)) { - // otherwise, round it to nearest decimal - const nearest = Math.round(slots) * SLOT_MULTIPLE; - end = moment(start).add(nearest, 'minutes'); + // otherwise, round it to upper decimal + const upper = Math.ceil(slots) * SLOT_MULTIPLE; + end = moment(start).add(upper, 'minutes'); } // then we open a modal window to let the admin specify the slot type @@ -507,21 +507,25 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui Tag.query().$promise.then(function (data) { $scope.tags = data; }); - // When we configure a machine availability, do not let the user change the end time, as the total - // time must be dividable by 60 minutes (base slot duration). For training availabilities, the user + // When we configure a machine/space availability, do not let the user change the end time, as the total + // time must be dividable by Fablab.slotDuration minutes (base slot duration). For training availabilities, the user // can configure any duration as it does not matters. $scope.$watch('availability.available_type', function (newValue, oldValue, scope) { if ((newValue === 'machines') || (newValue === 'space')) { $scope.endDateReadOnly = true; - const diff = moment($scope.end).diff($scope.start, 'hours'); // the result is rounded down by moment.js - $scope.end = moment($scope.start).add(diff, 'hours').toDate(); + const slots = Math.trunc(($scope.end.valueOf() - $scope.start.valueOf()) / (60 * 1000)) / Fablab.slotDuration; + if (!Number.isInteger(slots)) { + // otherwise, round it to upper decimal + const upper = Math.ceil(slots) * Fablab.slotDuration; + $scope.end = moment($scope.start).add(upper, 'minutes').toDate(); + } return $scope.availability.end_at = $scope.end; } else { return $scope.endDateReadOnly = false; } }); - // When the start date is changed, if we are configuring a machine availability, + // When the start date is changed, if we are configuring a machine/space availability, // maintain the relative length of the slot (ie. change the end time accordingly) $scope.$watch('start', function (newValue, oldValue, scope) { // for machine or space availabilities, adjust the end time @@ -542,7 +546,7 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui // Maintain consistency between the end time and the date object in the availability object return $scope.$watch('end', function (newValue, oldValue, scope) { // we prevent the admin from setting the end of the availability before its begining - if (moment($scope.start).add(1, 'hour').isAfter(newValue)) { + if (moment($scope.start).add(Fablab.slotDuration, 'minutes').isAfter(newValue)) { $scope.end = oldValue; } // update availability object diff --git a/doc/environment.md b/doc/environment.md index 594c9cf17..4fcfc5ada 100644 --- a/doc/environment.md +++ b/doc/environment.md @@ -108,6 +108,8 @@ This is useful if you have your own invoicing system and you want to prevent Fab Machine and space availabilities are divided in multiple slots of the duration set by this variable. Default value is 60 minutes (1 hour). + +⚠ You should not change this value during the application lifecycle! DEFAULT_MAIL_FROM From 8f04ea436ec2c4e220ef87fee52ed971622e92ee Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 30 Oct 2019 15:39:30 +0100 Subject: [PATCH 012/135] updated changelog --- CHANGELOG.md | 2 ++ doc/environment.md | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d40ef7fd..568c51c54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,12 @@ # Changelog Fab Manager - An administrator can delete a member +- Ability to configure the duration of a reservation slot. Previously, only 60 minutes slots were allowed - Improved user experience in defining slots in the calendar management - Improved notification email to the member when a rolling subscription is taken - Handle Ctrl^C in upgrade scripts - Updated moment-timezone +- [TODO DEPLOY] add the `SLOT_DURATION` environment variable (see [doc/environment.md](doc/environment.md#SLOT_DURATION) for configuration details) ## v4.2.3 2019 October 22 diff --git a/doc/environment.md b/doc/environment.md index 4fcfc5ada..5ba50d6ec 100644 --- a/doc/environment.md +++ b/doc/environment.md @@ -109,7 +109,8 @@ This is useful if you have your own invoicing system and you want to prevent Fab Machine and space availabilities are divided in multiple slots of the duration set by this variable. Default value is 60 minutes (1 hour). -⚠ You should not change this value during the application lifecycle! +⚠ Changing this value during the application life may cause serious issues. +Please ensure there's no machine/space availabilities opened to reservation or already reserved **in the future** when you change this value. DEFAULT_MAIL_FROM From a755f34372831c4423ba28c1f2c936e9023573f4 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 30 Oct 2019 15:44:00 +0100 Subject: [PATCH 013/135] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 568c51c54..72023a739 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Improved notification email to the member when a rolling subscription is taken - Handle Ctrl^C in upgrade scripts - Updated moment-timezone +- Fix a bug: in some cases, the invoices were not generated after deploying v4.2.0+. This can occurs if VAT was changed/enabled during the application life (#156) - [TODO DEPLOY] add the `SLOT_DURATION` environment variable (see [doc/environment.md](doc/environment.md#SLOT_DURATION) for configuration details) ## v4.2.3 2019 October 22 From a2597a982ed86fcd52824afd5c8ec1e232a05f1f Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 30 Oct 2019 15:45:54 +0100 Subject: [PATCH 014/135] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72023a739..8b634ddca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Updated moment-timezone - Fix a bug: in some cases, the invoices were not generated after deploying v4.2.0+. This can occurs if VAT was changed/enabled during the application life (#156) - [TODO DEPLOY] add the `SLOT_DURATION` environment variable (see [doc/environment.md](doc/environment.md#SLOT_DURATION) for configuration details) +- [TODO DEPLOY] `rake fablab:maintenance:regenerate_invoices[2019,10]` only if you had download issues with your last invoices ## v4.2.3 2019 October 22 From 279e5f692b21a2b3db9b14945cd29350bae14542 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Nov 2019 18:00:54 +0000 Subject: [PATCH 015/135] Bump loofah from 2.3.0 to 2.3.1 Bumps [loofah](https://github.com/flavorjones/loofah) from 2.3.0 to 2.3.1. - [Release notes](https://github.com/flavorjones/loofah/releases) - [Changelog](https://github.com/flavorjones/loofah/blob/master/CHANGELOG.md) - [Commits](https://github.com/flavorjones/loofah/compare/v2.3.0...v2.3.1) Signed-off-by: dependabot[bot] --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 0c1c06a6b..eec86b501 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -153,7 +153,7 @@ GEM execjs (2.7.0) faker (1.4.3) i18n (~> 0.5) - faraday (0.17) + faraday (0.17.0) multipart-post (>= 1.2, < 3) ffi (1.9.24) figaro (1.1.0) @@ -208,7 +208,7 @@ GEM actionpack (>= 3.0.0) activesupport (>= 3.0.0) libv8 (3.16.14.19) - loofah (2.3.0) + loofah (2.3.1) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) @@ -240,7 +240,7 @@ GEM multi_xml (0.6.0) multipart-post (2.1.1) naught (1.1.0) - nokogiri (1.10.4) + nokogiri (1.10.5) mini_portile2 (~> 2.4.0) notify_with (0.0.2) jbuilder (~> 2.0) From df26e4de4a89c435a36a1781708987efc156b80f Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 7 Nov 2019 19:38:29 +0100 Subject: [PATCH 016/135] updated changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c908ce7ed..79ce9b6f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,9 @@ - Improved notification email to the member when a rolling subscription is taken - Handle Ctrl^C in upgrade scripts - Updated moment-timezone +- Fix a security issue: fixed [CVE-2019-15587](https://github.com/advisories/GHSA-c3gv-9cxf-6f57) - [TODO DEPLOY] add the `SLOT_DURATION` environment variable (see [doc/environment.md](doc/environment.md#SLOT_DURATION) for configuration details) +- [TODO DEPLOY] -> (only dev) `bundle install` ## v4.2.4 2019 October 30 From 2cccbd3486d2dfbf6738d87ec91d616cbe0501d0 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 12 Nov 2019 14:25:28 +0100 Subject: [PATCH 017/135] recurrence UI --- .../controllers/admin/calendar.js.erb | 38 +++++++++- .../admin/calendar/eventModal.html.erb | 69 ++++++++++++++++++- config/locales/app.admin.en.yml | 11 +++ config/locales/app.admin.es.yml | 11 +++ config/locales/app.admin.fr.yml | 11 +++ config/locales/app.admin.pt.yml | 11 +++ 6 files changed, 146 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/controllers/admin/calendar.js.erb b/app/assets/javascripts/controllers/admin/calendar.js.erb index be492e2a0..538e4e755 100644 --- a/app/assets/javascripts/controllers/admin/calendar.js.erb +++ b/app/assets/javascripts/controllers/admin/calendar.js.erb @@ -468,6 +468,8 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui */ $scope.next = function () { if ($scope.step === 1) { $scope.setNbTotalPlaces(); } + if ($scope.step === 2) { return validateSelection(); } + if ($scope.step === 4) { return validateRecurrence(); } return $scope.step++; }; @@ -534,8 +536,8 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui end.add(moment(newValue).diff(oldValue), 'milliseconds'); $scope.end = end.toDate(); } else { // for training availabilities - // prevent the admin from setting the begining after the and - if (moment(newValue).add(1, 'hour').isAfter($scope.end)) { + // prevent the admin from setting the beginning after the end + if (moment(newValue).add(Fablab.slotDuration, 'minutes').isAfter($scope.end)) { $scope.start = oldValue; } } @@ -545,7 +547,7 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui // Maintain consistency between the end time and the date object in the availability object return $scope.$watch('end', function (newValue, oldValue, scope) { - // we prevent the admin from setting the end of the availability before its begining + // we prevent the admin from setting the end of the availability before its beginning if (moment($scope.start).add(Fablab.slotDuration, 'minutes').isAfter(newValue)) { $scope.end = oldValue; } @@ -554,6 +556,36 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui }); }; + /** + * Validates that a machine or more was/were selected before continuing to step 3 (adjust time + tags) + */ + const validateSelection = function () { + if ($scope.availability.available_type === 'machines') { + if ($scope.selectedMachines.length === 0) { + return growl.error(_t('admin_calendar.you_should_select_at_least_a_machine')); + } + } + $scope.step++; + }; + + /** + * Validates that the recurrence parameters were correctly set before continuing to step 5 (summary) + */ + const validateRecurrence = function () { + if ($scope.availability.is_recurrent) { + if (!$scope.availability.period) { + return growl.error(_t('admin_calendar.select_period')); + } + if (!$scope.availability.nb_periods) { + return growl.error(_t('admin_calendar.select_nb_period')); + } + if (!$scope.availability.end_date) { + return growl.error(_t('admin_calendar.select_end_date')); + } + } + $scope.step++; + }; + // !!! MUST BE CALLED AT THE END of the controller return initialize(); } diff --git a/app/assets/templates/admin/calendar/eventModal.html.erb b/app/assets/templates/admin/calendar/eventModal.html.erb index 938b91668..03477cb5c 100644 --- a/app/assets/templates/admin/calendar/eventModal.html.erb +++ b/app/assets/templates/admin/calendar/eventModal.html.erb @@ -93,12 +93,77 @@ - diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index d298da152..d36dec8d0 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -55,7 +55,7 @@ en: select_period: "Please select a period for the recurrence" select_nb_period: "Please select a number of periods for the recurrence" select_end_date: "Please select the date of the last occurrence" - about_to_create: "You are about to create the following {TYPE, select, machines{machine} training{training} space{space} other{other}} {NUMBER, plural, one{slot} other{slots}}, {NAME}:" # messageFormat interpolation + about_to_create: "You are about to create the following {TYPE, select, machines{machine} training{training} space{space} other{other}} {NUMBER, plural, one{slot} other{slots}}:" # messageFormat interpolation reservable: "Reservable(s):" labels: "Label(s):" none: "None" diff --git a/config/locales/app.admin.es.yml b/config/locales/app.admin.es.yml index 024c73127..036104dc3 100644 --- a/config/locales/app.admin.es.yml +++ b/config/locales/app.admin.es.yml @@ -55,7 +55,7 @@ es: select_period: "Please select a period for the recurrence" # translation_missing select_nb_period: "Please select a number of periods for the recurrence" # translation_missing select_end_date: "Please select the date of the last occurrence" # translation_missing - about_to_create: "You are about to create the following {TYPE, select, machines{machine} training{training} space{space} other{other}} {NUMBER, plural, one{slot} other{slots}}, {NAME}:" # messageFormat interpolation # translation_missing + about_to_create: "You are about to create the following {TYPE, select, machines{machine} training{training} space{space} other{other}} {NUMBER, plural, one{slot} other{slots}}:" # messageFormat interpolation # translation_missing reservable: "Reservable(s):" # translation_missing labels: "Etiqueta(s):" none: "Ninguna" diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 2015b063c..b7684d326 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -55,7 +55,7 @@ fr: select_period: "Veuillez choisir une période pour la récurrence" select_nb_period: "Veuillez choisir un nombre de périodes pour la récurrence" select_end_date: "Veuillez choisir la date de dernière occurrence" - about_to_create: "Vous vous apprêtez à créer {NUMBER, plural, one{le créneau} other{les créneaux}} {TYPE, select, machines{machine} training{formation} space{espace} other{autre}} {NAME} suivant :" # messageFormat interpolation + about_to_create: "Vous vous apprêtez à créer {NUMBER, plural, one{le créneau} other{les créneaux}} {TYPE, select, machines{machine} training{formation} space{espace} other{autre}} suivant :" # messageFormat interpolation reservable: "Réservable(s) :" labels: "Étiquette(s) :" none: "Aucune" diff --git a/config/locales/app.admin.pt.yml b/config/locales/app.admin.pt.yml index a3e330dee..e5c061bb5 100755 --- a/config/locales/app.admin.pt.yml +++ b/config/locales/app.admin.pt.yml @@ -55,7 +55,7 @@ pt: select_period: "Please select a period for the recurrence" # translation_missing select_nb_period: "Please select a number of periods for the recurrence" # translation_missing select_end_date: "Please select the date of the last occurrence" # translation_missing - about_to_create: "You are about to create the following {TYPE, select, machines{machine} training{training} space{space} other{other}} {NUMBER, plural, one{slot} other{slots}}, {NAME}:" # messageFormat interpolation # translation_missing + about_to_create: "You are about to create the following {TYPE, select, machines{machine} training{training} space{space} other{other}} {NUMBER, plural, one{slot} other{slots}}:" # messageFormat interpolation # translation_missing reservable: "Reservable(s) :" # translation_missing labels: "Etiqueta(s):" none: "Nenhuma" From a60a39ff9eb4ab37767457cf1d9112c6d57f4c6d Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 13 Nov 2019 12:13:22 +0100 Subject: [PATCH 020/135] [ongoing] create multiple availabilities according to UI --- CHANGELOG.md | 1 + .../controllers/admin/calendar.js.erb | 8 ++++-- .../api/availabilities_controller.rb | 3 +++ app/models/availability.rb | 8 +++--- .../create_availabilities_service.rb | 25 +++++++++++++++++++ config/locales/en.yml | 2 +- config/locales/es.yml | 2 +- config/locales/fr.yml | 2 +- config/locales/pt.yml | 2 +- ...103352_add_recurrence_to_availabilities.rb | 13 ++++++++++ db/schema.rb | 9 +++++-- 11 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 app/services/availabilities/create_availabilities_service.rb create mode 100644 db/migrate/20191113103352_add_recurrence_to_availabilities.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 79ce9b6f5..562f6f662 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Fix a security issue: fixed [CVE-2019-15587](https://github.com/advisories/GHSA-c3gv-9cxf-6f57) - [TODO DEPLOY] add the `SLOT_DURATION` environment variable (see [doc/environment.md](doc/environment.md#SLOT_DURATION) for configuration details) - [TODO DEPLOY] -> (only dev) `bundle install` +- [TODO DEPLOY] `rake db:migrate` ## v4.2.4 2019 October 30 diff --git a/app/assets/javascripts/controllers/admin/calendar.js.erb b/app/assets/javascripts/controllers/admin/calendar.js.erb index 0f53a498f..d10a0550d 100644 --- a/app/assets/javascripts/controllers/admin/calendar.js.erb +++ b/app/assets/javascripts/controllers/admin/calendar.js.erb @@ -476,9 +476,13 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui } else if ($scope.availability.available_type === 'space') { $scope.availability.space_ids = [$scope.selectedSpace.id]; } + if ($scope.availability.is_recurrent) { + $scope.availability.occurrences = $scope.occurrences; + } return Availability.save( - { availability: $scope.availability } - , function (availability) { $uibModalInstance.close(availability); }); + { availability: $scope.availability }, + function (availability) { $uibModalInstance.close(availability); } + ); }; /** diff --git a/app/controllers/api/availabilities_controller.rb b/app/controllers/api/availabilities_controller.rb index f9e98f5c1..3bf9dfd29 100644 --- a/app/controllers/api/availabilities_controller.rb +++ b/app/controllers/api/availabilities_controller.rb @@ -47,6 +47,8 @@ class API::AvailabilitiesController < API::ApiController def create authorize Availability @availability = Availability.new(availability_params) + service = Availabilities::CreateAvailabilitiesService.new + service.create(@availability, params[:availability][:occurrences]) if @availability.save render :show, status: :created, location: @availability else @@ -140,6 +142,7 @@ class API::AvailabilitiesController < API::ApiController def availability_params params.require(:availability).permit(:start_at, :end_at, :available_type, :machine_ids, :training_ids, :nb_total_places, + :is_recurrent, :period, :nb_periods, :end_date, machine_ids: [], training_ids: [], space_ids: [], tag_ids: [], machines_attributes: %i[id _destroy]) end diff --git a/app/models/availability.rb b/app/models/availability.rb index 0951ddf19..13d48d253 100644 --- a/app/models/availability.rb +++ b/app/models/availability.rb @@ -36,7 +36,7 @@ class Availability < ActiveRecord::Base attr_accessor :is_reserved, :slot_id, :can_modify validates :start_at, :end_at, presence: true - validate :length_must_be_1h_minimum, unless: proc { end_at.blank? or start_at.blank? } + validate :length_must_be_slot_multiple, unless: proc { end_at.blank? or start_at.blank? } validate :should_be_associated ## elastic callbacks @@ -155,8 +155,10 @@ class Availability < ActiveRecord::Base private - def length_must_be_1h_minimum - errors.add(:end_at, I18n.t('availabilities.must_be_at_least_1_hour_after_the_start_date')) if end_at < (start_at + 1.hour) + def length_must_be_slot_multiple + if end_at < (start_at + Rails.application.secrets.slot_duration.minutes) + errors.add(:end_at, I18n.t('availabilities.length_must_be_slot_multiple', MIN: Rails.application.secrets.slot_duration)) + end end def should_be_associated diff --git a/app/services/availabilities/create_availabilities_service.rb b/app/services/availabilities/create_availabilities_service.rb new file mode 100644 index 000000000..f488d6449 --- /dev/null +++ b/app/services/availabilities/create_availabilities_service.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# Provides helper methods toi create an Availability with multiple occurrences +class Availabilities::CreateAvailabilitiesService + def create(availability, occurrences) + availability.update_attributes(occurrence_id: availability.id) + + occurrences.each do |o| + Availability.new( + start_at: o[:start_at], + end_at: o[:end_at], + available_type: availability.available_type, + is_recurrent: availability.is_recurrent, + period: availability.period, + nb_periods: availability.nb_periods, + end_date: availability.end_date, + occurrence_id: availability.occurrence_id, + machine_ids: availability.machine_ids, + training_ids: availability.training_ids, + space_ids: availability.space_ids, + tag_ids: availability.tag_ids + ).save! + end + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 1bf554024..ab0e1d645 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -62,7 +62,7 @@ en: # availability slots in the calendar not_available: "Not available" i_ve_reserved: "I've reserved" - must_be_at_least_1_hour_after_the_start_date: "must be at least 1 hour after the start date" + length_must_be_slot_multiple: "must be at least %{MIN} minutes after the start date" must_be_associated_with_at_least_1_machine: "must be associated with at least 1 machine" members: diff --git a/config/locales/es.yml b/config/locales/es.yml index 4dc9dcc30..a117f19a0 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -62,7 +62,7 @@ es: # availability slots in the calendar not_available: "No disponible" i_ve_reserved: "He reservado" - must_be_at_least_1_hour_after_the_start_date: "Debe ser al menos 1 hora después de la fecha de inicio" + length_must_be_slot_multiple: "Debe ser al menos %{MIN} minutos después de la fecha de inicio" must_be_associated_with_at_least_1_machine: "debe estar asociado con al menos 1 máquina" members: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 120a624d7..6a71f1e2c 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -62,7 +62,7 @@ fr: # créneaux de disponibilité dans le calendrier not_available: "Non disponible" i_ve_reserved: "J'ai réservé" - must_be_at_least_1_hour_after_the_start_date: "doit être au moins 1 heure après la date de début" + length_must_be_slot_multiple: "doit être au moins %{MIN} minutes après la date de début" must_be_associated_with_at_least_1_machine: "doit être associé avec au moins 1 machine" members: diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 813e004ff..6230f49a0 100755 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -62,7 +62,7 @@ pt: # availability slots in the calendar not_available: "Não disponível " i_ve_reserved: "Eu reservei" - must_be_at_least_1_hour_after_the_start_date: "deve ser pelo menos 1 hora após a data de início" + length_must_be_slot_multiple: "deve ser pelo menos %{MIN} minutos após a data de início" must_be_associated_with_at_least_1_machine: "deve estar associada a pelo menos uma máquina" members: diff --git a/db/migrate/20191113103352_add_recurrence_to_availabilities.rb b/db/migrate/20191113103352_add_recurrence_to_availabilities.rb new file mode 100644 index 000000000..c989f3a5c --- /dev/null +++ b/db/migrate/20191113103352_add_recurrence_to_availabilities.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# From this migration, we store recurrence info into the availability object, the availability can be linked to others, which are +# its "children". +class AddRecurrenceToAvailabilities < ActiveRecord::Migration + def change + add_column :availabilities, :is_recurrent, :boolean + add_column :availabilities, :occurrence_id, :integer + add_column :availabilities, :period, :string + add_column :availabilities, :nb_periods, :integer + add_column :availabilities, :end_date, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index 3a3ac9fb4..6e2ed6b2c 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: 20190924140726) do +ActiveRecord::Schema.define(version: 20191113103352) 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" @@ -92,6 +92,11 @@ ActiveRecord::Schema.define(version: 20190924140726) do t.integer "nb_total_places" t.boolean "destroying", default: false t.boolean "lock", default: false + t.boolean "is_recurrent" + t.integer "occurrence_id" + t.string "period" + t.integer "nb_periods" + t.datetime "end_date" end create_table "availability_tags", force: :cascade do |t| From 26738563bda4a5fe364c5d29f160a2ad1bc43af8 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 18 Nov 2019 11:50:20 +0100 Subject: [PATCH 021/135] create reccursive events --- app/assets/javascripts/controllers/admin/calendar.js.erb | 2 +- app/controllers/api/availabilities_controller.rb | 6 ++++-- .../availabilities/create_availabilities_service.rb | 4 +++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/controllers/admin/calendar.js.erb b/app/assets/javascripts/controllers/admin/calendar.js.erb index d10a0550d..812549c45 100644 --- a/app/assets/javascripts/controllers/admin/calendar.js.erb +++ b/app/assets/javascripts/controllers/admin/calendar.js.erb @@ -617,7 +617,7 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui if ($scope.availability.is_recurrent) { const date = moment($scope.availability.start_at); - const diff = moment($scope.availability.start_at).diff($scope.availability.end_at); + const diff = moment($scope.availability.end_at).diff($scope.availability.start_at); const end = moment($scope.availability.end_date).endOf('day'); while (date.isBefore(end)) { const occur_end = moment(date).add(diff, 'ms'); diff --git a/app/controllers/api/availabilities_controller.rb b/app/controllers/api/availabilities_controller.rb index 3bf9dfd29..b6c6da6c4 100644 --- a/app/controllers/api/availabilities_controller.rb +++ b/app/controllers/api/availabilities_controller.rb @@ -47,9 +47,11 @@ class API::AvailabilitiesController < API::ApiController def create authorize Availability @availability = Availability.new(availability_params) - service = Availabilities::CreateAvailabilitiesService.new - service.create(@availability, params[:availability][:occurrences]) if @availability.save + if params[:availability][:occurrences] + service = Availabilities::CreateAvailabilitiesService.new + service.create(@availability, params[:availability][:occurrences]) + end render :show, status: :created, location: @availability else render json: @availability.errors, status: :unprocessable_entity diff --git a/app/services/availabilities/create_availabilities_service.rb b/app/services/availabilities/create_availabilities_service.rb index f488d6449..f05a145ae 100644 --- a/app/services/availabilities/create_availabilities_service.rb +++ b/app/services/availabilities/create_availabilities_service.rb @@ -2,10 +2,12 @@ # Provides helper methods toi create an Availability with multiple occurrences class Availabilities::CreateAvailabilitiesService - def create(availability, occurrences) + def create(availability, occurrences = []) availability.update_attributes(occurrence_id: availability.id) occurrences.each do |o| + next if availability.start_at == o[:start_at] && availability.end_at == o[:end_at] + Availability.new( start_at: o[:start_at], end_at: o[:end_at], From 9ec736e6b5f56ba0c8047d365a0f8d9d1b050e3e Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 18 Nov 2019 17:29:51 +0100 Subject: [PATCH 022/135] delete recursive slots --- .../controllers/admin/calendar.js.erb | 112 ++++++++++++++---- .../controllers/admin/members.js.erb | 2 +- .../admin/calendar/deleteRecurrent.html | 26 ++++ .../api/availabilities_controller.rb | 10 +- .../create_availabilities_service.rb | 2 +- .../delete_availabilities_service.rb | 33 ++++++ .../api/availabilities/show.json.jbuilder | 8 +- config/locales/app.admin.en.yml | 7 +- config/locales/app.admin.es.yml | 11 +- config/locales/app.admin.fr.yml | 10 +- config/locales/app.admin.pt.yml | 7 +- 11 files changed, 183 insertions(+), 45 deletions(-) create mode 100644 app/assets/templates/admin/calendar/deleteRecurrent.html create mode 100644 app/services/availabilities/delete_availabilities_service.rb diff --git a/app/assets/javascripts/controllers/admin/calendar.js.erb b/app/assets/javascripts/controllers/admin/calendar.js.erb index 812549c45..6e1b19d1b 100644 --- a/app/assets/javascripts/controllers/admin/calendar.js.erb +++ b/app/assets/javascripts/controllers/admin/calendar.js.erb @@ -228,32 +228,24 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state */ $scope.removeSlot = function () { // open a confirmation dialog - dialogs.confirm( - { - resolve: { - object () { - return { - title: _t('admin_calendar.confirmation_required'), - msg: _t('admin_calendar.do_you_really_want_to_delete_this_slot') - }; - } - } - }, - function () { - // the admin has confirmed, delete the slot - Availability.delete( - { id: $scope.availability.id }, - function () { - uiCalendarConfig.calendars.calendar.fullCalendar('removeEvents', $scope.availability.id); - - growl.success(_t('admin_calendar.the_slot_START-END_has_been_successfully_deleted', { START: moment(event.start).format('LL LT'), END: moment(event.end).format('LT') })); - $scope.availability = null; - }, - function () { - growl.error(_t('admin_calendar.unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member', { START: moment(event.start).format('LL LT'), END: moment(event.end).format('LT') })); - }); + const modalInstance = $uibModal.open({ + animation: true, + templateUrl: '<%= asset_path "admin/calendar/deleteRecurrent.html" %>', + size: 'md', + controller: 'DeleteRecurrentAvailabilityController', + resolve: { + availabilityPromise: ['Availability', function (Availability) { return Availability.get({ id: $scope.availability.id }).$promise; }] } - ); + }); + // once the dialog was closed, do things depending on the result + modalInstance.result.then(function (res) { + if (res.status == 'success') { + $scope.availability = null; + } + for (const availability of res.availabilities) { + uiCalendarConfig.calendars.calendar.fullCalendar('removeEvents', availability); + } + }); }; /* PRIVATE SCOPE */ @@ -669,3 +661,73 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui return initialize(); } ]); + +/** + * Controller used in the slot deletion modal window + */ +Application.Controllers.controller('DeleteRecurrentAvailabilityController', ['$scope', '$uibModalInstance', 'Availability', 'availabilityPromise', 'growl', '_t', + function ($scope, $uibModalInstance, Availability, availabilityPromise, growl, _t) { + + // is the current slot (to be deleted) recurrent? + $scope.isRecurrent = availabilityPromise.is_recurrent; + + // with recurrent slots: how many slots should we delete? + $scope.deleteMode = 'single'; + + /** + * Confirmation callback + */ + $scope.ok = function () { + const { id, start_at, end_at } = availabilityPromise; + // the admin has confirmed, delete the slot + Availability.delete( + { id, mode: $scope.deleteMode }, + function (res) { + // delete success + if (res.deleted > 1) { + growl.success(_t( + 'admin_calendar.slots_deleted', + {START: moment(start_at).format('LL LT'), COUNT: res.deleted - 1}, + 'messageformat' + )); + } else { + growl.success(_t( + 'admin_calendar.slot_successfully_deleted', + {START: moment(start_at).format('LL LT'), END: moment(end_at).format('LT')} + )); + } + $uibModalInstance.close({ + status: 'success', + availabilities: res.details.map(function (d) { return d.availability.id }) + }); + }, + function (res) { + // not everything was deleted + const { data } = res; + if (data.total > 1) { + growl.warning(_t( + 'admin_calendar.slots_not_deleted', + {TOTAL: data.total, COUNT: data.total - data.deleted}, + 'messageformat' + )); + } else { + growl.error(_t( + 'admin_calendar.unable_to_delete_the_slot', + {START: moment(start_at).format('LL LT'), END: moment(end_at).format('LT')} + )); + } + $uibModalInstance.close({ + status: 'failed', + availabilities: data.details.filter(function (d) { return d.status }).map(function (d) { return d.availability.id }) + }); + }); + } + + /** + * Cancellation callback + */ + $scope.cancel = function () { + $uibModalInstance.dismiss('cancel'); + } + } +]); diff --git a/app/assets/javascripts/controllers/admin/members.js.erb b/app/assets/javascripts/controllers/admin/members.js.erb index 2e95fd0c1..e9c9f77c9 100644 --- a/app/assets/javascripts/controllers/admin/members.js.erb +++ b/app/assets/javascripts/controllers/admin/members.js.erb @@ -487,7 +487,7 @@ Application.Controllers.controller('EditMemberController', ['$scope', '$state', $scope.cancel = function () { $uibModalInstance.dismiss('cancel'); }; }] }); - // once the form was validated succesfully ... + // once the form was validated successfully ... return modalInstance.result.then(function (subscription) { $scope.subscription = subscription; }); }; diff --git a/app/assets/templates/admin/calendar/deleteRecurrent.html b/app/assets/templates/admin/calendar/deleteRecurrent.html new file mode 100644 index 000000000..9baeb1799 --- /dev/null +++ b/app/assets/templates/admin/calendar/deleteRecurrent.html @@ -0,0 +1,26 @@ + + + diff --git a/app/controllers/api/availabilities_controller.rb b/app/controllers/api/availabilities_controller.rb index b6c6da6c4..825d4104e 100644 --- a/app/controllers/api/availabilities_controller.rb +++ b/app/controllers/api/availabilities_controller.rb @@ -5,7 +5,7 @@ class API::AvailabilitiesController < API::ApiController include FablabConfiguration before_action :authenticate_user!, except: [:public] - before_action :set_availability, only: %i[show update destroy reservations lock] + before_action :set_availability, only: %i[show update reservations lock] before_action :define_max_visibility, only: %i[machine trainings spaces] respond_to :json @@ -69,10 +69,12 @@ class API::AvailabilitiesController < API::ApiController def destroy authorize Availability - if @availability.safe_destroy - head :no_content + service = Availabilities::DeleteAvailabilitiesService.new + res = service.delete(params[:id], params[:mode]) + if res.all? { |r| r[:status] } + render json: { deleted: res.length, details: res }, status: :ok else - head :unprocessable_entity + render json: { total: res.length, deleted: res.select { |r| r[:status] }.length, details: res }, status: :unprocessable_entity end end diff --git a/app/services/availabilities/create_availabilities_service.rb b/app/services/availabilities/create_availabilities_service.rb index f05a145ae..f848f0597 100644 --- a/app/services/availabilities/create_availabilities_service.rb +++ b/app/services/availabilities/create_availabilities_service.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# Provides helper methods toi create an Availability with multiple occurrences +# Provides helper methods to create an Availability with multiple occurrences class Availabilities::CreateAvailabilitiesService def create(availability, occurrences = []) availability.update_attributes(occurrence_id: availability.id) diff --git a/app/services/availabilities/delete_availabilities_service.rb b/app/services/availabilities/delete_availabilities_service.rb new file mode 100644 index 000000000..9616b28b4 --- /dev/null +++ b/app/services/availabilities/delete_availabilities_service.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# Provides helper methods to delete an Availability with multiple occurrences +class Availabilities::DeleteAvailabilitiesService + def delete(availability_id, mode = 'single') + + results = [] + availability = Availability.find(availability_id) + availabilities = case mode + when 'single' + [availability] + when 'next' + Availability.where( + 'start_at >= ? AND occurrence_id = ? AND is_recurrent = true', + availability.start_at, + availability.occurrence_id + ) + when 'all' + Availability.where( + 'occurrence_id = ? AND is_recurrent = true', + availability.occurrence_id + ) + else + [] + end + + availabilities.each do |a| + # here we use double negation because safe_destroy can return either a boolean (false) or an Availability (in case of delete success) + results.push status: !!a.safe_destroy, availability: a # rubocop:disable Style/DoubleNegation + end + results + end +end diff --git a/app/views/api/availabilities/show.json.jbuilder b/app/views/api/availabilities/show.json.jbuilder index 68706604f..df5b2d734 100644 --- a/app/views/api/availabilities/show.json.jbuilder +++ b/app/views/api/availabilities/show.json.jbuilder @@ -1,14 +1,14 @@ -json.id @availability.id +# frozen_string_literal: true + +json.extract! @availability, :id, :title, :lock, :is_recurrent, :occurrence_id, :period, :nb_periods, :end_date json.start_at @availability.start_at.iso8601 json.end_at @availability.end_at.iso8601 json.available_type @availability.available_type json.machine_ids @availability.machine_ids json.backgroundColor 'white' json.borderColor availability_border_color(@availability) -json.title @availability.title json.tag_ids @availability.tag_ids json.tags @availability.tags do |t| json.id t.id json.name t.name -end -json.lock @availability.lock +end \ No newline at end of file diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index d36dec8d0..4c9640bc7 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -59,8 +59,10 @@ en: reservable: "Reservable(s):" labels: "Label(s):" none: "None" - the_slot_START-END_has_been_successfully_deleted: "The slot {{START}} - {{END}} has been successfully deleted" # angular interpolation - unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "Unable to delete the slot {{START}} - {{END}} because it's already reserved by a member" # angular interpolation + slot_successfully_deleted: "The slot {{START}} - {{END}} has been successfully deleted" # angular interpolation + slots_deleted: "The slot of {{START}}, and {COUNT, plural, =1{one other} other{{COUNT} others}}}, have been deleted" # messageFormat interpolation + unable_to_delete_the_slot: "Unable to delete the slot {{START}} - {{END}}, probably because it's already reserved by a member" # angular interpolation + slots_not_deleted: "On {TOTAL} slots, {COUNT, plural, =1{one was not deleted} other{{COUNT} were not deleted}}. Some reservations may exist on {COUNT, plural, =1{it} other{them}}." # messageFormat interpolation you_should_select_at_least_a_machine: "You should select at least one machine on this slot." export_is_running_you_ll_be_notified_when_its_ready: "Export is running. You'll be notified when it's ready." actions: "Actions" @@ -76,6 +78,7 @@ en: unlockable_because_reservations: "Unable to block booking on this slot because some uncancelled reservations exist on it." delete_slot: "Delete this slot" do_you_really_want_to_delete_this_slot: "Do you really want to delete this slot?" + delete_recurring_slot: "You're about to delete a recurring slot. What do you want to do?" event_in_the_past: "Unable to create a slot in the past." project_elements: diff --git a/config/locales/app.admin.es.yml b/config/locales/app.admin.es.yml index 036104dc3..c44ecbf2b 100644 --- a/config/locales/app.admin.es.yml +++ b/config/locales/app.admin.es.yml @@ -56,11 +56,13 @@ es: select_nb_period: "Please select a number of periods for the recurrence" # translation_missing select_end_date: "Please select the date of the last occurrence" # translation_missing about_to_create: "You are about to create the following {TYPE, select, machines{machine} training{training} space{space} other{other}} {NUMBER, plural, one{slot} other{slots}}:" # messageFormat interpolation # translation_missing - reservable: "Reservable(s):" # translation_missing + reservable: "Reservable(s):" # translation_missing labels: "Etiqueta(s):" none: "Ninguna" - the_slot_START-END_has_been_successfully_deleted: "La ranura {{START}} - {{END}} se ha eliminado correctamente" # angular interpolation - unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "No se puede eliminar la ranura {{START}} - {{END}} porque ya está reservada por un miembror" # angular interpolation + slot_successfully_deleted: "La ranura {{START}} - {{END}} se ha eliminado correctamente" # angular interpolation + slots_deleted: "The slot of {{START}}, and {COUNT, plural, =1{one other} other{{COUNT} others}}}, have been deleted" # messageFormat interpolation # translation_missing + unable_to_delete_the_slot: "No se puede eliminar la ranura {{START}} - {{END}}, probablemente porque ya está reservada por un miembror" # angular interpolation + slots_not_deleted: "On {TOTAL} slots, {COUNT, plural, =1{one was not deleted} other{{COUNT} were not deleted}}. Some reservations may exist on {COUNT, plural, =1{it} other{them}}." # messageFormat interpolation # translation_missing you_should_select_at_least_a_machine: "Debe seleccionar al menos una máquina en esta ranura." export_is_running_you_ll_be_notified_when_its_ready: "La exportación se está ejecutando. Se le notificará cuando esté listo." actions: "Acciones" @@ -75,7 +77,8 @@ es: reservations_locked: "La reserva está bloqueada" unlockable_because_reservations: "No se puede bloquear la reserva en esta ranura porque existen algunas reservas no canceladas." delete_slot: "Delete this slot" # translation_missing - do_you_really_want_to_delete_this_slot: "Do you really want to delete this slot?" + do_you_really_want_to_delete_this_slot: "Do you really want to delete this slot?" # translation_missing + delete_recurring_slot: "You're about to delete a recurring slot. What do you want to do?"# translation_missing event_in_the_past: "Unable to create a slot in the past." # translation_missing project_elements: diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index b7684d326..d448b3b30 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -59,8 +59,10 @@ fr: reservable: "Réservable(s) :" labels: "Étiquette(s) :" none: "Aucune" - the_slot_START-END_has_been_successfully_deleted: "Le créneau {{START}} - {{END}} a bien été supprimé" # angular interpolation - unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "Le créneau {{START}} - {{END}} n'a pu être supprimé car il est déjà réservé par un membre" # angular interpolation + slot_successfully_deleted: "Le créneau {{START}} - {{END}} a bien été supprimé" # angular interpolation + slots_deleted: "Le créneau du {{START}}, ainsi {COUNT, plural, =1{qu'un autre créneau} other{que {COUNT} autres créneaux}}}, ont été supprimés" # messageFormat interpolation + unable_to_delete_the_slot: "Le créneau {{START}} - {{END}} n'a pu être supprimé, probablement car il est déjà réservé par un membre" # angular interpolation + slots_not_deleted: "Sur {TOTAL} créneaux, {COUNT, plural, =1{un n'a pas pu être supprimé} other{{COUNT} n'ont pas pu être supprimés}}. Il est possible que des réservations existent sur {COUNT, plural, =1{celui-ci} other{ceux-ci}}." # messageFormat interpolation you_should_select_at_least_a_machine: "Vous devriez sélectionner au moins une machine pour ce créneau." export_is_running_you_ll_be_notified_when_its_ready: "L'export est en cours. Vous serez notifié lorsqu'il sera prêt." actions: "Actions" @@ -76,6 +78,10 @@ fr: unlockable_because_reservations: "Impossible de bloquer les réservations sur ce créneau car il existe des réservations non annulées sur celui-ci." delete_slot: "Supprimer le créneau" do_you_really_want_to_delete_this_slot: "Êtes vous sur de vouloir supprimer ce créneau ?" + delete_recurring_slot: "Vous êtes sur le point de supprimer un créneau périodique. Que voulez-vous supprimer ?" + delete_this_slot: "Uniquement ce créneau" + delete_this_and_next: "Ce créneau et tous les suivants" + delete_all: "Tous les créneaux" event_in_the_past: "Impossible de créer un créneau dans le passé." project_elements: diff --git a/config/locales/app.admin.pt.yml b/config/locales/app.admin.pt.yml index e5c061bb5..158118490 100755 --- a/config/locales/app.admin.pt.yml +++ b/config/locales/app.admin.pt.yml @@ -59,8 +59,10 @@ pt: reservable: "Reservable(s) :" # translation_missing labels: "Etiqueta(s):" none: "Nenhuma" - the_slot_START-END_has_been_successfully_deleted: "O slot {{START}} - {{END}} foi deletado com sucesso" # angular interpolation - unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "Não é possível deletar o slot {{START}} - {{END}} porque já foi reservado por um membro" # angular interpolation + slot_successfully_deleted: "O slot {{START}} - {{END}} foi deletado com sucesso" # angular interpolation + slots_deleted: "The slot of {{START}}, and {COUNT, plural, =1{one other} other{{COUNT} others}}}, have been deleted" # messageFormat interpolation # translation_missing + unable_to_delete_the_slot: "Não é possível deletar o slot {{START}} - {{END}}, provavelmente porque já foi reservado por um membro" # angular interpolation + slots_not_deleted: "On {TOTAL} slots, {COUNT, plural, =1{one was not deleted} other{{COUNT} were not deleted}}. Some reservations may exist on {COUNT, plural, =1{it} other{them}}." # messageFormat interpolation # translation_missing you_should_select_at_least_a_machine: "Você deveria selecionar a última máquina neste slot." export_is_running_you_ll_be_notified_when_its_ready: "A Exportação está em andamento. Você será notificado quando terminar." actions: "Ações" @@ -76,6 +78,7 @@ pt: unlockable_because_reservations: "Não é possível bloquear a reserva neste slot porque existem algumas reservas não cancelados nele." delete_slot: "Exclua o slot" do_you_really_want_to_delete_this_slot: "Você realmente quer excluir esse slot?" + delete_recurring_slot: "You're about to delete a recurring slot. What do you want to do?"# translation_missing event_in_the_past: "Unable to create a slot in the past." # translation_missing project_elements: From ad928bd4e62d22d4c2048d110d0b75d55aa3ea0f Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 19 Nov 2019 11:44:32 +0100 Subject: [PATCH 023/135] ability to set phone number optional or required also: show stars on required fields in new admin form --- CHANGELOG.md | 4 ++ app/assets/javascripts/app.js | 2 + .../templates/admin/admins/new.html.erb | 2 +- app/assets/templates/shared/_admin_form.html | 39 ++++++++++--------- .../templates/shared/_member_form.html.erb | 4 +- .../templates/shared/signupModal.html.erb | 6 ++- app/models/profile.rb | 2 +- app/models/user.rb | 3 +- app/views/application/index.html.erb | 1 + config/application.yml.default | 1 + config/secrets.yml | 4 ++ doc/environment.md | 5 +++ docker/env.example | 1 + 13 files changed, 48 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 562f6f662..0557c355a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,17 @@ # Changelog Fab Manager +- Ability to create and delete periodic calendar availabilities (recurrence) - An administrator can delete a member - Ability to configure the duration of a reservation slot. Previously, only 60 minutes slots were allowed +- Display indications on required fields in new administrator form +- Configuration of phone number in members registration forms: can be required or optional, depending on `PHONE_REQUIRED` configuration - Improved user experience in defining slots in the calendar management - Improved notification email to the member when a rolling subscription is taken - Handle Ctrl^C in upgrade scripts - Updated moment-timezone - Fix a security issue: fixed [CVE-2019-15587](https://github.com/advisories/GHSA-c3gv-9cxf-6f57) - [TODO DEPLOY] add the `SLOT_DURATION` environment variable (see [doc/environment.md](doc/environment.md#SLOT_DURATION) for configuration details) +- [TODO DEPLOY] add the `PHONE_REQUIRED` environment variable (see [doc/environment.md](doc/environment.md#PHONE_REQUIRED) for configuration details) - [TODO DEPLOY] -> (only dev) `bundle install` - [TODO DEPLOY] `rake db:migrate` diff --git a/app/assets/javascripts/app.js b/app/assets/javascripts/app.js index 01b4265e9..ea94b5a12 100644 --- a/app/assets/javascripts/app.js +++ b/app/assets/javascripts/app.js @@ -86,6 +86,8 @@ angular.module('application', ['ngCookies', 'ngResource', 'ngSanitize', 'ui.rout $rootScope.fablabWithoutOnlinePayment = Fablab.withoutOnlinePayment; // Global config: if true, no invoices will be generated $rootScope.fablabWithoutInvoices = Fablab.withoutInvoices; + // Global config: if true, the phone number is required to create an account + $rootScope.phoneRequired = Fablab.phoneRequired; // Global function to allow the user to navigate to the previous screen (ie. $state). // If no previous $state were recorded, navigate to the home page diff --git a/app/assets/templates/admin/admins/new.html.erb b/app/assets/templates/admin/admins/new.html.erb index a53506394..8eeebbb34 100644 --- a/app/assets/templates/admin/admins/new.html.erb +++ b/app/assets/templates/admin/admins/new.html.erb @@ -20,7 +20,7 @@
- + '">
diff --git a/app/assets/templates/shared/_admin_form.html b/app/assets/templates/shared/_admin_form.html index dae346c14..8470d9e70 100644 --- a/app/assets/templates/shared/_admin_form.html +++ b/app/assets/templates/shared/_admin_form.html @@ -1,26 +1,27 @@
- - + + +
- +
- +
- +
- +
- + + ng-required="phoneRequired"/>
{{ 'phone_number_is_required' }}
diff --git a/app/assets/templates/shared/signupModal.html.erb b/app/assets/templates/shared/signupModal.html.erb index 9c33debf2..f138bc513 100644 --- a/app/assets/templates/shared/signupModal.html.erb +++ b/app/assets/templates/shared/signupModal.html.erb @@ -204,9 +204,11 @@ name="phone" class="form-control" placeholder="{{ 'phone_number' | translate }}" - required> + ng-required="phoneRequired">
- + + + {{ 'phone_number_is_required' }}
diff --git a/app/models/profile.rb b/app/models/profile.rb index b673cd417..6814835c6 100644 --- a/app/models/profile.rb +++ b/app/models/profile.rb @@ -10,7 +10,7 @@ class Profile < ActiveRecord::Base validates :first_name, presence: true, length: { maximum: 30 } validates :last_name, presence: true, length: { maximum: 30 } - validates_numericality_of :phone, only_integer: true, allow_blank: false + validates_numericality_of :phone, only_integer: true, allow_blank: false, if: -> { Rails.application.secrets.phone_required } after_commit :update_invoicing_profile, if: :invoicing_data_was_modified?, on: [:update] diff --git a/app/models/user.rb b/app/models/user.rb index 481032fd7..919660c5a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -160,7 +160,8 @@ class User < ActiveRecord::Base def need_completion? statistic_profile.gender.nil? || profile.first_name.blank? || profile.last_name.blank? || username.blank? || - email.blank? || encrypted_password.blank? || group_id.nil? || statistic_profile.birthday.blank? || profile.phone.blank? + email.blank? || encrypted_password.blank? || group_id.nil? || statistic_profile.birthday.blank? || + (Rails.application.secrets.phone_required && profile.phone.blank?) end ## Retrieve the requested data in the User and user's Profile tables diff --git a/app/views/application/index.html.erb b/app/views/application/index.html.erb index 123ad711b..65efb7dd1 100644 --- a/app/views/application/index.html.erb +++ b/app/views/application/index.html.erb @@ -20,6 +20,7 @@ Fablab.withoutSpaces = ('<%= Rails.application.secrets.fablab_without_spaces %>' !== 'false'); Fablab.withoutOnlinePayment = ('<%= Rails.application.secrets.fablab_without_online_payments %>' === 'true'); Fablab.withoutInvoices = ('<%= Rails.application.secrets.fablab_without_invoices %>' === 'true'); + Fablab.phoneRequired = ('<%= Rails.application.secrets.phone_required %>' === 'true'); Fablab.slotDuration = parseInt("<%= ApplicationHelper::SLOT_DURATION %>", 10); Fablab.disqusShortname = "<%= Rails.application.secrets.disqus_shortname %>"; Fablab.defaultHost = "<%= Rails.application.secrets.default_host %>"; diff --git a/config/application.yml.default b/config/application.yml.default index e8775cb29..f59f0a3ec 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -20,6 +20,7 @@ FABLAB_WITHOUT_PLANS: 'false' FABLAB_WITHOUT_SPACES: 'true' FABLAB_WITHOUT_ONLINE_PAYMENT: 'false' FABLAB_WITHOUT_INVOICES: 'false' +PHONE_REQUIRED: 'true' SLOT_DURATION: '60' diff --git a/config/secrets.yml b/config/secrets.yml index 87e05bab2..e277437a1 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -20,6 +20,7 @@ development: fablab_without_spaces: <%= ENV["FABLAB_WITHOUT_SPACES"] %> fablab_without_online_payments: <%= ENV["FABLAB_WITHOUT_ONLINE_PAYMENT"] %> fablab_without_invoices: <%= ENV["FABLAB_WITHOUT_INVOICES"] %> + phone_required: <%= ENV["PHONE_REQUIRED"] %> slot_duration: <%= ENV["SLOT_DURATION"] %> default_host: <%= ENV["DEFAULT_HOST"] %> default_protocol: <%= ENV["DEFAULT_PROTOCOL"] %> @@ -63,6 +64,7 @@ test: fablab_without_spaces: false fablab_without_online_payments: false fablab_without_invoices: false + phone_required: true slot_duration: <%= ENV["SLOT_DURATION"] %> default_host: <%= ENV["DEFAULT_HOST"] %> default_protocol: <%= ENV["DEFAULT_PROTOCOL"] %> @@ -106,6 +108,7 @@ staging: fablab_without_spaces: <%= ENV["FABLAB_WITHOUT_SPACES"] %> fablab_without_online_payments: <%= ENV["FABLAB_WITHOUT_ONLINE_PAYMENT"] %> fablab_without_invoices: <%= ENV["FABLAB_WITHOUT_INVOICES"] %> + phone_required: <%= ENV["PHONE_REQUIRED"] %> slot_duration: <%= ENV["SLOT_DURATION"] %> default_host: <%= ENV["DEFAULT_HOST"] %> default_protocol: <%= ENV["DEFAULT_PROTOCOL"] %> @@ -160,6 +163,7 @@ production: fablab_without_spaces: <%= ENV["FABLAB_WITHOUT_SPACES"] %> fablab_without_online_payments: <%= ENV["FABLAB_WITHOUT_ONLINE_PAYMENT"] %> fablab_without_invoices: <%= ENV["FABLAB_WITHOUT_INVOICES"] %> + phone_required: <%= ENV["PHONE_REQUIRED"] %> slot_duration: <%= ENV["SLOT_DURATION"] %> default_host: <%= ENV["DEFAULT_HOST"] %> default_protocol: <%= ENV["DEFAULT_PROTOCOL"] %> diff --git a/doc/environment.md b/doc/environment.md index 5ba50d6ec..b0bee2b16 100644 --- a/doc/environment.md +++ b/doc/environment.md @@ -102,6 +102,11 @@ Valid stripe API keys are still required, even if you don't require online payme If set to 'true', the invoices will be disabled. This is useful if you have your own invoicing system and you want to prevent Fab-manager from generating and sending invoices to members. **Very important**: if you disable invoices, you still have to configure VAT in the interface to prevent errors in accounting and prices. + + + PHONE_REQUIRED + +If set to 'false' the phone number won't be required to register a new user on the software. SLOT_DURATION diff --git a/docker/env.example b/docker/env.example index 5460ef28c..2f3f1d014 100644 --- a/docker/env.example +++ b/docker/env.example @@ -13,6 +13,7 @@ FABLAB_WITHOUT_PLANS=false FABLAB_WITHOUT_SPACES=true FABLAB_WITHOUT_ONLINE_PAYMENT=true FABLAB_WITHOUT_INVOICES=false +PHONE_REQUIRED=false SLOT_DURATION=60 From 37d6c84cc6e01bee48e8b4a20d35634ea547750b Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 19 Nov 2019 15:18:00 +0100 Subject: [PATCH 024/135] [bug] unable to remove training picture also: removed some invalid href=# in the templates --- CHANGELOG.md | 1 + app/assets/stylesheets/app.utilities.scss | 4 ++++ .../templates/admin/coupons/edit.html.erb | 2 +- .../templates/admin/coupons/new.html.erb | 4 ++-- .../templates/admin/trainings/_form.html.erb | 2 +- app/assets/templates/shared/_cart.html.erb | 2 +- app/assets/templates/shared/_coupon.html.erb | 2 +- app/assets/templates/shared/header.html.erb | 22 +++++++++---------- app/assets/templates/shared/leftnav.html.erb | 20 ++++++++--------- 9 files changed, 32 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0557c355a..1fe181342 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Improved notification email to the member when a rolling subscription is taken - Handle Ctrl^C in upgrade scripts - Updated moment-timezone +- Fix a bug: unable to remove the picture from a training - Fix a security issue: fixed [CVE-2019-15587](https://github.com/advisories/GHSA-c3gv-9cxf-6f57) - [TODO DEPLOY] add the `SLOT_DURATION` environment variable (see [doc/environment.md](doc/environment.md#SLOT_DURATION) for configuration details) - [TODO DEPLOY] add the `PHONE_REQUIRED` environment variable (see [doc/environment.md](doc/environment.md#PHONE_REQUIRED) for configuration details) diff --git a/app/assets/stylesheets/app.utilities.scss b/app/assets/stylesheets/app.utilities.scss index 274545752..7b6349c1d 100644 --- a/app/assets/stylesheets/app.utilities.scss +++ b/app/assets/stylesheets/app.utilities.scss @@ -379,6 +379,10 @@ p, .widget p { justify-content: center; } +.pointer { + cursor: pointer; +} + @media screen and (min-width: $screen-lg-min) { .b-r-lg {border-right: 1px solid $border-color; } .hide-b-r-lg { border: none !important; } diff --git a/app/assets/templates/admin/coupons/edit.html.erb b/app/assets/templates/admin/coupons/edit.html.erb index b391f8dbc..438b4e0a4 100644 --- a/app/assets/templates/admin/coupons/edit.html.erb +++ b/app/assets/templates/admin/coupons/edit.html.erb @@ -2,7 +2,7 @@
- +
diff --git a/app/assets/templates/admin/coupons/new.html.erb b/app/assets/templates/admin/coupons/new.html.erb index 16bf475cc..b0a0d992d 100644 --- a/app/assets/templates/admin/coupons/new.html.erb +++ b/app/assets/templates/admin/coupons/new.html.erb @@ -2,7 +2,7 @@
- +
@@ -20,7 +20,7 @@
- + '">
diff --git a/app/assets/templates/shared/_cart.html.erb b/app/assets/templates/shared/_cart.html.erb index 4a7179243..2e8f37beb 100644 --- a/app/assets/templates/shared/_cart.html.erb +++ b/app/assets/templates/shared/_cart.html.erb @@ -33,7 +33,7 @@
- +
diff --git a/app/assets/templates/shared/_coupon.html.erb b/app/assets/templates/shared/_coupon.html.erb index bc4552ece..163d92493 100644 --- a/app/assets/templates/shared/_coupon.html.erb +++ b/app/assets/templates/shared/_coupon.html.erb @@ -1,5 +1,5 @@
- {{ 'i_have_a_coupon' }} + {{ 'i_have_a_coupon' }}
diff --git a/app/assets/templates/shared/header.html.erb b/app/assets/templates/shared/header.html.erb index 56a480048..a2be7428e 100644 --- a/app/assets/templates/shared/header.html.erb +++ b/app/assets/templates/shared/header.html.erb @@ -26,7 +26,7 @@ {{notifications.unread}} -
  • {{ 'sign_up' | translate }}
  • +
  • {{ 'sign_up' | translate }}
  • - {{ 'sign_in' | translate }} + {{ 'sign_in' | translate }}
  • diff --git a/app/assets/templates/shared/leftnav.html.erb b/app/assets/templates/shared/leftnav.html.erb index afde8ad6d..a480316d5 100644 --- a/app/assets/templates/shared/leftnav.html.erb +++ b/app/assets/templates/shared/leftnav.html.erb @@ -8,18 +8,18 @@
    -
    +

    {{ 'admin_calendar.ongoing_reservations' }}

    @@ -57,7 +62,7 @@
    {{ 'admin_calendar.no_reservations' }}
    -
    {{ 'admin_calendar.reservations_locked' }}
    +
    {{ 'admin_calendar.reservations_locked' }}
    @@ -75,28 +80,42 @@
    -
    +

    {{ 'admin_calendar.actions' }}

    -
    +
    +
    diff --git a/app/controllers/api/availabilities_controller.rb b/app/controllers/api/availabilities_controller.rb index 825d4104e..405f8e443 100644 --- a/app/controllers/api/availabilities_controller.rb +++ b/app/controllers/api/availabilities_controller.rb @@ -14,9 +14,10 @@ class API::AvailabilitiesController < API::ApiController start_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:start]) end_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:end]).end_of_day @availabilities = Availability.includes(:machines, :tags, :trainings, :spaces) - .where.not(available_type: 'event') .where('start_at >= ? AND end_at <= ?', start_date, end_date) + @availabilities = @availabilities.where.not(available_type: 'event') unless Rails.application.secrets.events_in_calendar + @availabilities = @availabilities.where.not(available_type: 'space') if fablab_spaces_deactivated? end diff --git a/app/views/api/availabilities/index.json.jbuilder b/app/views/api/availabilities/index.json.jbuilder index 8749f6ca8..e6e86f563 100644 --- a/app/views/api/availabilities/index.json.jbuilder +++ b/app/views/api/availabilities/index.json.jbuilder @@ -6,6 +6,7 @@ json.array!(@availabilities) do |availability| json.available_type availability.available_type json.machine_ids availability.machine_ids json.training_ids availability.training_ids + json.event_id availability.event&.id json.backgroundColor !availability.lock ? 'white' : '#f5f5f5' json.borderColor availability_border_color(availability) json.tag_ids availability.tag_ids diff --git a/config/application.yml.default b/config/application.yml.default index f59f0a3ec..c53e53def 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -22,6 +22,7 @@ FABLAB_WITHOUT_ONLINE_PAYMENT: 'false' FABLAB_WITHOUT_INVOICES: 'false' PHONE_REQUIRED: 'true' +EVENTS_IN_CALENDAR: 'false' SLOT_DURATION: '60' DEFAULT_MAIL_FROM: Fab Manager Demo diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index c69336065..c6c0ee47a 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -18,6 +18,7 @@ en: trainings: "Trainings" machines: "Machines" spaces: "Spaces" + events: "Eventos" availabilities: "Availabilities" availabilities_notice: "Export to an Excel workbook every slots available for reservation, and their occupancy rate." ongoing_reservations: "Ongoing reservations" @@ -83,6 +84,8 @@ en: delete_this_and_next: "This slot and the following" delete_all: "All slots" event_in_the_past: "Unable to create a slot in the past." + edit_event: "Edit the event" + view_reservations: "View reservations" project_elements: # management of the projects' components diff --git a/config/locales/app.admin.es.yml b/config/locales/app.admin.es.yml index db37e504e..d7b1beb2b 100644 --- a/config/locales/app.admin.es.yml +++ b/config/locales/app.admin.es.yml @@ -18,6 +18,7 @@ es: trainings: "Formación" machines: "Máquinas" spaces: "Espacios" + events: "Eventos" availabilities: "Disponibilidades" availabilities_notice: "Exportar a un libro de trabajo de Excel cada ranura disponible para reserva, y su ratio de ocupación." ongoing_reservations: "Reservas en curso" @@ -83,6 +84,8 @@ es: delete_this_and_next: "This slot and the following" # translation_missing delete_all: "All slots" # translation_missing event_in_the_past: "Unable to create a slot in the past." # translation_missing + edit_event: "Edit the event" # translation_missing + view_reservations: "Ver reservas" # translation_missing project_elements: # management of the projects' components diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 806c6905b..32b548742 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -18,6 +18,7 @@ fr: trainings: "Formations" machines: "Machines" spaces: "Espaces" + events: "Évènements" availabilities: "Disponibilités" availabilities_notice: "Exporter dans un classeur Excel tous les créneaux ouverts à la réservation et leurs taux d'occupation." ongoing_reservations: "Réservations en cours" @@ -83,6 +84,8 @@ fr: delete_this_and_next: "Ce créneau et tous les suivants" delete_all: "Tous les créneaux" event_in_the_past: "Impossible de créer un créneau dans le passé." + edit_event: "Modifier l'évènement" + view_reservations: "Voir les réservations" project_elements: # gestion des éléments constituant les projets diff --git a/config/locales/app.admin.pt.yml b/config/locales/app.admin.pt.yml index e91ccdbe1..ca8bdb488 100755 --- a/config/locales/app.admin.pt.yml +++ b/config/locales/app.admin.pt.yml @@ -18,6 +18,7 @@ pt: trainings: "Treinamentos" machines: "Máquinas" spaces: "Espaços" + events: "Eventos" availabilities: "Disponíveis" availabilities_notice: "Exportar para Excel livro com todos os slots disponíveis para reserva, e suas ocupações." ongoing_reservations: "Reservas em curso" @@ -83,6 +84,8 @@ pt: delete_this_and_next: "This slot and the following" # translation_missing delete_all: "All slots" # translation_missing event_in_the_past: "Unable to create a slot in the past." # translation_missing + edit_event: "Edit the event" # translation_missing + view_reservations: "Ver reservas" # translation_missing project_elements: # management of the projects' components diff --git a/config/secrets.yml b/config/secrets.yml index e277437a1..0f79cec6c 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -21,6 +21,7 @@ development: fablab_without_online_payments: <%= ENV["FABLAB_WITHOUT_ONLINE_PAYMENT"] %> fablab_without_invoices: <%= ENV["FABLAB_WITHOUT_INVOICES"] %> phone_required: <%= ENV["PHONE_REQUIRED"] %> + events_in_calendar: <%= ENV["EVENTS_IN_CALENDAR"] %> slot_duration: <%= ENV["SLOT_DURATION"] %> default_host: <%= ENV["DEFAULT_HOST"] %> default_protocol: <%= ENV["DEFAULT_PROTOCOL"] %> @@ -65,7 +66,8 @@ test: fablab_without_online_payments: false fablab_without_invoices: false phone_required: true - slot_duration: <%= ENV["SLOT_DURATION"] %> + events_in_calendar: false + slot_duration: 60 default_host: <%= ENV["DEFAULT_HOST"] %> default_protocol: <%= ENV["DEFAULT_PROTOCOL"] %> time_zone: Paris @@ -109,6 +111,7 @@ staging: fablab_without_online_payments: <%= ENV["FABLAB_WITHOUT_ONLINE_PAYMENT"] %> fablab_without_invoices: <%= ENV["FABLAB_WITHOUT_INVOICES"] %> phone_required: <%= ENV["PHONE_REQUIRED"] %> + events_in_calendar: <%= ENV["EVENTS_IN_CALENDAR"] %> slot_duration: <%= ENV["SLOT_DURATION"] %> default_host: <%= ENV["DEFAULT_HOST"] %> default_protocol: <%= ENV["DEFAULT_PROTOCOL"] %> @@ -164,6 +167,7 @@ production: fablab_without_online_payments: <%= ENV["FABLAB_WITHOUT_ONLINE_PAYMENT"] %> fablab_without_invoices: <%= ENV["FABLAB_WITHOUT_INVOICES"] %> phone_required: <%= ENV["PHONE_REQUIRED"] %> + events_in_calendar: <%= ENV["EVENTS_IN_CALENDAR"] %> slot_duration: <%= ENV["SLOT_DURATION"] %> default_host: <%= ENV["DEFAULT_HOST"] %> default_protocol: <%= ENV["DEFAULT_PROTOCOL"] %> diff --git a/doc/environment.md b/doc/environment.md index b0bee2b16..a3c1fd0ca 100644 --- a/doc/environment.md +++ b/doc/environment.md @@ -107,6 +107,11 @@ This is useful if you have your own invoicing system and you want to prevent Fab PHONE_REQUIRED If set to 'false' the phone number won't be required to register a new user on the software. + + + EVENTS_IN_CALENDAR + +If set to 'true', the admin calendar will display the scheduled events in the current view, as read-only items. SLOT_DURATION diff --git a/docker/env.example b/docker/env.example index 2f3f1d014..bb27bd84d 100644 --- a/docker/env.example +++ b/docker/env.example @@ -15,6 +15,7 @@ FABLAB_WITHOUT_ONLINE_PAYMENT=true FABLAB_WITHOUT_INVOICES=false PHONE_REQUIRED=false +EVENTS_IN_CALENDAR=false SLOT_DURATION=60 DEFAULT_MAIL_FROM=Fab Manager Demo From 48b811d2b58c4d5e2929882af2fb241f31935a58 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 25 Nov 2019 16:12:23 +0100 Subject: [PATCH 035/135] Added freeCAD files as default allowed extensions also: refactored some ruby code --- CHANGELOG.md | 1 + app/models/project_image.rb | 5 ++++ app/models/project_step_image.rb | 5 ++++ app/uploaders/project_image_uploader.rb | 36 +++++-------------------- config/application.yml.default | 2 +- docker/env.example | 2 +- 6 files changed, 20 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbb4d213d..faa2b1991 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Calendar management: improved legend style - Handle Ctrl^C in upgrade scripts - Updated moment-timezone +- Added freeCAD files as default allowed extensions - Fix a bug: unable to remove the picture from a training - Fix a security issue: updated loofah to fix [CVE-2019-15587](https://github.com/advisories/GHSA-c3gv-9cxf-6f57) - Fix a security issue: updated angular to 1.7.9 to fix [CVE-2019-10768](https://github.com/advisories/GHSA-89mq-4x47-5v83) diff --git a/app/models/project_image.rb b/app/models/project_image.rb index 7f69a93e0..f9d548e2c 100644 --- a/app/models/project_image.rb +++ b/app/models/project_image.rb @@ -1,4 +1,9 @@ +# frozen_string_literal: true + +# Main image for project documentation class ProjectImage < Asset include ImageValidatorConcern mount_uploader :attachment, ProjectImageUploader + + validates :attachment, file_mime_type: { content_type: /image/ } end diff --git a/app/models/project_step_image.rb b/app/models/project_step_image.rb index 3caabb3c5..b1bb4ea4b 100644 --- a/app/models/project_step_image.rb +++ b/app/models/project_step_image.rb @@ -1,4 +1,9 @@ +# frozen_string_literal: true + +# Images for the documentation of a project step class ProjectStepImage < Asset include ImageValidatorConcern mount_uploader :attachment, ProjectImageUploader + + validates :attachment, file_mime_type: { content_type: /image/ } end diff --git a/app/uploaders/project_image_uploader.rb b/app/uploaders/project_image_uploader.rb index 30eac1a35..729ebe9a9 100644 --- a/app/uploaders/project_image_uploader.rb +++ b/app/uploaders/project_image_uploader.rb @@ -1,16 +1,14 @@ +# frozen_string_literal: true + +# CarrierWave uploader for project image. +# This file defines the parameters for these uploads. class ProjectImageUploader < CarrierWave::Uploader::Base - # Include RMagick or MiniMagick support: - # include CarrierWave::RMagick include CarrierWave::MiniMagick include UploadHelper # Choose what kind of storage to use for this uploader: storage :file after :remove, :delete_empty_dirs - # storage :fog - - # Override the directory where uploaded files will be stored. - # This is a sensible default for uploaders that are meant to be mounted: def store_dir "#{base_store_dir}/#{model.id}" @@ -20,38 +18,18 @@ class ProjectImageUploader < CarrierWave::Uploader::Base "uploads/#{model.class.to_s.underscore}" end - # Provide a default URL as a default if there hasn't been a file uploaded: - # def default_url - # # For Rails 3.1+ asset pipeline compatibility: - # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_')) - # - # "/images/fallback/" + [version_name, "default.png"].compact.join('_') - # end - - # Process files as they are uploaded: - # process :scale => [200, 300] - # - # def scale(width, height) - # # do something - # end - - # Create different versions of your uploaded files: - # version :normal do - # process :resize_to_fit => [312, 270] - # end - version :large do - process :resize_to_fit => [1000, 700] + process resize_to_fit: [1000, 700] end version :medium do - process :resize_to_fit => [700, 400] + process resize_to_fit: [700, 400] end # Add a white list of extensions which are allowed to be uploaded. # For images you might use something like this: def extension_white_list - %w(jpg jpeg gif png) + %w[jpg jpeg gif png] end # Override the filename of the uploaded files: diff --git a/config/application.yml.default b/config/application.yml.default index c53e53def..8327dc659 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -91,7 +91,7 @@ LOG_LEVEL: 'debug' DISK_SPACE_MB_ALERT: '100' SUPERADMIN_EMAIL: 'admin@sleede.com' -ALLOWED_EXTENSIONS: pdf ai eps cad math svg stl dxf dwg obj step iges igs 3dm 3dmf doc docx png ino scad fcad skp sldprt sldasm slddrw slddrt tex latex ps +ALLOWED_EXTENSIONS: pdf ai eps cad math svg stl dxf dwg obj step iges igs 3dm 3dmf doc docx png ino scad fcad skp sldprt sldasm slddrw slddrt tex latex ps fcstd fcstd1 ALLOWED_MIME_TYPES: application/pdf application/postscript application/illustrator image/x-eps image/svg+xml application/sla application/dxf application/acad application/dwg application/octet-stream application/step application/iges model/iges x-world/x-3dmf application/vnd.openxmlformats-officedocument.wordprocessingml.document image/png text/x-arduino text/plain application/scad application/vnd.sketchup.skp application/x-koan application/vnd-koan koan/x-skm application/vnd.koan application/x-tex application/x-latex # 5242880 = 5 megabytes diff --git a/docker/env.example b/docker/env.example index bb27bd84d..0717f6fed 100644 --- a/docker/env.example +++ b/docker/env.example @@ -76,7 +76,7 @@ DISK_SPACE_MB_ALERT='100' SUPERADMIN_EMAIL='admin@sleede.com' -ALLOWED_EXTENSIONS=pdf ai eps cad math svg stl dxf dwg obj step iges igs 3dm 3dmf doc docx png ino scad fcad skp sldprt sldasm slddrw slddrt tex latex ps +ALLOWED_EXTENSIONS=pdf ai eps cad math svg stl dxf dwg obj step iges igs 3dm 3dmf doc docx png ino scad fcad skp sldprt sldasm slddrw slddrt tex latex ps fcstd fcstd1 ALLOWED_MIME_TYPES=application/pdf application/postscript application/illustrator image/x-eps image/svg+xml application/sla application/dxf application/acad application/dwg application/octet-stream application/step application/iges model/iges x-world/x-3dmf application/ application/vnd.openxmlformats-officedocument.wordprocessingml.document image/png text/x-arduino text/plain application/scad application/vnd.sketchup.skp application/x-koan application/vnd-koan koan/x-skm application/vnd.koan application/x-tex application/x-latex # 5242880 = 5 megabytes From eb3c78a61d449718e2702ab7a48ea8bc3577c64e Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 26 Nov 2019 13:44:43 +0100 Subject: [PATCH 036/135] [poc] show google agenda events in the public calendar --- Gemfile | 2 ++ Gemfile.lock | 4 ++++ .../javascripts/controllers/calendar.js | 7 +++++-- app/assets/javascripts/router.js.erb | 1 + app/assets/javascripts/services/ical.js | 5 +++++ app/controllers/api/ical_controller.rb | 19 +++++++++++++++++++ app/views/api/ical/externals.json.jbuilder | 8 ++++++++ config/routes.rb | 2 ++ 8 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/services/ical.js create mode 100644 app/controllers/api/ical_controller.rb create mode 100644 app/views/api/ical/externals.json.jbuilder diff --git a/Gemfile b/Gemfile index b69b1c8f9..af5fd4003 100644 --- a/Gemfile +++ b/Gemfile @@ -152,3 +152,5 @@ gem 'sys-filesystem' gem 'sha3' gem 'repost' + +gem 'icalendar' diff --git a/Gemfile.lock b/Gemfile.lock index eec86b501..db0e0d4e7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -191,6 +191,9 @@ GEM multi_xml (>= 0.5.2) i18n (0.9.5) concurrent-ruby (~> 1.0) + icalendar (2.5.3) + ice_cube (~> 0.16) + ice_cube (0.16.3) ice_nine (0.11.2) jaro_winkler (1.5.1) jbuilder (2.5.0) @@ -497,6 +500,7 @@ DEPENDENCIES forgery friendly_id (~> 5.1.0) has_secure_token + icalendar jbuilder (~> 2.5) jbuilder_cache_multi jquery-rails diff --git a/app/assets/javascripts/controllers/calendar.js b/app/assets/javascripts/controllers/calendar.js index 0af678931..14515614c 100644 --- a/app/assets/javascripts/controllers/calendar.js +++ b/app/assets/javascripts/controllers/calendar.js @@ -16,8 +16,8 @@ * Controller used in the public calendar global */ -Application.Controllers.controller('CalendarController', ['$scope', '$state', '$aside', 'moment', 'Availability', 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise', 'spacesPromise', - function ($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise, spacesPromise) { +Application.Controllers.controller('CalendarController', ['$scope', '$state', '$aside', 'moment', 'Availability', 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise', 'spacesPromise', 'externalsPromise', + function ($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise, spacesPromise, externalsPromise) { /* PRIVATE STATIC CONSTANTS */ let currentMachineEvent = null; machinesPromise.forEach(m => m.checked = true); @@ -38,6 +38,9 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ // List of spaces $scope.spaces = spacesPromise.filter(t => !t.disabled); + // External ICS calendars + $scope.externals = externalsPromise; + // add availabilities source to event sources $scope.eventSources = []; diff --git a/app/assets/javascripts/router.js.erb b/app/assets/javascripts/router.js.erb index 12cad9d20..4b020ab3d 100644 --- a/app/assets/javascripts/router.js.erb +++ b/app/assets/javascripts/router.js.erb @@ -638,6 +638,7 @@ angular.module('application.router', ['ui.router']) trainingsPromise: ['Training', function (Training) { return Training.query().$promise; }], machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }], spacesPromise: ['Space', function (Space) { return Space.query().$promise; }], + externalsPromise: ['Ical', function (Ical) { return Ical.get().$promise; }], translations: ['Translations', function (Translations) { return Translations.query(['app.public.calendar']).$promise; }] } }) diff --git a/app/assets/javascripts/services/ical.js b/app/assets/javascripts/services/ical.js new file mode 100644 index 000000000..dec1ed635 --- /dev/null +++ b/app/assets/javascripts/services/ical.js @@ -0,0 +1,5 @@ +'use strict'; + +Application.Services.factory('Ical', ['$resource', function ($resource) { + return $resource('/api/ical/externals'); +}]); diff --git a/app/controllers/api/ical_controller.rb b/app/controllers/api/ical_controller.rb new file mode 100644 index 000000000..dc3307d85 --- /dev/null +++ b/app/controllers/api/ical_controller.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# API Controller for resources of type iCalendar +class API::IcalController < API::ApiController + respond_to :json + + def externals + require 'net/http' + require 'uri' + + ics = Net::HTTP.get(URI.parse('https://calendar.google.com/calendar/ical/sylvain%40sleede.com/public/basic.ics')) + + require 'icalendar' + require 'icalendar/tzinfo' + + cals = Icalendar::Calendar.parse(ics) + @events = cals.first.events + end +end \ No newline at end of file diff --git a/app/views/api/ical/externals.json.jbuilder b/app/views/api/ical/externals.json.jbuilder new file mode 100644 index 000000000..faedb232b --- /dev/null +++ b/app/views/api/ical/externals.json.jbuilder @@ -0,0 +1,8 @@ +json.array!(@events) do |event| + json.id event.uid + json.title event.summary + json.start event.dtstart.iso8601 + json.end event.dtend.iso8601 + json.backgroundColor 'white' + json.borderColor '#214712' +end diff --git a/config/routes.rb b/config/routes.rb index 0609afaeb..9e0b5e095 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -112,6 +112,8 @@ Rails.application.routes.draw do get 'first', action: 'first', on: :collection end + get 'ical/externals' => 'ical#externals' + # for admin resources :trainings do get :availabilities, on: :member From 91008b3df1723b3715c2d7882ab173d95f7929f2 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 27 Nov 2019 11:52:37 +0100 Subject: [PATCH 037/135] improved calendar legend --- app/assets/javascripts/app.js | 2 ++ app/assets/stylesheets/app.components.scss | 27 ++++++++++++++----- app/assets/stylesheets/app.layout.scss | 18 +------------ .../admin/calendar/calendar.html.erb | 22 ++++++++------- app/views/application/index.html.erb | 1 + 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 | 1 + 9 files changed, 40 insertions(+), 34 deletions(-) diff --git a/app/assets/javascripts/app.js b/app/assets/javascripts/app.js index ea94b5a12..f932fd84f 100644 --- a/app/assets/javascripts/app.js +++ b/app/assets/javascripts/app.js @@ -88,6 +88,8 @@ angular.module('application', ['ngCookies', 'ngResource', 'ngSanitize', 'ui.rout $rootScope.fablabWithoutInvoices = Fablab.withoutInvoices; // Global config: if true, the phone number is required to create an account $rootScope.phoneRequired = Fablab.phoneRequired; + // Global config: if true, the events are shown in the admin calendar + $rootScope.eventsInCalendar = Fablab.eventsInCalendar; // Global function to allow the user to navigate to the previous screen (ie. $state). // If no previous $state were recorded, navigate to the home page diff --git a/app/assets/stylesheets/app.components.scss b/app/assets/stylesheets/app.components.scss index 4de6ebbc3..6a1659507 100644 --- a/app/assets/stylesheets/app.components.scss +++ b/app/assets/stylesheets/app.components.scss @@ -651,11 +651,24 @@ padding: 10px; text-transform: uppercase; } } +.calendar-legend-block { + text-align: right; + padding-right: 2em; -.calendar-legend { - border: 1px solid; - border-left: 3px solid; - border-radius: 3px; - font-size: 10px; - padding: 2px; -} \ No newline at end of file + h4 { + font-size: 12px; + font-style: italic; + } + .legends { + display: flex; + flex-direction: row-reverse; + } + .calendar-legend { + border: 1px solid; + border-left: 3px solid; + border-radius: 3px; + font-size: 10px; + padding: 2px; + margin-left: 10px; + } +} diff --git a/app/assets/stylesheets/app.layout.scss b/app/assets/stylesheets/app.layout.scss index 17aadf990..1c257746b 100644 --- a/app/assets/stylesheets/app.layout.scss +++ b/app/assets/stylesheets/app.layout.scss @@ -101,22 +101,6 @@ } } -.display-h { - display: flex; - flex-direction: row; - justify-content: space-evenly; - padding: 0; -} - -.display-v { - display: flex; - flex-direction: column; - justify-content: space-around; - align-self: baseline; - height: inherit; -} - - body.container{ padding: 0; } @@ -651,4 +635,4 @@ body.container{ position: absolute; left: -4px; } -} \ No newline at end of file +} diff --git a/app/assets/templates/admin/calendar/calendar.html.erb b/app/assets/templates/admin/calendar/calendar.html.erb index 42cbe4ea1..21366925d 100644 --- a/app/assets/templates/admin/calendar/calendar.html.erb +++ b/app/assets/templates/admin/calendar/calendar.html.erb @@ -12,15 +12,8 @@
    -
    -
    - {{ 'admin_calendar.trainings' }}
    - {{ 'admin_calendar.machines' }}
    -
    -
    - {{ 'admin_calendar.spaces' }} - {{ 'admin_calendar.events' }} -
    +
    +
    @@ -32,6 +25,15 @@
    +
    +

    {{ 'admin_calendar.legend' }}

    +
    + {{ 'admin_calendar.trainings' }}
    + {{ 'admin_calendar.machines' }}
    + {{ 'admin_calendar.spaces' }} + {{ 'admin_calendar.events' }} +
    +
    @@ -119,4 +121,4 @@
    -
    \ No newline at end of file + diff --git a/app/views/application/index.html.erb b/app/views/application/index.html.erb index 65efb7dd1..b8885143e 100644 --- a/app/views/application/index.html.erb +++ b/app/views/application/index.html.erb @@ -21,6 +21,7 @@ Fablab.withoutOnlinePayment = ('<%= Rails.application.secrets.fablab_without_online_payments %>' === 'true'); Fablab.withoutInvoices = ('<%= Rails.application.secrets.fablab_without_invoices %>' === 'true'); Fablab.phoneRequired = ('<%= Rails.application.secrets.phone_required %>' === 'true'); + Fablab.eventsInCalendar = ('<%= Rails.application.secrets.events_in_calendar %>' === 'true'); Fablab.slotDuration = parseInt("<%= ApplicationHelper::SLOT_DURATION %>", 10); Fablab.disqusShortname = "<%= Rails.application.secrets.disqus_shortname %>"; Fablab.defaultHost = "<%= Rails.application.secrets.default_host %>"; diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index c6c0ee47a..0b1aaa437 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -86,6 +86,7 @@ en: event_in_the_past: "Unable to create a slot in the past." edit_event: "Edit the event" view_reservations: "View reservations" + legend: "legend" project_elements: # management of the projects' components diff --git a/config/locales/app.admin.es.yml b/config/locales/app.admin.es.yml index d7b1beb2b..2f329ba4b 100644 --- a/config/locales/app.admin.es.yml +++ b/config/locales/app.admin.es.yml @@ -86,6 +86,7 @@ es: event_in_the_past: "Unable to create a slot in the past." # translation_missing edit_event: "Edit the event" # translation_missing view_reservations: "Ver reservas" # translation_missing + legend: "leyenda" project_elements: # management of the projects' components diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 32b548742..c55d89754 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -86,6 +86,7 @@ fr: event_in_the_past: "Impossible de créer un créneau dans le passé." edit_event: "Modifier l'évènement" view_reservations: "Voir les réservations" + legend: "Légende" project_elements: # gestion des éléments constituant les projets diff --git a/config/locales/app.admin.pt.yml b/config/locales/app.admin.pt.yml index ca8bdb488..487a45cf2 100755 --- a/config/locales/app.admin.pt.yml +++ b/config/locales/app.admin.pt.yml @@ -86,6 +86,7 @@ pt: event_in_the_past: "Unable to create a slot in the past." # translation_missing edit_event: "Edit the event" # translation_missing view_reservations: "Ver reservas" # translation_missing + legend: "legenda" project_elements: # management of the projects' components From 90b35641385e878b8c5da746d378252ceb10aa53 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 27 Nov 2019 12:31:32 +0100 Subject: [PATCH 038/135] admin calendar: surround select item by shadow-box --- CHANGELOG.md | 2 +- .../controllers/admin/calendar.js.erb | 32 ++++++++++++++++--- app/assets/stylesheets/app.plugins.scss | 6 +++- .../admin/calendar/calendar.html.erb | 2 +- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index faa2b1991..ccd19e3a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ - Configuration of phone number in members registration forms: can be required or optional, depending on `PHONE_REQUIRED` configuration - Improved user experience in defining slots in the calendar management - Improved notification email to the member when a rolling subscription is taken -- Calendar management: improved legend style +- Calendar management: improved legend display and visual behavior - Handle Ctrl^C in upgrade scripts - Updated moment-timezone - Added freeCAD files as default allowed extensions diff --git a/app/assets/javascripts/controllers/admin/calendar.js.erb b/app/assets/javascripts/controllers/admin/calendar.js.erb index a4604246d..5b7979642 100644 --- a/app/assets/javascripts/controllers/admin/calendar.js.erb +++ b/app/assets/javascripts/controllers/admin/calendar.js.erb @@ -23,7 +23,6 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state /* PRIVATE STATIC CONSTANTS */ // The calendar is divided in slots of 30 minutes - let loadingCb; const BASE_SLOT = '00:30:00'; // The bookings can be positioned every half hours @@ -40,6 +39,9 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state // currently selected availability $scope.availability = null; + // corresponding fullCalendar item in the DOM + $scope.availabilityDom = null; + // bind the availabilities slots with full-Calendar events $scope.eventSources = []; $scope.eventSources.push({ @@ -62,7 +64,10 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state return calendarEventClickCb(event, jsEvent, view); }, eventRender (event, element, view) { - return eventRenderCb(event, element); + return eventRenderCb(event, element, view); + }, + viewRender(view, element) { + return viewRenderCb(view, element); }, loading (isLoading, view) { return loadingCb(isLoading, view); @@ -328,6 +333,12 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state var calendarEventClickCb = function (event, jsEvent, view) { $scope.availability = event; + if ($scope.availabilityDom) { + $scope.availabilityDom.classList.remove("fc-selected") + } + $scope.availabilityDom = jsEvent.target.closest('.fc-event'); + $scope.availabilityDom.classList.add("fc-selected") + // if the user has clicked on the delete event button, delete the event if ($(jsEvent.target).hasClass('remove-event')) { return $scope.removeSlot(); @@ -360,12 +371,23 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state * Triggered when resource fetching starts/stops. * @see https://fullcalendar.io/docs/resource_data/loading/ */ - return loadingCb = function (isLoading, view) { + const loadingCb = function (isLoading, view) { if (isLoading) { - // we remove existing events when fetching starts to prevent duplicates - return uiCalendarConfig.calendars.calendar.fullCalendar('removeEvents'); + // we remove existing events when fetching starts to prevent duplicates + uiCalendarConfig.calendars.calendar.fullCalendar('removeEvents'); + } }; + + /** + * Triggered when the view is changed + * @see https://fullcalendar.io/docs/v3/viewRender#v2 + */ + const viewRenderCb = function(view, element) { + // we unselect the current event to keep consistency + $scope.availability = null; + $scope.availabilityDom = null; + }; } ]); diff --git a/app/assets/stylesheets/app.plugins.scss b/app/assets/stylesheets/app.plugins.scss index af46e9830..e1e6360c2 100644 --- a/app/assets/stylesheets/app.plugins.scss +++ b/app/assets/stylesheets/app.plugins.scss @@ -1,4 +1,4 @@ -// medium editor placeholder +// medium editor placeholder .medium-editor-placeholder { min-height: 30px; // fix for firefox } @@ -126,6 +126,10 @@ } } +.fc-selected { + box-shadow: 0 6px 10px 0 rgba(0,0,0,0.14),0 1px 18px 0 rgba(0,0,0,0.12),0 3px 5px -1px rgba(0,0,0,0.2); +} + diff --git a/app/assets/templates/admin/calendar/calendar.html.erb b/app/assets/templates/admin/calendar/calendar.html.erb index 21366925d..f1666a628 100644 --- a/app/assets/templates/admin/calendar/calendar.html.erb +++ b/app/assets/templates/admin/calendar/calendar.html.erb @@ -105,7 +105,7 @@
    - + {{ 'admin_calendar.edit_event' }} From a9b1eabb2cb0585f184779b038d8a004746bd401 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 26 Nov 2019 13:44:43 +0100 Subject: [PATCH 039/135] [poc] show google agenda events in the public calendar --- Gemfile | 2 ++ Gemfile.lock | 4 ++++ .../javascripts/controllers/calendar.js | 7 +++++-- app/assets/javascripts/router.js.erb | 1 + app/assets/javascripts/services/ical.js | 5 +++++ app/controllers/api/ical_controller.rb | 19 +++++++++++++++++++ app/views/api/ical/externals.json.jbuilder | 8 ++++++++ config/routes.rb | 2 ++ 8 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/services/ical.js create mode 100644 app/controllers/api/ical_controller.rb create mode 100644 app/views/api/ical/externals.json.jbuilder diff --git a/Gemfile b/Gemfile index b69b1c8f9..af5fd4003 100644 --- a/Gemfile +++ b/Gemfile @@ -152,3 +152,5 @@ gem 'sys-filesystem' gem 'sha3' gem 'repost' + +gem 'icalendar' diff --git a/Gemfile.lock b/Gemfile.lock index eec86b501..db0e0d4e7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -191,6 +191,9 @@ GEM multi_xml (>= 0.5.2) i18n (0.9.5) concurrent-ruby (~> 1.0) + icalendar (2.5.3) + ice_cube (~> 0.16) + ice_cube (0.16.3) ice_nine (0.11.2) jaro_winkler (1.5.1) jbuilder (2.5.0) @@ -497,6 +500,7 @@ DEPENDENCIES forgery friendly_id (~> 5.1.0) has_secure_token + icalendar jbuilder (~> 2.5) jbuilder_cache_multi jquery-rails diff --git a/app/assets/javascripts/controllers/calendar.js b/app/assets/javascripts/controllers/calendar.js index 0af678931..14515614c 100644 --- a/app/assets/javascripts/controllers/calendar.js +++ b/app/assets/javascripts/controllers/calendar.js @@ -16,8 +16,8 @@ * Controller used in the public calendar global */ -Application.Controllers.controller('CalendarController', ['$scope', '$state', '$aside', 'moment', 'Availability', 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise', 'spacesPromise', - function ($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise, spacesPromise) { +Application.Controllers.controller('CalendarController', ['$scope', '$state', '$aside', 'moment', 'Availability', 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise', 'spacesPromise', 'externalsPromise', + function ($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise, spacesPromise, externalsPromise) { /* PRIVATE STATIC CONSTANTS */ let currentMachineEvent = null; machinesPromise.forEach(m => m.checked = true); @@ -38,6 +38,9 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ // List of spaces $scope.spaces = spacesPromise.filter(t => !t.disabled); + // External ICS calendars + $scope.externals = externalsPromise; + // add availabilities source to event sources $scope.eventSources = []; diff --git a/app/assets/javascripts/router.js.erb b/app/assets/javascripts/router.js.erb index 12cad9d20..4b020ab3d 100644 --- a/app/assets/javascripts/router.js.erb +++ b/app/assets/javascripts/router.js.erb @@ -638,6 +638,7 @@ angular.module('application.router', ['ui.router']) trainingsPromise: ['Training', function (Training) { return Training.query().$promise; }], machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }], spacesPromise: ['Space', function (Space) { return Space.query().$promise; }], + externalsPromise: ['Ical', function (Ical) { return Ical.get().$promise; }], translations: ['Translations', function (Translations) { return Translations.query(['app.public.calendar']).$promise; }] } }) diff --git a/app/assets/javascripts/services/ical.js b/app/assets/javascripts/services/ical.js new file mode 100644 index 000000000..dec1ed635 --- /dev/null +++ b/app/assets/javascripts/services/ical.js @@ -0,0 +1,5 @@ +'use strict'; + +Application.Services.factory('Ical', ['$resource', function ($resource) { + return $resource('/api/ical/externals'); +}]); diff --git a/app/controllers/api/ical_controller.rb b/app/controllers/api/ical_controller.rb new file mode 100644 index 000000000..dc3307d85 --- /dev/null +++ b/app/controllers/api/ical_controller.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# API Controller for resources of type iCalendar +class API::IcalController < API::ApiController + respond_to :json + + def externals + require 'net/http' + require 'uri' + + ics = Net::HTTP.get(URI.parse('https://calendar.google.com/calendar/ical/sylvain%40sleede.com/public/basic.ics')) + + require 'icalendar' + require 'icalendar/tzinfo' + + cals = Icalendar::Calendar.parse(ics) + @events = cals.first.events + end +end \ No newline at end of file diff --git a/app/views/api/ical/externals.json.jbuilder b/app/views/api/ical/externals.json.jbuilder new file mode 100644 index 000000000..faedb232b --- /dev/null +++ b/app/views/api/ical/externals.json.jbuilder @@ -0,0 +1,8 @@ +json.array!(@events) do |event| + json.id event.uid + json.title event.summary + json.start event.dtstart.iso8601 + json.end event.dtend.iso8601 + json.backgroundColor 'white' + json.borderColor '#214712' +end diff --git a/config/routes.rb b/config/routes.rb index 0609afaeb..9e0b5e095 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -112,6 +112,8 @@ Rails.application.routes.draw do get 'first', action: 'first', on: :collection end + get 'ical/externals' => 'ical#externals' + # for admin resources :trainings do get :availabilities, on: :member From bb777227d6a79ce8a26733859cc82675ad7639d7 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 27 Nov 2019 13:59:56 +0100 Subject: [PATCH 040/135] display events of external ics in calendar --- app/assets/javascripts/controllers/calendar.js | 11 ++++++----- app/assets/javascripts/router.js.erb | 1 - app/assets/templates/calendar/calendar.html.erb | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/controllers/calendar.js b/app/assets/javascripts/controllers/calendar.js index 14515614c..43f0542a3 100644 --- a/app/assets/javascripts/controllers/calendar.js +++ b/app/assets/javascripts/controllers/calendar.js @@ -16,8 +16,8 @@ * Controller used in the public calendar global */ -Application.Controllers.controller('CalendarController', ['$scope', '$state', '$aside', 'moment', 'Availability', 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise', 'spacesPromise', 'externalsPromise', - function ($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise, spacesPromise, externalsPromise) { +Application.Controllers.controller('CalendarController', ['$scope', '$state', '$aside', 'moment', 'Availability', 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise', 'spacesPromise', + function ($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise, spacesPromise) { /* PRIVATE STATIC CONSTANTS */ let currentMachineEvent = null; machinesPromise.forEach(m => m.checked = true); @@ -38,11 +38,12 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ // List of spaces $scope.spaces = spacesPromise.filter(t => !t.disabled); - // External ICS calendars - $scope.externals = externalsPromise; - // add availabilities source to event sources $scope.eventSources = []; + $scope.eventSources.push({ + url: '/api/ical/externals', + textColor: 'black' + }); // filter availabilities if have change $scope.filterAvailabilities = function (filter, scope) { diff --git a/app/assets/javascripts/router.js.erb b/app/assets/javascripts/router.js.erb index 4b020ab3d..12cad9d20 100644 --- a/app/assets/javascripts/router.js.erb +++ b/app/assets/javascripts/router.js.erb @@ -638,7 +638,6 @@ angular.module('application.router', ['ui.router']) trainingsPromise: ['Training', function (Training) { return Training.query().$promise; }], machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }], spacesPromise: ['Space', function (Space) { return Space.query().$promise; }], - externalsPromise: ['Ical', function (Ical) { return Ical.get().$promise; }], translations: ['Translations', function (Translations) { return Translations.query(['app.public.calendar']).$promise; }] } }) diff --git a/app/assets/templates/calendar/calendar.html.erb b/app/assets/templates/calendar/calendar.html.erb index bc3c16530..fc2a5c8ae 100644 --- a/app/assets/templates/calendar/calendar.html.erb +++ b/app/assets/templates/calendar/calendar.html.erb @@ -41,7 +41,7 @@

    {{ 'calendar.filter_calendar' }}

    - + '">
    @@ -56,7 +56,7 @@

    {{ 'calendar.filter_calendar' }}

    From 9e2134c9cfc8d2ee8ce57e552a8a4f5fc535b7b4 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 27 Nov 2019 16:17:32 +0100 Subject: [PATCH 041/135] ics sources configuration interface --- .../controllers/admin/calendar.js.erb | 54 ++++++++++++ .../javascripts/controllers/calendar.js | 2 +- app/assets/javascripts/router.js.erb | 12 +++ app/assets/stylesheets/app.components.scss | 1 + app/assets/stylesheets/app.utilities.scss | 1 + app/assets/stylesheets/application.scss.erb | 6 +- app/assets/stylesheets/modules/icalendar.scss | 10 +++ .../admin/calendar/calendar.html.erb | 12 +-- .../templates/admin/calendar/icalendar.html | 87 +++++++++++++++++++ .../templates/admin/settings/general.html | 4 +- config/locales/app.admin.fr.yml | 14 +++ 11 files changed, 191 insertions(+), 12 deletions(-) create mode 100644 app/assets/stylesheets/modules/icalendar.scss create mode 100644 app/assets/templates/admin/calendar/icalendar.html diff --git a/app/assets/javascripts/controllers/admin/calendar.js.erb b/app/assets/javascripts/controllers/admin/calendar.js.erb index 5b7979642..569d7a35d 100644 --- a/app/assets/javascripts/controllers/admin/calendar.js.erb +++ b/app/assets/javascripts/controllers/admin/calendar.js.erb @@ -755,3 +755,57 @@ Application.Controllers.controller('DeleteRecurrentAvailabilityController', ['$s } } ]); + + + +/** + * Controller used in the iCalendar (ICS) imports management page + */ + +Application.Controllers.controller('AdminICalendarController', ['$scope', + function ($scope) { + // list of ICS sources + $scope.calendars = []; + + // configuration of a new ICS source + $scope.newCalendar = { + color: undefined, + textColor: undefined, + url: undefined, + textHidden: false + }; + + /** + * Save the new iCalendar in database + */ + $scope.save = function () { + $scope.calendars.push(Object.assign({}, $scope.newCalendar)); + $scope.newCalendar.url = undefined; + $scope.newCalendar.color = null; + $scope.newCalendar.textColor = null; + $scope.newCalendar.textHidden = false; + } + + /** + * Return a CSS-like style of the given calendar configuration + * @param calendar + */ + $scope.calendarStyle = function (calendar) { + return { + 'border-color': calendar.color, + 'color': calendar.textColor, + 'width': calendar.textHidden ? '50px' : 'auto', + 'height': calendar.textHidden ? '21px' : 'auto' + }; + } + + /** + * Delete the given calendar from the database + * @param calendar + */ + $scope.delete = function (calendar) { + const idx = $scope.calendars.indexOf(calendar); + $scope.calendars.splice(idx, 1); + } + } +]); diff --git a/app/assets/javascripts/controllers/calendar.js b/app/assets/javascripts/controllers/calendar.js index 43f0542a3..e46eb8db1 100644 --- a/app/assets/javascripts/controllers/calendar.js +++ b/app/assets/javascripts/controllers/calendar.js @@ -170,7 +170,7 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ }; const eventRenderCb = function (event, element) { - if (event.tags.length > 0) { + if (event.tags && event.tags.length > 0) { let html = ''; for (let tag of Array.from(event.tags)) { html += `${tag.name} `; diff --git a/app/assets/javascripts/router.js.erb b/app/assets/javascripts/router.js.erb index 12cad9d20..f0476c79c 100644 --- a/app/assets/javascripts/router.js.erb +++ b/app/assets/javascripts/router.js.erb @@ -659,6 +659,18 @@ angular.module('application.router', ['ui.router']) translations: ['Translations', function (Translations) { return Translations.query('app.admin.calendar').$promise; }] } }) + .state('app.admin.calendar.icalendar', { + url: '/admin/calendar/icalendar', + views: { + 'main@': { + templateUrl: '<%= asset_path "admin/calendar/icalendar.html" %>', + controller: 'AdminICalendarController' + } + }, + resolve: { + translations: ['Translations', function (Translations) { return Translations.query('app.admin.icalendar').$promise; }] + } + }) // project's elements .state('app.admin.project_elements', { diff --git a/app/assets/stylesheets/app.components.scss b/app/assets/stylesheets/app.components.scss index 6a1659507..e550aa8ea 100644 --- a/app/assets/stylesheets/app.components.scss +++ b/app/assets/stylesheets/app.components.scss @@ -670,5 +670,6 @@ padding: 10px; font-size: 10px; padding: 2px; margin-left: 10px; + display: inline-block; } } diff --git a/app/assets/stylesheets/app.utilities.scss b/app/assets/stylesheets/app.utilities.scss index 7b6349c1d..ad1d4241c 100644 --- a/app/assets/stylesheets/app.utilities.scss +++ b/app/assets/stylesheets/app.utilities.scss @@ -102,6 +102,7 @@ p, .widget p { .text-italic { font-style: italic; } +.text-left { text-align: left !important; } .text-center { text-align: center; } .text-right { text-align: right; } diff --git a/app/assets/stylesheets/application.scss.erb b/app/assets/stylesheets/application.scss.erb index ada57aaa4..b87230fd9 100644 --- a/app/assets/stylesheets/application.scss.erb +++ b/app/assets/stylesheets/application.scss.erb @@ -32,11 +32,7 @@ @import "app.buttons"; @import "app.components"; @import "app.plugins"; -@import "modules/invoice"; -@import "modules/signup"; -@import "modules/abuses"; -@import "modules/cookies"; -@import "modules/stripe"; +@import "modules/*"; @import "app.responsive"; diff --git a/app/assets/stylesheets/modules/icalendar.scss b/app/assets/stylesheets/modules/icalendar.scss new file mode 100644 index 000000000..a36dafa74 --- /dev/null +++ b/app/assets/stylesheets/modules/icalendar.scss @@ -0,0 +1,10 @@ +.calendar-form { + margin : 2em; + border: 1px solid #ddd; + border-radius: 3px; + padding: 1em; + + & > .input-group, & > .minicolors { + margin-top: 1em; + } +} diff --git a/app/assets/templates/admin/calendar/calendar.html.erb b/app/assets/templates/admin/calendar/calendar.html.erb index f1666a628..ef3345004 100644 --- a/app/assets/templates/admin/calendar/calendar.html.erb +++ b/app/assets/templates/admin/calendar/calendar.html.erb @@ -5,15 +5,17 @@
    -
    +

    {{ 'admin_calendar.calendar_management' }}

    -
    -
    - +
    +
    + + +
    @@ -28,7 +30,7 @@

    {{ 'admin_calendar.legend' }}

    - {{ 'admin_calendar.trainings' }}
    + {{ 'admin_calendar.trainings' }}
    {{ 'admin_calendar.machines' }}
    {{ 'admin_calendar.spaces' }} {{ 'admin_calendar.events' }} diff --git a/app/assets/templates/admin/calendar/icalendar.html b/app/assets/templates/admin/calendar/icalendar.html new file mode 100644 index 000000000..70faf8f5f --- /dev/null +++ b/app/assets/templates/admin/calendar/icalendar.html @@ -0,0 +1,87 @@ +
    +
    +
    +
    + +
    +
    +
    +
    +

    {{ 'icalendar.icalendar_import' }}

    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    + +
    +
    + {{ 'icalendar.intro' }} +
    +
    + + + + + + + + + + + + + + +
    {{ 'icalendar.url' }}{{ 'icalendar.display' }}
    {{calendar.url}} {{ calendar.textHidden ? '' : 'icalendar.example' }} + + +
    + +

    {{ 'icalendar.new_import' }}

    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + + +
    +
    + +
    + +
    +
    +
    diff --git a/app/assets/templates/admin/settings/general.html b/app/assets/templates/admin/settings/general.html index 2822ad1ba..f4f9c0514 100644 --- a/app/assets/templates/admin/settings/general.html +++ b/app/assets/templates/admin/settings/general.html @@ -341,7 +341,9 @@
    -
    +
    +
    +
    diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index c55d89754..576cada15 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -88,6 +88,20 @@ fr: view_reservations: "Voir les réservations" legend: "Légende" + icalendar: + icalendar: + icalendar_import: "Import iCalendar" + intro: "Fab-manager vous permet d'importer automatiquement des évènements de calendrier, au format iCalendar RFC 5545, depuis des URL externes. Ces URL seront synchronisée toutes les nuits et les évènements seront affichés dans le calendrier publique." + new_import: "Nouvel import ICS" + color: "Couleur" + text_color: "Couleur du texte" + url: "URL" + example: "Exemple" + display: "Affichage" + hide_text: "Cacher le texte" + hidden: "Caché" + shown: "Affiché" + project_elements: # gestion des éléments constituant les projets project_elements: From baf8cfb4876556bb47055af3253d15697cc2f516 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 27 Nov 2019 17:05:19 +0100 Subject: [PATCH 042/135] iCalendar server api & db model --- .../controllers/admin/calendar.js.erb | 6 +- .../javascripts/controllers/calendar.js | 2 +- app/assets/javascripts/router.js.erb | 1 + app/assets/javascripts/services/ical.js | 5 -- app/assets/javascripts/services/icalendar.js | 12 ++++ app/controllers/api/i_calendar_controller.rb | 55 +++++++++++++++++++ app/controllers/api/ical_controller.rb | 19 ------- app/models/i_calendar.rb | 5 ++ app/policies/i_calendar_policy.rb | 12 ++++ .../api/i_calendar/_i_calendar.json.jbuilder | 3 + .../events.json.jbuilder} | 4 +- app/views/api/i_calendar/index.json.jbuilder | 5 ++ app/views/api/i_calendar/show.json.jbuilder | 3 + config/routes.rb | 4 +- .../20191127153729_create_i_calendars.rb | 16 ++++++ db/schema.rb | 11 +++- test/fixtures/i_calendars.yml | 13 +++++ test/models/i_calendar_test.rb | 7 +++ 18 files changed, 152 insertions(+), 31 deletions(-) delete mode 100644 app/assets/javascripts/services/ical.js create mode 100644 app/assets/javascripts/services/icalendar.js create mode 100644 app/controllers/api/i_calendar_controller.rb delete mode 100644 app/controllers/api/ical_controller.rb create mode 100644 app/models/i_calendar.rb create mode 100644 app/policies/i_calendar_policy.rb create mode 100644 app/views/api/i_calendar/_i_calendar.json.jbuilder rename app/views/api/{ical/externals.json.jbuilder => i_calendar/events.json.jbuilder} (74%) create mode 100644 app/views/api/i_calendar/index.json.jbuilder create mode 100644 app/views/api/i_calendar/show.json.jbuilder create mode 100644 db/migrate/20191127153729_create_i_calendars.rb create mode 100644 test/fixtures/i_calendars.yml create mode 100644 test/models/i_calendar_test.rb diff --git a/app/assets/javascripts/controllers/admin/calendar.js.erb b/app/assets/javascripts/controllers/admin/calendar.js.erb index 569d7a35d..e41157041 100644 --- a/app/assets/javascripts/controllers/admin/calendar.js.erb +++ b/app/assets/javascripts/controllers/admin/calendar.js.erb @@ -762,10 +762,10 @@ Application.Controllers.controller('DeleteRecurrentAvailabilityController', ['$s * Controller used in the iCalendar (ICS) imports management page */ -Application.Controllers.controller('AdminICalendarController', ['$scope', - function ($scope) { +Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCalendars', + function ($scope, iCalendars) { // list of ICS sources - $scope.calendars = []; + $scope.calendars = iCalendars; // configuration of a new ICS source $scope.newCalendar = { diff --git a/app/assets/javascripts/controllers/calendar.js b/app/assets/javascripts/controllers/calendar.js index e46eb8db1..33d811f55 100644 --- a/app/assets/javascripts/controllers/calendar.js +++ b/app/assets/javascripts/controllers/calendar.js @@ -41,7 +41,7 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ // add availabilities source to event sources $scope.eventSources = []; $scope.eventSources.push({ - url: '/api/ical/externals', + url: '/api/i_calendar/events', textColor: 'black' }); diff --git a/app/assets/javascripts/router.js.erb b/app/assets/javascripts/router.js.erb index f0476c79c..62503a9cf 100644 --- a/app/assets/javascripts/router.js.erb +++ b/app/assets/javascripts/router.js.erb @@ -668,6 +668,7 @@ angular.module('application.router', ['ui.router']) } }, resolve: { + iCalendars: ['ICalendar', function (ICalendar) { return ICalendar.query().$promise; }], translations: ['Translations', function (Translations) { return Translations.query('app.admin.icalendar').$promise; }] } }) diff --git a/app/assets/javascripts/services/ical.js b/app/assets/javascripts/services/ical.js deleted file mode 100644 index dec1ed635..000000000 --- a/app/assets/javascripts/services/ical.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -Application.Services.factory('Ical', ['$resource', function ($resource) { - return $resource('/api/ical/externals'); -}]); diff --git a/app/assets/javascripts/services/icalendar.js b/app/assets/javascripts/services/icalendar.js new file mode 100644 index 000000000..21d30843e --- /dev/null +++ b/app/assets/javascripts/services/icalendar.js @@ -0,0 +1,12 @@ +'use strict'; + +Application.Services.factory('ICalendar', ['$resource', function ($resource) { + return $resource('/api/i_calendar/:id', + { id: '@id' }, { + events: { + method: 'GET', + url: '/api/i_calendar/events' + } + } + ); +}]); diff --git a/app/controllers/api/i_calendar_controller.rb b/app/controllers/api/i_calendar_controller.rb new file mode 100644 index 000000000..f004a13ab --- /dev/null +++ b/app/controllers/api/i_calendar_controller.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +# API Controller for resources of type iCalendar +class API::ICalendarController < API::ApiController + before_action :authenticate_user!, except: %i[index events] + before_action :set_i_cal, only: [:destroy] + respond_to :json + + def index + @i_cals = ICalendar.all + end + + def create + authorize ICalendar + @i_cal = ICalendar.new(i_calendar_params) + if @i_cal.save + render :show, status: :created, location: @i_cal + else + render json: @i_cal.errors, status: :unprocessable_entity + end + end + + def destroy + authorize ICalendar + @i_cal.destroy + head :no_content + end + + def events + require 'net/http' + require 'uri' + require 'icalendar' + + @events = [] + + @i_cals = ICalendar.all.each do |i_cal| + ics = Net::HTTP.get(URI.parse(i_cal.url)) + cals = Icalendar::Calendar.parse(ics) + + cals.first.events.each do |evt| + @events.push(evt.merge!(color: i_cal.color)) + end + end + end + + private + + def set_i_cal + @i_cal = ICalendar.find(params[:id]) + end + + def i_calendar_params + params.require(:i_calendar).permit(:url, :color, :text_color, :text_hidden) + end +end diff --git a/app/controllers/api/ical_controller.rb b/app/controllers/api/ical_controller.rb deleted file mode 100644 index dc3307d85..000000000 --- a/app/controllers/api/ical_controller.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -# API Controller for resources of type iCalendar -class API::IcalController < API::ApiController - respond_to :json - - def externals - require 'net/http' - require 'uri' - - ics = Net::HTTP.get(URI.parse('https://calendar.google.com/calendar/ical/sylvain%40sleede.com/public/basic.ics')) - - require 'icalendar' - require 'icalendar/tzinfo' - - cals = Icalendar::Calendar.parse(ics) - @events = cals.first.events - end -end \ No newline at end of file diff --git a/app/models/i_calendar.rb b/app/models/i_calendar.rb new file mode 100644 index 000000000..17d630cae --- /dev/null +++ b/app/models/i_calendar.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# iCalendar (RFC 5545) files, stored by URL and kept with their display configuration +class ICalendar < ActiveRecord::Base +end diff --git a/app/policies/i_calendar_policy.rb b/app/policies/i_calendar_policy.rb new file mode 100644 index 000000000..fd4021814 --- /dev/null +++ b/app/policies/i_calendar_policy.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# Check the access policies for API::ICalendarController +class ICalendarPolicy < ApplicationPolicy + def create? + user.admin? + end + + def destroy? + user.admin? + end +end diff --git a/app/views/api/i_calendar/_i_calendar.json.jbuilder b/app/views/api/i_calendar/_i_calendar.json.jbuilder new file mode 100644 index 000000000..23b68df1d --- /dev/null +++ b/app/views/api/i_calendar/_i_calendar.json.jbuilder @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +json.extract! i_cal, :id, :url, :color, :text_color, :text_hidden diff --git a/app/views/api/ical/externals.json.jbuilder b/app/views/api/i_calendar/events.json.jbuilder similarity index 74% rename from app/views/api/ical/externals.json.jbuilder rename to app/views/api/i_calendar/events.json.jbuilder index faedb232b..5078729e7 100644 --- a/app/views/api/ical/externals.json.jbuilder +++ b/app/views/api/i_calendar/events.json.jbuilder @@ -1,8 +1,10 @@ +# frozen_string_literal: true + json.array!(@events) do |event| json.id event.uid json.title event.summary json.start event.dtstart.iso8601 json.end event.dtend.iso8601 json.backgroundColor 'white' - json.borderColor '#214712' + json.borderColor event.color end diff --git a/app/views/api/i_calendar/index.json.jbuilder b/app/views/api/i_calendar/index.json.jbuilder new file mode 100644 index 000000000..a517d6997 --- /dev/null +++ b/app/views/api/i_calendar/index.json.jbuilder @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +json.array!(@i_cals) do |i_cal| + json.partial! 'api/i_calendar/i_calendar', i_cal: i_cal +end diff --git a/app/views/api/i_calendar/show.json.jbuilder b/app/views/api/i_calendar/show.json.jbuilder new file mode 100644 index 000000000..8e787f030 --- /dev/null +++ b/app/views/api/i_calendar/show.json.jbuilder @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +json.partial! 'api/i_calendar/i_calendar', i_cal: @i_cal diff --git a/config/routes.rb b/config/routes.rb index 9e0b5e095..bcb92b44c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -112,7 +112,9 @@ Rails.application.routes.draw do get 'first', action: 'first', on: :collection end - get 'ical/externals' => 'ical#externals' + resources :i_calendar, only: %i[index create destroy] do + get 'events', on: :collection + end # for admin resources :trainings do diff --git a/db/migrate/20191127153729_create_i_calendars.rb b/db/migrate/20191127153729_create_i_calendars.rb new file mode 100644 index 000000000..289e93b94 --- /dev/null +++ b/db/migrate/20191127153729_create_i_calendars.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# From this migration, we store URL to iCalendar files and a piece of configuration about them. +# This allows to display the events of these external calendars in fab-manager +class CreateICalendars < ActiveRecord::Migration + def change + create_table :i_calendars do |t| + t.string :url + t.string :color + t.string :text_color + t.boolean :text_hidden + + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 6e2ed6b2c..bd9579e7f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20191113103352) do +ActiveRecord::Schema.define(version: 20191127153729) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -251,6 +251,15 @@ ActiveRecord::Schema.define(version: 20191113103352) do add_index "history_values", ["invoicing_profile_id"], name: "index_history_values_on_invoicing_profile_id", using: :btree add_index "history_values", ["setting_id"], name: "index_history_values_on_setting_id", using: :btree + create_table "i_calendars", force: :cascade do |t| + t.string "url" + t.string "color" + t.string "text_color" + t.boolean "text_hidden" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "imports", force: :cascade do |t| t.integer "user_id" t.string "attachment" diff --git a/test/fixtures/i_calendars.yml b/test/fixtures/i_calendars.yml new file mode 100644 index 000000000..d3406e7cf --- /dev/null +++ b/test/fixtures/i_calendars.yml @@ -0,0 +1,13 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + url: MyString + color: MyString + text_color: MyString + text_hidden: false + +two: + url: MyString + color: MyString + text_color: MyString + text_hidden: false diff --git a/test/models/i_calendar_test.rb b/test/models/i_calendar_test.rb new file mode 100644 index 000000000..0ab530e7c --- /dev/null +++ b/test/models/i_calendar_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class ICalendarTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From 97d93cd6226ff9ddfbc4e124c7c43144c5a7c080 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 27 Nov 2019 17:39:19 +0100 Subject: [PATCH 043/135] added a name to imported calendar --- .../controllers/admin/calendar.js.erb | 23 +++++++++++++------ app/assets/stylesheets/modules/icalendar.scss | 9 ++++++++ .../templates/admin/calendar/icalendar.html | 12 ++++++++-- app/controllers/api/i_calendar_controller.rb | 2 +- .../api/i_calendar/_i_calendar.json.jbuilder | 2 +- config/locales/app.admin.fr.yml | 2 ++ .../20191127153729_create_i_calendars.rb | 1 + db/schema.rb | 1 + 8 files changed, 41 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/controllers/admin/calendar.js.erb b/app/assets/javascripts/controllers/admin/calendar.js.erb index e41157041..85d95c1db 100644 --- a/app/assets/javascripts/controllers/admin/calendar.js.erb +++ b/app/assets/javascripts/controllers/admin/calendar.js.erb @@ -762,8 +762,8 @@ Application.Controllers.controller('DeleteRecurrentAvailabilityController', ['$s * Controller used in the iCalendar (ICS) imports management page */ -Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCalendars', - function ($scope, iCalendars) { +Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCalendars', 'ICalendar', 'growl', '_t', + function ($scope, iCalendars, ICalendar, growl, _t) { // list of ICS sources $scope.calendars = iCalendars; @@ -772,6 +772,7 @@ Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCale color: undefined, textColor: undefined, url: undefined, + name: undefined, textHidden: false }; @@ -779,11 +780,19 @@ Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCale * Save the new iCalendar in database */ $scope.save = function () { - $scope.calendars.push(Object.assign({}, $scope.newCalendar)); - $scope.newCalendar.url = undefined; - $scope.newCalendar.color = null; - $scope.newCalendar.textColor = null; - $scope.newCalendar.textHidden = false; + ICalendar.save({}, { i_calendar: $scope.newCalendar }, function (data) { + // success + $scope.calendars.push(data); + $scope.newCalendar.url = undefined; + $scope.newCalendar.name = undefined; + $scope.newCalendar.color = null; + $scope.newCalendar.textColor = null; + $scope.newCalendar.textHidden = false; + }, function (error) { + // failed + growl.error(_t('icalendar.create_error')); + console.error(error); + }) } /** diff --git a/app/assets/stylesheets/modules/icalendar.scss b/app/assets/stylesheets/modules/icalendar.scss index a36dafa74..7c852fcd6 100644 --- a/app/assets/stylesheets/modules/icalendar.scss +++ b/app/assets/stylesheets/modules/icalendar.scss @@ -8,3 +8,12 @@ margin-top: 1em; } } + +.calendar-name { + font-weight: 600; + font-style: italic; +} + +.calendar-url { + overflow: hidden; +} diff --git a/app/assets/templates/admin/calendar/icalendar.html b/app/assets/templates/admin/calendar/icalendar.html index 70faf8f5f..fec55b748 100644 --- a/app/assets/templates/admin/calendar/icalendar.html +++ b/app/assets/templates/admin/calendar/icalendar.html @@ -30,14 +30,16 @@ - + + - + +
    {{ 'icalendar.url' }}{{ 'icalendar.name' }}{{ 'icalendar.url' }} {{ 'icalendar.display' }}
    {{calendar.url}}{{calendar.name}}{{calendar.url}} {{ calendar.textHidden ? '' : 'icalendar.example' }} @@ -47,6 +49,12 @@

    {{ 'icalendar.new_import' }}

    +
    +
    + +
    + +
    diff --git a/app/controllers/api/i_calendar_controller.rb b/app/controllers/api/i_calendar_controller.rb index f004a13ab..857ae30d1 100644 --- a/app/controllers/api/i_calendar_controller.rb +++ b/app/controllers/api/i_calendar_controller.rb @@ -50,6 +50,6 @@ class API::ICalendarController < API::ApiController end def i_calendar_params - params.require(:i_calendar).permit(:url, :color, :text_color, :text_hidden) + params.require(:i_calendar).permit(:name, :url, :color, :text_color, :text_hidden) end end diff --git a/app/views/api/i_calendar/_i_calendar.json.jbuilder b/app/views/api/i_calendar/_i_calendar.json.jbuilder index 23b68df1d..dfa627cd3 100644 --- a/app/views/api/i_calendar/_i_calendar.json.jbuilder +++ b/app/views/api/i_calendar/_i_calendar.json.jbuilder @@ -1,3 +1,3 @@ # frozen_string_literal: true -json.extract! i_cal, :id, :url, :color, :text_color, :text_hidden +json.extract! i_cal, :id, :name, :url, :color, :text_color, :text_hidden diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 576cada15..85e75a36a 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -96,11 +96,13 @@ fr: color: "Couleur" text_color: "Couleur du texte" url: "URL" + name: "Nom" example: "Exemple" display: "Affichage" hide_text: "Cacher le texte" hidden: "Caché" shown: "Affiché" + create_error: "Impossible de créer l'import iCalendar. Veuillez réessayer ultérieurement" project_elements: # gestion des éléments constituant les projets diff --git a/db/migrate/20191127153729_create_i_calendars.rb b/db/migrate/20191127153729_create_i_calendars.rb index 289e93b94..b9b558a0f 100644 --- a/db/migrate/20191127153729_create_i_calendars.rb +++ b/db/migrate/20191127153729_create_i_calendars.rb @@ -6,6 +6,7 @@ class CreateICalendars < ActiveRecord::Migration def change create_table :i_calendars do |t| t.string :url + t.string :name t.string :color t.string :text_color t.boolean :text_hidden diff --git a/db/schema.rb b/db/schema.rb index bd9579e7f..30b47937e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -253,6 +253,7 @@ ActiveRecord::Schema.define(version: 20191127153729) do create_table "i_calendars", force: :cascade do |t| t.string "url" + t.string "name" t.string "color" t.string "text_color" t.boolean "text_hidden" From 85d17d62f39df8ae18709bca981e6fb1800e2e90 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 27 Nov 2019 17:44:01 +0100 Subject: [PATCH 044/135] fix events endpoint --- app/controllers/api/i_calendar_controller.rb | 2 +- app/views/api/i_calendar/events.json.jbuilder | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/controllers/api/i_calendar_controller.rb b/app/controllers/api/i_calendar_controller.rb index 857ae30d1..902ae04b7 100644 --- a/app/controllers/api/i_calendar_controller.rb +++ b/app/controllers/api/i_calendar_controller.rb @@ -38,7 +38,7 @@ class API::ICalendarController < API::ApiController cals = Icalendar::Calendar.parse(ics) cals.first.events.each do |evt| - @events.push(evt.merge!(color: i_cal.color)) + @events.push(evt) end end end diff --git a/app/views/api/i_calendar/events.json.jbuilder b/app/views/api/i_calendar/events.json.jbuilder index 5078729e7..fc8e89f64 100644 --- a/app/views/api/i_calendar/events.json.jbuilder +++ b/app/views/api/i_calendar/events.json.jbuilder @@ -6,5 +6,4 @@ json.array!(@events) do |event| json.start event.dtstart.iso8601 json.end event.dtend.iso8601 json.backgroundColor 'white' - json.borderColor event.color end From f72ae98109e90c2db797299b7c827df03a1feaa4 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 27 Nov 2019 17:50:38 +0100 Subject: [PATCH 045/135] do not show title if calendar is configured to hide them --- app/controllers/api/i_calendar_controller.rb | 2 +- app/views/api/i_calendar/events.json.jbuilder | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/controllers/api/i_calendar_controller.rb b/app/controllers/api/i_calendar_controller.rb index 902ae04b7..6b798921f 100644 --- a/app/controllers/api/i_calendar_controller.rb +++ b/app/controllers/api/i_calendar_controller.rb @@ -38,7 +38,7 @@ class API::ICalendarController < API::ApiController cals = Icalendar::Calendar.parse(ics) cals.first.events.each do |evt| - @events.push(evt) + @events.push(calendar: i_cal, event: evt) end end end diff --git a/app/views/api/i_calendar/events.json.jbuilder b/app/views/api/i_calendar/events.json.jbuilder index fc8e89f64..2029e8558 100644 --- a/app/views/api/i_calendar/events.json.jbuilder +++ b/app/views/api/i_calendar/events.json.jbuilder @@ -1,9 +1,9 @@ # frozen_string_literal: true json.array!(@events) do |event| - json.id event.uid - json.title event.summary - json.start event.dtstart.iso8601 - json.end event.dtend.iso8601 + json.id event[:event].uid + json.title event[:calendar].text_hidden ? '' : event[:event].summary + json.start event[:event].dtstart.iso8601 + json.end event[:event].dtend.iso8601 json.backgroundColor 'white' end From 36eba998082ec27f97ebe54a6fb145aa59630af9 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 2 Dec 2019 10:39:42 +0100 Subject: [PATCH 046/135] [bug] report errors on admin creation --- CHANGELOG.md | 1 + app/assets/javascripts/controllers/admin/members.js.erb | 3 ++- app/services/user_service.rb | 2 +- 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 | 1 + 7 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccd19e3a4..e36c9ad9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Updated moment-timezone - Added freeCAD files as default allowed extensions - Fix a bug: unable to remove the picture from a training +- Fix a bug: report errors on admin creation - Fix a security issue: updated loofah to fix [CVE-2019-15587](https://github.com/advisories/GHSA-c3gv-9cxf-6f57) - Fix a security issue: updated angular to 1.7.9 to fix [CVE-2019-10768](https://github.com/advisories/GHSA-89mq-4x47-5v83) - [TODO DEPLOY] add the `SLOT_DURATION` environment variable (see [doc/environment.md](doc/environment.md#SLOT_DURATION) for configuration details) diff --git a/app/assets/javascripts/controllers/admin/members.js.erb b/app/assets/javascripts/controllers/admin/members.js.erb index e9c9f77c9..bacbd58aa 100644 --- a/app/assets/javascripts/controllers/admin/members.js.erb +++ b/app/assets/javascripts/controllers/admin/members.js.erb @@ -754,7 +754,8 @@ Application.Controllers.controller('NewAdminController', ['$state', '$scope', 'A return $state.go('app.admin.members'); } , function (error) { - console.log(error); + growl.error(_t('failed_to_create_admin') + JSON.stringify(error.data ? error.data : error)); + console.error(error); } ); }; diff --git a/app/services/user_service.rb b/app/services/user_service.rb index 3293c57c6..55d05e6fa 100644 --- a/app/services/user_service.rb +++ b/app/services/user_service.rb @@ -41,7 +41,7 @@ class UserService # if the authentication is made through an SSO, generate a migration token admin.generate_auth_migration_token unless AuthProvider.active.providable_type == DatabaseProvider.name - saved = admin.save(validate: false) + saved = admin.save if saved admin.send_confirmation_instructions admin.add_role(:admin) diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index 0b1aaa437..960196bbc 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -678,6 +678,7 @@ en: # add a new administrator to the platform add_an_administrator: "Add an administrator" administrator_successfully_created_he_will_receive_his_connection_directives_by_email: "Administrator successfully created. {GENDER, select, female{She} other{He}} receive {GENDER, select, female{her} other{his}} connection directives by e-mail." # messageFormat interpolation + failed_to_create_admin: "Unable to create the administrator:" authentication_new: # add a new authentication provider (SSO) diff --git a/config/locales/app.admin.es.yml b/config/locales/app.admin.es.yml index 2f329ba4b..ed24b3296 100644 --- a/config/locales/app.admin.es.yml +++ b/config/locales/app.admin.es.yml @@ -678,6 +678,7 @@ es: # add a new administrator to the platform add_an_administrator: "Agregar un administrador" administrator_successfully_created_he_will_receive_his_connection_directives_by_email: "administrador creado correctamente. {GENDER, select, female{She} other{He}} receive {GENDER, select, female{her} other{his}} directivas de conexión por e-mail." # messageFormat interpolation + failed_to_create_admin: "No se puede crear el administrador :" authentication_new: # add a new authentication provider (SSO) diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 85e75a36a..ada28f728 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -694,6 +694,7 @@ fr: # ajouter un nouvel administrateur à la plate-forme add_an_administrator: "Ajouter un administrateur" administrator_successfully_created_he_will_receive_his_connection_directives_by_email: "L'administrateur a bien été créé. {GENDER, select, female{Elle} other{Il}} recevra ses instructions de connexion par email." # messageFormat interpolation + failed_to_create_admin: "Impossible de créer l'administrateur :" authentication_new: # ajouter un nouveau fournisseur d'authentification (SSO) diff --git a/config/locales/app.admin.pt.yml b/config/locales/app.admin.pt.yml index 487a45cf2..d6795c2b4 100755 --- a/config/locales/app.admin.pt.yml +++ b/config/locales/app.admin.pt.yml @@ -678,6 +678,7 @@ pt: # add a new administrator to the platform add_an_administrator: "Adicionar administrador" administrator_successfully_created_he_will_receive_his_connection_directives_by_email: "Administrator criado com sucesso. {GENDER, select, female{Ela} other{Ele}} receberá {GENDER, select, female{sua} other{seu}} diretivas de conexão por e-mail." # messageFormat interpolation + failed_to_create_admin: "Não é possível criar administrador:" authentication_new: # add a new authentication provider (SSO) From 77cf8c821857db99ae8988710c9fb8af411ed79f Mon Sep 17 00:00:00 2001 From: Nicolas Florentin Date: Mon, 2 Dec 2019 11:57:25 +0100 Subject: [PATCH 047/135] replaces almost all Time.now by DateTime.current --- .../api/accounting_periods_controller.rb | 2 +- app/controllers/api/events_controller.rb | 8 ++++---- .../open_api/v1/events_controller.rb | 2 +- app/controllers/rss/events_controller.rb | 2 +- app/models/avoir.rb | 2 +- app/models/coupon.rb | 6 +++--- app/models/event.rb | 2 +- app/models/project.rb | 2 +- app/models/slot.rb | 2 +- app/models/subscription.rb | 6 +++--- app/models/user.rb | 4 ++-- app/policies/event_policy.rb | 2 +- app/policies/slot_policy.rb | 2 +- .../availabilities/availabilities_service.rb | 18 +++++++++--------- .../public_availabilities_service.rb | 2 +- app/services/invoice_reference_service.rb | 2 +- app/services/slot_service.rb | 2 +- app/services/user_service.rb | 2 +- app/services/vat_history_service.rb | 2 +- app/validators/closed_period_validator.rb | 2 +- app/validators/coupon_expiration_validator.rb | 2 +- app/views/exports/users_members.xlsx.axlsx | 4 ++-- app/workers/archive_worker.rb | 2 +- .../open_api_trace_calls_count_worker.rb | 2 +- app/workers/reservation_reminder_worker.rb | 2 +- app/workers/subscription_expire_worker.rb | 4 ++-- db/schema.rb | 2 +- db/seeds.rb | 2 +- lib/tasks/fablab/es.rake | 2 +- lib/tasks/fablab/stripe.rake | 2 +- 30 files changed, 48 insertions(+), 48 deletions(-) diff --git a/app/controllers/api/accounting_periods_controller.rb b/app/controllers/api/accounting_periods_controller.rb index f374e18bd..e154ef76e 100644 --- a/app/controllers/api/accounting_periods_controller.rb +++ b/app/controllers/api/accounting_periods_controller.rb @@ -14,7 +14,7 @@ class API::AccountingPeriodsController < API::ApiController def create authorize AccountingPeriod - @accounting_period = AccountingPeriod.new(period_params.merge(closed_at: DateTime.now, closed_by: current_user.id)) + @accounting_period = AccountingPeriod.new(period_params.merge(closed_at: DateTime.current, closed_by: current_user.id)) if @accounting_period.save render :show, status: :created, location: @accounting_period else diff --git a/app/controllers/api/events_controller.rb b/app/controllers/api/events_controller.rb index 0791b8de0..532db2f7f 100644 --- a/app/controllers/api/events_controller.rb +++ b/app/controllers/api/events_controller.rb @@ -17,11 +17,11 @@ class API::EventsController < API::ApiController if current_user&.admin? @events = case params[:scope] when 'future' - @events.where('availabilities.start_at >= ?', Time.now).order('availabilities.start_at DESC') + @events.where('availabilities.start_at >= ?', DateTime.current).order('availabilities.start_at DESC') when 'future_asc' - @events.where('availabilities.start_at >= ?', Time.now).order('availabilities.start_at ASC') + @events.where('availabilities.start_at >= ?', DateTime.current).order('availabilities.start_at ASC') when 'passed' - @events.where('availabilities.start_at < ?', Time.now).order('availabilities.start_at DESC') + @events.where('availabilities.start_at < ?', DateTime.current).order('availabilities.start_at DESC') else @events.order('availabilities.start_at DESC') end @@ -36,7 +36,7 @@ class API::EventsController < API::ApiController limit = params[:limit] @events = Event.includes(:event_image, :event_files, :availability, :category) .where('events.nb_total_places != -1 OR events.nb_total_places IS NULL') - .where('availabilities.start_at >= ?', Time.now) + .where('availabilities.start_at >= ?', DateTime.current) .order('availabilities.start_at ASC').references(:availabilities) .limit(limit) end diff --git a/app/controllers/open_api/v1/events_controller.rb b/app/controllers/open_api/v1/events_controller.rb index adcc4a6ed..169c367ef 100644 --- a/app/controllers/open_api/v1/events_controller.rb +++ b/app/controllers/open_api/v1/events_controller.rb @@ -6,7 +6,7 @@ class OpenAPI::V1::EventsController < OpenAPI::V1::BaseController if upcoming @events = Event.includes(:event_image, :event_files, :availability, :category) - .where('availabilities.end_at >= ?', Time.now) + .where('availabilities.end_at >= ?', DateTime.current) .order('availabilities.start_at ASC').references(:availabilities) else @events = Event.includes(:event_image, :event_files, :availability, :category).order(created_at: :desc) diff --git a/app/controllers/rss/events_controller.rb b/app/controllers/rss/events_controller.rb index 2b6351ef5..63ea6a657 100644 --- a/app/controllers/rss/events_controller.rb +++ b/app/controllers/rss/events_controller.rb @@ -5,7 +5,7 @@ class Rss::EventsController < Rss::RssController def index @events = Event.includes(:event_image, :event_files, :availability, :category) - .where('availabilities.start_at >= ?', Time.now) + .where('availabilities.start_at >= ?', DateTime.current) .order('availabilities.start_at ASC').references(:availabilities).limit(10) @fab_name = Setting.find_by(name: 'fablab_name').value end diff --git a/app/models/avoir.rb b/app/models/avoir.rb index c4d976cfe..e8d6bd98f 100644 --- a/app/models/avoir.rb +++ b/app/models/avoir.rb @@ -14,6 +14,6 @@ class Avoir < Invoice end def expire_subscription - user.subscription.expire(Time.now) + user.subscription.expire(DateTime.current) end end diff --git a/app/models/coupon.rb b/app/models/coupon.rb index 25855cf1e..f510cae51 100644 --- a/app/models/coupon.rb +++ b/app/models/coupon.rb @@ -17,7 +17,7 @@ class Coupon < ActiveRecord::Base validates_with CouponExpirationValidator scope :disabled, -> { where(active: false) } - scope :expired, -> { where('valid_until IS NOT NULL AND valid_until < ?', DateTime.now) } + scope :expired, -> { where('valid_until IS NOT NULL AND valid_until < ?', DateTime.current) } scope :sold_out, lambda { joins(:invoices).select('coupons.*, COUNT(invoices.id) as invoices_count').group('coupons.id') .where.not(max_usages: nil).having('COUNT(invoices.id) >= coupons.max_usages') @@ -26,7 +26,7 @@ class Coupon < ActiveRecord::Base joins('LEFT OUTER JOIN invoices ON invoices.coupon_id = coupons.id') .select('coupons.*, COUNT(invoices.id) as invoices_count') .group('coupons.id') - .where('active = true AND (valid_until IS NULL OR valid_until >= ?)', DateTime.now) + .where('active = true AND (valid_until IS NULL OR valid_until >= ?)', DateTime.current) .having('COUNT(invoices.id) < coupons.max_usages OR coupons.max_usages IS NULL') } @@ -55,7 +55,7 @@ class Coupon < ActiveRecord::Base def status(user_id = nil, amount = nil) if !active? 'disabled' - elsif !valid_until.nil? && valid_until.at_end_of_day < DateTime.now + elsif !valid_until.nil? && valid_until.at_end_of_day < DateTime.current 'expired' elsif !max_usages.nil? && invoices.count >= max_usages 'sold_out' diff --git a/app/models/event.rb b/app/models/event.rb index bad777185..d1b12bb6c 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -37,7 +37,7 @@ class Event < ActiveRecord::Base def recurrence_events Event.includes(:availability) - .where('events.recurrence_id = ? AND events.id != ? AND availabilities.start_at >= ?', recurrence_id, id, Time.now) + .where('events.recurrence_id = ? AND events.id != ? AND availabilities.start_at >= ?', recurrence_id, id, DateTime.current) .references(:availabilities) end diff --git a/app/models/project.rb b/app/models/project.rb index 18b564d01..d9768d22f 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -151,7 +151,7 @@ class Project < ActiveRecord::Base def after_save_and_publish return unless state_changed? && published? - update_columns(published_at: Time.now) + update_columns(published_at: DateTime.current) notify_admin_when_project_published end end diff --git a/app/models/slot.rb b/app/models/slot.rb index cddb98dac..ddb60de9a 100644 --- a/app/models/slot.rb +++ b/app/models/slot.rb @@ -52,7 +52,7 @@ class Slot < ActiveRecord::Base end def can_be_modified? - return false if (start_at - Time.now) / 1.day < 1 + return false if (start_at - DateTime.current) / 1.day < 1 true end diff --git a/app/models/subscription.rb b/app/models/subscription.rb index 4d3160eb2..86d13371b 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -81,7 +81,7 @@ class Subscription < ActiveRecord::Base end def cancel - update_columns(canceled_at: Time.now) + update_columns(canceled_at: DateTime.current) end def expire(time) @@ -96,7 +96,7 @@ class Subscription < ActiveRecord::Base end def expired? - expired_at <= Time.now + expired_at <= DateTime.current end def expired_at @@ -179,7 +179,7 @@ class Subscription < ActiveRecord::Base end def set_expiration_date - start_at = Time.now + start_at = DateTime.current self.expiration_date = start_at + plan.duration end diff --git a/app/models/user.rb b/app/models/user.rb index 919660c5a..96d841d36 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -108,7 +108,7 @@ class User < ActiveRecord::Base end def subscribed_plan - return nil if subscription.nil? || subscription.expired_at < Time.now + return nil if subscription.nil? || subscription.expired_at < DateTime.current subscription.plan end @@ -240,7 +240,7 @@ class User < ActiveRecord::Base # remove the token self.auth_token = nil - self.merged_at = DateTime.now + self.merged_at = DateTime.current # check that the email duplication was resolved if sso_user.email.end_with? '-duplicate' diff --git a/app/policies/event_policy.rb b/app/policies/event_policy.rb index e271834d7..c6855ba0b 100644 --- a/app/policies/event_policy.rb +++ b/app/policies/event_policy.rb @@ -7,7 +7,7 @@ class EventPolicy < ApplicationPolicy def resolve if user.nil? || (user && !user.admin?) scope.includes(:event_image, :event_files, :availability, :category) - .where('availabilities.start_at >= ?', Time.now) + .where('availabilities.start_at >= ?', DateTime.current) .order('availabilities.start_at ASC') .references(:availabilities) else diff --git a/app/policies/slot_policy.rb b/app/policies/slot_policy.rb index cbf742b51..3ec6819b9 100644 --- a/app/policies/slot_policy.rb +++ b/app/policies/slot_policy.rb @@ -6,7 +6,7 @@ class SlotPolicy < ApplicationPolicy # these condition does not apply to admins user.admin? or - (record.reservation.user == user and enabled and ((record.start_at - Time.now).to_i / 3600 >= delay)) + (record.reservation.user == user and enabled and ((record.start_at - DateTime.current).to_i / 3600 >= delay)) end def cancel? diff --git a/app/services/availabilities/availabilities_service.rb b/app/services/availabilities/availabilities_service.rb index 019082cae..5539faac4 100644 --- a/app/services/availabilities/availabilities_service.rb +++ b/app/services/availabilities/availabilities_service.rb @@ -18,7 +18,7 @@ class Availabilities::AvailabilitiesService slots = [] availabilities.each do |a| ((a.end_at - a.start_at) / ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i| - next unless (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now + next unless (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > DateTime.current slot = Slot.new( start_at: a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes, @@ -44,7 +44,7 @@ class Availabilities::AvailabilitiesService slots = [] availabilities.each do |a| ((a.end_at - a.start_at) / ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i| - next unless (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now + next unless (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > DateTime.current slot = Slot.new( start_at: a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes, @@ -69,7 +69,7 @@ class Availabilities::AvailabilitiesService # first, we get the already-made reservations reservations = user.reservations.where("reservable_type = 'Training'") reservations = reservations.where('reservable_id = :id', id: training_id.to_i) if training_id.is_number? - reservations = reservations.joins(:slots).where('slots.start_at > ?', Time.now) + reservations = reservations.joins(:slots).where('slots.start_at > ?', DateTime.current) # visible availabilities depends on multiple parameters availabilities = training_availabilities(training_id, user) @@ -83,7 +83,7 @@ class Availabilities::AvailabilitiesService private def subscription_year?(user) - user.subscription && user.subscription.plan.interval == 'year' && user.subscription.expired_at >= Time.now + user.subscription && user.subscription.plan.interval == 'year' && user.subscription.expired_at >= DateTime.current end # member must have validated at least 1 training and must have a valid yearly subscription. @@ -95,21 +95,21 @@ class Availabilities::AvailabilitiesService Reservation.where('reservable_type = ? and reservable_id = ?', reservable.class.name, reservable.id) .includes(:slots, statistic_profile: [user: [:profile]]) .references(:slots, :user) - .where('slots.start_at > ?', Time.now) + .where('slots.start_at > ?', DateTime.current) end def availabilities(reservable, type, user) if user.admin? reservable.availabilities .includes(:tags) - .where('end_at > ? AND available_type = ?', Time.now, type) + .where('end_at > ? AND available_type = ?', DateTime.current, type) .where(lock: false) else end_at = @maximum_visibility[:other] end_at = @maximum_visibility[:year] if subscription_year?(user) reservable.availabilities .includes(:tags) - .where('end_at > ? AND end_at < ? AND available_type = ?', Time.now, end_at, type) + .where('end_at > ? AND end_at < ? AND available_type = ?', DateTime.current, end_at, type) .where('availability_tags.tag_id' => user.tag_ids.concat([nil])) .where(lock: false) end @@ -126,14 +126,14 @@ class Availabilities::AvailabilitiesService # 1) an admin (he can see all future availabilities) if @current_user.admin? availabilities.includes(:tags, :slots, trainings: [:machines]) - .where('availabilities.start_at > ?', Time.now) + .where('availabilities.start_at > ?', DateTime.current) .where(lock: false) # 2) an user (he cannot see availabilities further than 1 (or 3) months) else end_at = @maximum_visibility[:other] end_at = @maximum_visibility[:year] if show_extended_slots?(user) availabilities.includes(:tags, :slots, :availability_tags, trainings: [:machines]) - .where('availabilities.start_at > ? AND availabilities.start_at < ?', Time.now, end_at) + .where('availabilities.start_at > ? AND availabilities.start_at < ?', DateTime.current, end_at) .where('availability_tags.tag_id' => user.tag_ids.concat([nil])) .where(lock: false) end diff --git a/app/services/availabilities/public_availabilities_service.rb b/app/services/availabilities/public_availabilities_service.rb index 3e0257a53..9c3f63a0f 100644 --- a/app/services/availabilities/public_availabilities_service.rb +++ b/app/services/availabilities/public_availabilities_service.rb @@ -47,7 +47,7 @@ class Availabilities::PublicAvailabilitiesService availabilities.each do |a| space = a.spaces.first ((a.end_at - a.start_at) / ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i| - next unless (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now + next unless (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > DateTime.current slot = Slot.new( start_at: a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes, diff --git a/app/services/invoice_reference_service.rb b/app/services/invoice_reference_service.rb index e756a7aa9..966a76b15 100644 --- a/app/services/invoice_reference_service.rb +++ b/app/services/invoice_reference_service.rb @@ -3,7 +3,7 @@ # Provides methods to generate invoice references class InvoiceReferenceService class << self - def generate_reference(invoice, date: Time.now, avoir: false) + def generate_reference(invoice, date: DateTime.current, avoir: false) pattern = Setting.find_by(name: 'invoice_reference').value reference = replace_invoice_number_pattern(pattern, invoice) diff --git a/app/services/slot_service.rb b/app/services/slot_service.rb index de3bb41fb..64419f8b0 100644 --- a/app/services/slot_service.rb +++ b/app/services/slot_service.rb @@ -4,7 +4,7 @@ class SlotService def cancel(slot) # first we mark ths slot as cancelled in DB, to free a ticket - slot.update_attributes(canceled_at: DateTime.now) + slot.update_attributes(canceled_at: DateTime.current) # then we try to remove this reservation from ElasticSearch, to keep the statistics up-to-date model_name = slot.reservation.reservable.class.name diff --git a/app/services/user_service.rb b/app/services/user_service.rb index 3293c57c6..898da5720 100644 --- a/app/services/user_service.rb +++ b/app/services/user_service.rb @@ -19,7 +19,7 @@ class UserService ) user.build_statistic_profile( gender: true, - birthday: Time.now + birthday: DateTime.current ) saved = user.save diff --git a/app/services/vat_history_service.rb b/app/services/vat_history_service.rb index ef8cf60d4..8ced7b37c 100644 --- a/app/services/vat_history_service.rb +++ b/app/services/vat_history_service.rb @@ -27,7 +27,7 @@ class VatHistoryService def vat_history chronology = [] - end_date = DateTime.now + end_date = DateTime.current Setting.find_by(name: 'invoice_VAT-active').history_values.order(created_at: 'DESC').each do |v| chronology.push(start: v.created_at, end: end_date, enabled: v.value == 'true') end_date = v.created_at diff --git a/app/validators/closed_period_validator.rb b/app/validators/closed_period_validator.rb index db6ae34e5..b81843c79 100644 --- a/app/validators/closed_period_validator.rb +++ b/app/validators/closed_period_validator.rb @@ -6,7 +6,7 @@ class ClosedPeriodValidator < ActiveModel::Validator date = if record.is_a?(Avoir) record.avoir_date else - DateTime.now + DateTime.current end diff --git a/app/validators/coupon_expiration_validator.rb b/app/validators/coupon_expiration_validator.rb index d9a80f55e..1e9cc6aab 100644 --- a/app/validators/coupon_expiration_validator.rb +++ b/app/validators/coupon_expiration_validator.rb @@ -7,7 +7,7 @@ class CouponExpirationValidator < ActiveModel::Validator current = record.valid_until unless current.blank? - if current.end_of_day < Time.now + if current.end_of_day < DateTime.current record.errors[:valid_until] << I18n.t('errors.messages.cannot_be_in_the_past') end diff --git a/app/views/exports/users_members.xlsx.axlsx b/app/views/exports/users_members.xlsx.axlsx index bd170e5ea..c77ca49bf 100644 --- a/app/views/exports/users_members.xlsx.axlsx +++ b/app/views/exports/users_members.xlsx.axlsx @@ -56,8 +56,8 @@ wb.add_worksheet(name: t('export_members.members')) do |sheet| member.profile.interest, member.profile.software_mastered, member.group&.name, - expiration && Time.now < expiration ? member.subscription.plan.name : t('export_members.without_subscriptions'), - expiration && Time.now < expiration ? member.subscription.expired_at.to_date : nil, + expiration && DateTime.current < expiration ? member.subscription.plan.name : t('export_members.without_subscriptions'), + expiration && DateTime.current < expiration ? member.subscription.expired_at.to_date : nil, member.trainings.map(&:name).join("\n"), member.tags.map(&:name).join("\n"), member.invoices.size, diff --git a/app/workers/archive_worker.rb b/app/workers/archive_worker.rb index 8dc432b8a..ec8530f72 100644 --- a/app/workers/archive_worker.rb +++ b/app/workers/archive_worker.rb @@ -45,7 +45,7 @@ class ArchiveWorker last_archive_checksum: last_checksum, previous_file: previous_file, software_version: Version.current, - date: Time.now.iso8601 + date: DateTime.current.iso8601 }, formats: [:json], handlers: [:jbuilder] diff --git a/app/workers/open_api_trace_calls_count_worker.rb b/app/workers/open_api_trace_calls_count_worker.rb index 5768522ce..e4bfd52f5 100644 --- a/app/workers/open_api_trace_calls_count_worker.rb +++ b/app/workers/open_api_trace_calls_count_worker.rb @@ -4,7 +4,7 @@ class OpenAPITraceCallsCountWorker < Sidekiq::Workers def perform OpenAPI::Client.find_each do |client| - OpenAPI::CallsCountTracing.create!(client: client, calls_count: client.calls_count, at: DateTime.now) + OpenAPI::CallsCountTracing.create!(client: client, calls_count: client.calls_count, at: DateTime.current) end end end diff --git a/app/workers/reservation_reminder_worker.rb b/app/workers/reservation_reminder_worker.rb index 75e03e0af..5d5b509cd 100644 --- a/app/workers/reservation_reminder_worker.rb +++ b/app/workers/reservation_reminder_worker.rb @@ -9,7 +9,7 @@ class ReservationReminderWorker if enabled == 'true' delay = Setting.find_by(name: 'reminder_delay').try(:value).try(:to_i).try(:hours) || DEFAULT_REMINDER_DELAY - starting = Time.now.beginning_of_hour + delay + starting = DateTime.current.beginning_of_hour + delay ending = starting + 1.hour Reservation.joins(:slots).where('slots.start_at >= ? AND slots.start_at <= ? AND slots.canceled_at IS NULL', starting, ending).each do |r| diff --git a/app/workers/subscription_expire_worker.rb b/app/workers/subscription_expire_worker.rb index def238f70..13362f0ae 100644 --- a/app/workers/subscription_expire_worker.rb +++ b/app/workers/subscription_expire_worker.rb @@ -2,8 +2,8 @@ class SubscriptionExpireWorker include Sidekiq::Worker def perform(expire_in) - Subscription.where('expiration_date >= ?', Time.now.at_beginning_of_day).each do |s| - if (s.expired_at - expire_in.days).to_date == Time.now.to_date + Subscription.where('expiration_date >= ?', DateTime.current.at_beginning_of_day).each do |s| + if (s.expired_at - expire_in.days).to_date == DateTime.current.to_date if expire_in != 0 NotificationCenter.call type: 'notify_member_subscription_will_expire_in_7_days', receiver: s.user, diff --git a/db/schema.rb b/db/schema.rb index 6e2ed6b2c..bd8b2e67e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -15,8 +15,8 @@ ActiveRecord::Schema.define(version: 20191113103352) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" - enable_extension "pg_trgm" enable_extension "unaccent" + enable_extension "pg_trgm" create_table "abuses", force: :cascade do |t| t.integer "signaled_id" diff --git a/db/seeds.rb b/db/seeds.rb index bd5890780..da3de1060 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -90,7 +90,7 @@ if Role.where(name: 'admin').joins(:users).count.zero? admin = User.new(username: 'admin', email: ENV['ADMIN_EMAIL'], password: ENV['ADMIN_PASSWORD'], password_confirmation: Rails.application.secrets.admin_password, group_id: Group.find_by(slug: 'admins').id, profile_attributes: { first_name: 'admin', last_name: 'admin', phone: '0123456789' }, - statistic_profile_attributes: { gender: true, birthday: Time.now }) + statistic_profile_attributes: { gender: true, birthday: DateTime.current }) admin.add_role 'admin' admin.save! end diff --git a/lib/tasks/fablab/es.rake b/lib/tasks/fablab/es.rake index db66a5f1a..5950bd550 100644 --- a/lib/tasks/fablab/es.rake +++ b/lib/tasks/fablab/es.rake @@ -170,7 +170,7 @@ namespace :fablab do days = args.period.to_i if days.zero? - StatisticService.new.generate_statistic(start_date: DateTime.now.beginning_of_day, end_date: DateTime.now.end_of_day) + StatisticService.new.generate_statistic(start_date: DateTime.current.beginning_of_day, end_date: DateTime.current.end_of_day) else days.times.each do |i| StatisticService.new.generate_statistic(start_date: i.day.ago.beginning_of_day, end_date: i.day.ago.end_of_day) diff --git a/lib/tasks/fablab/stripe.rake b/lib/tasks/fablab/stripe.rake index 4584cdca1..6f1249e95 100644 --- a/lib/tasks/fablab/stripe.rake +++ b/lib/tasks/fablab/stripe.rake @@ -6,7 +6,7 @@ namespace :fablab do desc 'Cancel stripe subscriptions' task cancel_subscriptions: :environment do - Subscription.where('expiration_date >= ?', Time.now.at_beginning_of_day).each do |s| + Subscription.where('expiration_date >= ?', DateTime.current.at_beginning_of_day).each do |s| puts "-> Start cancel subscription of #{s.user.email}" s.cancel puts '-> Done' From 55d2c88134538710e1ed82692df19bf2bba7735b Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 2 Dec 2019 12:19:30 +0100 Subject: [PATCH 048/135] delete & sync ical sources --- .../controllers/admin/calendar.js.erb | 31 +++++++++++++++++-- app/assets/javascripts/services/icalendar.js | 5 +++ .../templates/admin/calendar/icalendar.html | 3 +- app/controllers/api/i_calendar_controller.rb | 5 +++ config/locales/app.admin.fr.yml | 3 ++ config/routes.rb | 1 + 6 files changed, 45 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/controllers/admin/calendar.js.erb b/app/assets/javascripts/controllers/admin/calendar.js.erb index 85d95c1db..71fbd47b3 100644 --- a/app/assets/javascripts/controllers/admin/calendar.js.erb +++ b/app/assets/javascripts/controllers/admin/calendar.js.erb @@ -813,8 +813,35 @@ Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCale * @param calendar */ $scope.delete = function (calendar) { - const idx = $scope.calendars.indexOf(calendar); - $scope.calendars.splice(idx, 1); + ICalendar.delete( + { id: calendar.id }, + function () { + // success + const idx = $scope.calendars.indexOf(calendar); + $scope.calendars.splice(idx, 1); + }, function (error) { + // failed + growl.error(_t('icalendar.delete_failed')); + console.error(error); + }); + } + + /** + * Asynchronously re-fetches the events from the given calendar + * @param calendar + */ + $scope.sync = function (calendar) { + ICalendar.sync( + { id: calendar.id }, + function () { + // success + growl.info(_t('icalendar.refresh')); + }, function (error) { + // failed + growl.error(_t('icalendar.sync_failed')); + console.error(error); + } + ) } } ]); diff --git a/app/assets/javascripts/services/icalendar.js b/app/assets/javascripts/services/icalendar.js index 21d30843e..1e01bd5e3 100644 --- a/app/assets/javascripts/services/icalendar.js +++ b/app/assets/javascripts/services/icalendar.js @@ -6,6 +6,11 @@ Application.Services.factory('ICalendar', ['$resource', function ($resource) { events: { method: 'GET', url: '/api/i_calendar/events' + }, + sync: { + method: 'POST', + url: '/api/i_calendar/:id/sync', + params: { id: '@id' } } } ); diff --git a/app/assets/templates/admin/calendar/icalendar.html b/app/assets/templates/admin/calendar/icalendar.html index fec55b748..6c9411d48 100644 --- a/app/assets/templates/admin/calendar/icalendar.html +++ b/app/assets/templates/admin/calendar/icalendar.html @@ -33,7 +33,7 @@ {{ 'icalendar.name' }} {{ 'icalendar.url' }} {{ 'icalendar.display' }} - + @@ -42,6 +42,7 @@ {{calendar.url}} {{ calendar.textHidden ? '' : 'icalendar.example' }} + diff --git a/app/controllers/api/i_calendar_controller.rb b/app/controllers/api/i_calendar_controller.rb index 6b798921f..c506507fe 100644 --- a/app/controllers/api/i_calendar_controller.rb +++ b/app/controllers/api/i_calendar_controller.rb @@ -43,6 +43,11 @@ class API::ICalendarController < API::ApiController end end + def sync + puts '[TODO] run worker' + render json: { processing: true }, status: :created + end + private def set_i_cal diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index ada28f728..e5218afc6 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -103,6 +103,9 @@ fr: hidden: "Caché" shown: "Affiché" create_error: "Impossible de créer l'import iCalendar. Veuillez réessayer ultérieurement" + delete_failed: "Impossible de supprimer l'import iCalendar. Veuillez réessayer ultérieurement" + refresh: "Mise à jour en cours..." + sync_failed: "Impossible de synchroniser l'URL. Veuillez réessayer ultérieurement" project_elements: # gestion des éléments constituant les projets diff --git a/config/routes.rb b/config/routes.rb index bcb92b44c..a8f97dcbc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -114,6 +114,7 @@ Rails.application.routes.draw do resources :i_calendar, only: %i[index create destroy] do get 'events', on: :collection + post 'sync', on: :member end # for admin From 6ff6c710602b236c5c15184c0bdad6225f01cdc8 Mon Sep 17 00:00:00 2001 From: Nicolas Florentin Date: Mon, 2 Dec 2019 12:32:52 +0100 Subject: [PATCH 049/135] rm dead code Slot#can_be_modified? --- app/models/slot.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/models/slot.rb b/app/models/slot.rb index cddb98dac..58b8269a3 100644 --- a/app/models/slot.rb +++ b/app/models/slot.rb @@ -51,12 +51,6 @@ class Slot < ActiveRecord::Base attached_object: self end - def can_be_modified? - return false if (start_at - Time.now) / 1.day < 1 - - true - end - def dates_were_modified? start_at_changed? or end_at_changed? end From 43a05f624a8c16f4ae4ed50632f56308c8dbd158 Mon Sep 17 00:00:00 2001 From: Nicolas Florentin Date: Mon, 2 Dec 2019 12:35:01 +0100 Subject: [PATCH 050/135] updates changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccd19e3a4..0cce90749 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Updated moment-timezone - Added freeCAD files as default allowed extensions - Fix a bug: unable to remove the picture from a training +- Fix a bug: replaces all Time.now by DateTime.current to prevent time zones issues [Taiga#134] - Fix a security issue: updated loofah to fix [CVE-2019-15587](https://github.com/advisories/GHSA-c3gv-9cxf-6f57) - Fix a security issue: updated angular to 1.7.9 to fix [CVE-2019-10768](https://github.com/advisories/GHSA-89mq-4x47-5v83) - [TODO DEPLOY] add the `SLOT_DURATION` environment variable (see [doc/environment.md](doc/environment.md#SLOT_DURATION) for configuration details) From b17bcfde866e6cbcbfc99a80d7f4744fb2ea345f Mon Sep 17 00:00:00 2001 From: Nicolas Florentin Date: Mon, 2 Dec 2019 12:39:20 +0100 Subject: [PATCH 051/135] comment model slot.rb --- app/models/slot.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/slot.rb b/app/models/slot.rb index ddb60de9a..f59012d86 100644 --- a/app/models/slot.rb +++ b/app/models/slot.rb @@ -2,6 +2,7 @@ # Time range of duration defined by ApplicationHelper::SLOT_DURATION, slicing an Availability. # During a slot a Reservation is possible +# Only reserved slots are persisted in DB, others are instanciated on the fly class Slot < ActiveRecord::Base include NotifyWith::NotificationAttachedObject From db41e846ddfd19aec2c3cffc4b9025e755df88d1 Mon Sep 17 00:00:00 2001 From: Nicolas Florentin Date: Mon, 2 Dec 2019 15:29:05 +0100 Subject: [PATCH 052/135] uses DateTime#current instead of Time.now in tests --- app/models/slot.rb | 6 ---- app/models/statistic_profile.rb | 2 +- lib/tasks/fablab/fix.rake | 2 +- test/fixtures/availabilities.yml | 32 +++++++++---------- .../availabilities/as_admin_test.rb | 6 ++-- .../availabilities/as_user_test.rb | 2 +- test/integration/credits/machine_test.rb | 2 +- .../exports/accounting_export_test.rb | 2 +- test/integration/invoices/as_admin_test.rb | 2 +- .../subscriptions/renew_as_user_test.rb | 4 +-- test/integration/wallets_test.rb | 2 +- test/models/coupon_test.rb | 2 +- test/test_helper.rb | 2 +- 13 files changed, 30 insertions(+), 36 deletions(-) diff --git a/app/models/slot.rb b/app/models/slot.rb index f59012d86..c7a1a4b6d 100644 --- a/app/models/slot.rb +++ b/app/models/slot.rb @@ -52,12 +52,6 @@ class Slot < ActiveRecord::Base attached_object: self end - def can_be_modified? - return false if (start_at - DateTime.current) / 1.day < 1 - - true - end - def dates_were_modified? start_at_changed? or end_at_changed? end diff --git a/app/models/statistic_profile.rb b/app/models/statistic_profile.rb index d429366e0..599208b68 100644 --- a/app/models/statistic_profile.rb +++ b/app/models/statistic_profile.rb @@ -30,7 +30,7 @@ class StatisticProfile < ActiveRecord::Base def age if birthday.present? - now = Time.now.utc.to_date + now = DateTime.current.utc.to_date (now - birthday).to_f / AVG_DAYS_PER_YEAR else '' diff --git a/lib/tasks/fablab/fix.rake b/lib/tasks/fablab/fix.rake index 5a7dbfd6a..32453807e 100644 --- a/lib/tasks/fablab/fix.rake +++ b/lib/tasks/fablab/fix.rake @@ -129,7 +129,7 @@ namespace :fablab do desc '[release 3.1.2] fix users with invalid group_id' task users_group_ids: :environment do User.where.not(group_id: Group.all.map(&:id)).each do |u| - u.update_columns(group_id: Group.first.id, updated_at: DateTime.now) + u.update_columns(group_id: Group.first.id, updated_at: DateTime.current) meta_data = { ex_group_name: 'invalid group' } diff --git a/test/fixtures/availabilities.yml b/test/fixtures/availabilities.yml index 5610aaaf8..24b340890 100644 --- a/test/fixtures/availabilities.yml +++ b/test/fixtures/availabilities.yml @@ -1,8 +1,8 @@ availability_1: id: 1 - start_at: <%= DateTime.now.utc.change({hour: 6}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> - end_at: <%= DateTime.now.utc.change({hour: 10}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> + start_at: <%= DateTime.current.utc.change({hour: 6}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> + end_at: <%= DateTime.current.utc.change({hour: 10}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> available_type: training created_at: 2016-04-04 15:24:01.517486000 Z updated_at: 2016-04-04 15:24:01.517486000 Z @@ -11,8 +11,8 @@ availability_1: availability_2: id: 2 - start_at: <%= (DateTime.now + 1.day).utc.change({hour: 6}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> - end_at: <%= (DateTime.now + 1.day).utc.change({hour: 10}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> + start_at: <%= (DateTime.current + 1.day).utc.change({hour: 6}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> + end_at: <%= (DateTime.current + 1.day).utc.change({hour: 10}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> available_type: training created_at: 2016-04-04 15:24:09.169364000 Z updated_at: 2016-04-04 15:24:09.169364000 Z @@ -21,8 +21,8 @@ availability_2: availability_3: id: 3 - start_at: <%= DateTime.now.utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> - end_at: <%= DateTime.now.utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> + start_at: <%= DateTime.current.utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> + end_at: <%= DateTime.current.utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> available_type: machines created_at: 2016-04-04 15:24:27.587583000 Z updated_at: 2016-04-04 15:24:27.587583000 Z @@ -31,8 +31,8 @@ availability_3: availability_4: id: 4 - start_at: <%= (DateTime.now + 1.day).utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> - end_at: <%= (DateTime.now + 1.day).utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> + start_at: <%= (DateTime.current + 1.day).utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> + end_at: <%= (DateTime.current + 1.day).utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> available_type: machines created_at: 2016-04-04 15:24:44.044908000 Z updated_at: 2016-04-04 15:24:44.044908000 Z @@ -41,8 +41,8 @@ availability_4: availability_5: id: 5 - start_at: <%= (DateTime.now + 2.day).utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> - end_at: <%= (DateTime.now + 2.day).utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> + start_at: <%= (DateTime.current + 2.day).utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> + end_at: <%= (DateTime.current + 2.day).utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> available_type: machines created_at: 2016-04-04 15:25:48.584444000 Z updated_at: 2016-04-04 15:25:48.584444000 Z @@ -51,8 +51,8 @@ availability_5: availability_6: id: 6 - start_at: <%= (DateTime.now + 3.day).utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> - end_at: <%= (DateTime.now + 3.day).utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> + start_at: <%= (DateTime.current + 3.day).utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> + end_at: <%= (DateTime.current + 3.day).utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> available_type: machines created_at: 2016-04-04 15:26:17.953216000 Z updated_at: 2016-04-04 15:26:17.953216000 Z @@ -61,8 +61,8 @@ availability_6: availability_7: id: 7 - start_at: <%= (DateTime.now + 3.day).utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> - end_at: <%= (DateTime.now + 3.day).utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> + start_at: <%= (DateTime.current + 3.day).utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> + end_at: <%= (DateTime.current + 3.day).utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> available_type: machines created_at: 2016-04-04 15:26:39.278627000 Z updated_at: 2016-04-04 15:26:39.278627000 Z @@ -71,8 +71,8 @@ availability_7: availability_8: id: 8 - start_at: <%= (DateTime.now + 2.day).utc.change({hour: 6}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> - end_at: <%= (DateTime.now + 2.day).utc.change({hour: 10}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> + start_at: <%= (DateTime.current + 2.day).utc.change({hour: 6}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> + end_at: <%= (DateTime.current + 2.day).utc.change({hour: 10}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> available_type: training created_at: 2016-04-04 15:26:49.572724000 Z updated_at: 2016-04-04 15:26:49.572724000 Z diff --git a/test/integration/availabilities/as_admin_test.rb b/test/integration/availabilities/as_admin_test.rb index 50a046de1..092da0796 100644 --- a/test/integration/availabilities/as_admin_test.rb +++ b/test/integration/availabilities/as_admin_test.rb @@ -37,7 +37,7 @@ module Availabilities # Check that we din't get availabilities from the past availabilities.each do |a| - assert_not a[:start] < DateTime.now, 'retrieved a slot in the past' + assert_not a[:start] < DateTime.current, 'retrieved a slot in the past' end end @@ -46,7 +46,7 @@ module Availabilities Rails.application.secrets.fablab_without_spaces = true # this simulates a fullCalendar (v2) call - start_date = DateTime.now.utc.strftime('%Y-%m-%d') + start_date = DateTime.current.utc.strftime('%Y-%m-%d') end_date = 7.days.from_now.utc.strftime('%Y-%m-%d') tz = Time.zone.tzinfo.name get "/api/availabilities?start=#{start_date}&end=#{end_date}&timezone=#{tz}&_=1487169767960" @@ -68,7 +68,7 @@ module Availabilities test 'get calendar availabilities with spaces' do # this simulates a fullCalendar (v2) call - start_date = DateTime.now.utc.strftime('%Y-%m-%d') + start_date = DateTime.current.utc.strftime('%Y-%m-%d') end_date = 7.days.from_now.utc.strftime('%Y-%m-%d') tz = Time.zone.tzinfo.name get "/api/availabilities?start=#{start_date}&end=#{end_date}&timezone=#{tz}&_=1487169767960" diff --git a/test/integration/availabilities/as_user_test.rb b/test/integration/availabilities/as_user_test.rb index 88655fa7e..bfb9007b1 100644 --- a/test/integration/availabilities/as_user_test.rb +++ b/test/integration/availabilities/as_user_test.rb @@ -22,7 +22,7 @@ class Availabilities::AsUserTest < ActionDispatch::IntegrationTest # Check that we din't get availabilities from the past availabilities.each do |a| - assert_not a[:start] < DateTime.now, 'retrieved a slot in the past' + assert_not a[:start] < DateTime.current, 'retrieved a slot in the past' end # Check that we don't get availabilities in more than a month diff --git a/test/integration/credits/machine_test.rb b/test/integration/credits/machine_test.rb index 2728d08b0..dac006de3 100644 --- a/test/integration/credits/machine_test.rb +++ b/test/integration/credits/machine_test.rb @@ -57,7 +57,7 @@ module Credits credit = json_response(response.body) assert_equal 13, credit[:id] c = Credit.find(credit[:id]) - assert Time.now - c.updated_at < 1.minute + assert c.updated_at > 1.minute.ago assert_equal 5, c.hours end diff --git a/test/integration/exports/accounting_export_test.rb b/test/integration/exports/accounting_export_test.rb index 7885a6572..7a969c3b6 100644 --- a/test/integration/exports/accounting_export_test.rb +++ b/test/integration/exports/accounting_export_test.rb @@ -19,7 +19,7 @@ class Exports::AccountingExportTest < ActionDispatch::IntegrationTest encoding: 'ISO-8859-1', date_format: '%d/%m/%Y', start_date: '2012-03-12T00:00:00.000Z', - end_date: DateTime.now.utc.iso8601, + end_date: DateTime.current.utc.iso8601, label_max_length: 50, decimal_separator: ',', export_invoices_at_zero: false diff --git a/test/integration/invoices/as_admin_test.rb b/test/integration/invoices/as_admin_test.rb index bee8de4ec..15e9cb993 100644 --- a/test/integration/invoices/as_admin_test.rb +++ b/test/integration/invoices/as_admin_test.rb @@ -33,7 +33,7 @@ class InvoicesTest < ActionDispatch::IntegrationTest end test 'admin generates a refund' do - date = DateTime.now.iso8601 + date = DateTime.current.iso8601 post '/api/invoices', { avoir: { avoir_date: date, diff --git a/test/integration/subscriptions/renew_as_user_test.rb b/test/integration/subscriptions/renew_as_user_test.rb index 6ccdb8b17..117efac12 100644 --- a/test/integration/subscriptions/renew_as_user_test.rb +++ b/test/integration/subscriptions/renew_as_user_test.rb @@ -37,14 +37,14 @@ class Subscriptions::RenewAsUserTest < ActionDispatch::IntegrationTest assert_not_nil @user.subscription, "user's subscription was not found" # Check the expiration date - assert @user.subscription.expired_at > DateTime.now, + assert @user.subscription.expired_at > DateTime.current, "user's subscription expiration was not updated ... VCR cassettes may be outdated, please check the gitlab wiki" assert_equal @user.subscription.expired_at.iso8601, (@user.subscription.created_at + plan.duration).iso8601, 'subscription expiration date does not match' assert_in_delta 5, - (DateTime.now.to_i - @user.subscription.updated_at.to_i), + (DateTime.current.to_i - @user.subscription.updated_at.to_i), 10, "user's subscription was not updated recently" diff --git a/test/integration/wallets_test.rb b/test/integration/wallets_test.rb index 06ca437c9..fa3f6f47a 100644 --- a/test/integration/wallets_test.rb +++ b/test/integration/wallets_test.rb @@ -83,7 +83,7 @@ class WalletsTest < ActionDispatch::IntegrationTest login_as(admin, scope: :user) w = @vlonchamp.wallet amount = 10 - avoir_date = Time.now.end_of_day + avoir_date = DateTime.current.end_of_day expected_amount = w.amount + amount put "/api/wallet/#{w.id}/credit", amount: amount, diff --git a/test/models/coupon_test.rb b/test/models/coupon_test.rb index 1c8ade1a5..6f7efa052 100644 --- a/test/models/coupon_test.rb +++ b/test/models/coupon_test.rb @@ -3,7 +3,7 @@ require 'test_helper' class CouponTest < ActiveSupport::TestCase test 'valid coupon with percentage' do - c = Coupon.new({name: 'Hot deals', code: 'HOT15', percent_off: 15, validity_per_user: 'once', valid_until: (Time.now + 2.weeks), max_usages: 100, active: true}) + c = Coupon.new({name: 'Hot deals', code: 'HOT15', percent_off: 15, validity_per_user: 'once', valid_until: (DateTime.current + 2.weeks), max_usages: 100, active: true}) assert c.valid? assert_equal 'active', c.status, 'Invalid coupon status' assert_equal 'percent_off', c.type, 'Invalid coupon type' diff --git a/test/test_helper.rb b/test/test_helper.rb index 6081adc5e..e26a7a98b 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -39,7 +39,7 @@ class ActiveSupport::TestCase def stripe_payment_method(error: nil) number = '4242424242424242' exp_month = 4 - exp_year = DateTime.now.next_year.year + exp_year = DateTime.current.next_year.year cvc = '314' case error From cca6b14f58a1ae5cc97a8567af0ccec3b7ca71e9 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 2 Dec 2019 15:53:24 +0100 Subject: [PATCH 053/135] import events asyncronously from icalendar --- app/assets/javascripts/services/icalendar.js | 2 +- app/controllers/api/i_calendar_controller.rb | 20 +++--------- app/models/i_calendar.rb | 10 ++++++ app/models/i_calendar_event.rb | 6 ++++ app/services/i_calendar_import_service.rb | 31 +++++++++++++++++++ app/views/api/i_calendar/events.json.jbuilder | 8 ++--- app/workers/i_calendar_import_worker.rb | 14 +++++++++ config/routes.rb | 2 +- config/schedule.yml | 5 +++ ...20191202135507_create_i_calendar_events.rb | 15 +++++++++ db/schema.rb | 17 +++++++++- test/fixtures/i_calendar_events.yml | 19 ++++++++++++ test/models/i_calendar_test.rb | 7 ----- 13 files changed, 126 insertions(+), 30 deletions(-) create mode 100644 app/models/i_calendar_event.rb create mode 100644 app/services/i_calendar_import_service.rb create mode 100644 app/workers/i_calendar_import_worker.rb create mode 100644 db/migrate/20191202135507_create_i_calendar_events.rb create mode 100644 test/fixtures/i_calendar_events.yml delete mode 100644 test/models/i_calendar_test.rb diff --git a/app/assets/javascripts/services/icalendar.js b/app/assets/javascripts/services/icalendar.js index 1e01bd5e3..93c790dab 100644 --- a/app/assets/javascripts/services/icalendar.js +++ b/app/assets/javascripts/services/icalendar.js @@ -5,7 +5,7 @@ Application.Services.factory('ICalendar', ['$resource', function ($resource) { { id: '@id' }, { events: { method: 'GET', - url: '/api/i_calendar/events' + url: '/api/i_calendar/:id/events' }, sync: { method: 'POST', diff --git a/app/controllers/api/i_calendar_controller.rb b/app/controllers/api/i_calendar_controller.rb index c506507fe..5e17224f6 100644 --- a/app/controllers/api/i_calendar_controller.rb +++ b/app/controllers/api/i_calendar_controller.rb @@ -27,25 +27,13 @@ class API::ICalendarController < API::ApiController end def events - require 'net/http' - require 'uri' - require 'icalendar' - - @events = [] - - @i_cals = ICalendar.all.each do |i_cal| - ics = Net::HTTP.get(URI.parse(i_cal.url)) - cals = Icalendar::Calendar.parse(ics) - - cals.first.events.each do |evt| - @events.push(calendar: i_cal, event: evt) - end - end + @events = ICalendarEvent.where(i_calendar_id: params[:id]).joins(:i_calendar) end def sync - puts '[TODO] run worker' - render json: { processing: true }, status: :created + worker = ICalendarImportWorker.new + worker.perform([params[:id]]) + render json: { processing: [params[:id]] }, status: :created end private diff --git a/app/models/i_calendar.rb b/app/models/i_calendar.rb index 17d630cae..d670e4986 100644 --- a/app/models/i_calendar.rb +++ b/app/models/i_calendar.rb @@ -2,4 +2,14 @@ # iCalendar (RFC 5545) files, stored by URL and kept with their display configuration class ICalendar < ActiveRecord::Base + has_many :i_calendar_events + + after_create sync_events + + private + + def sync_events + worker = ICalendarImportWorker.new + worker.perform([id]) + end end diff --git a/app/models/i_calendar_event.rb b/app/models/i_calendar_event.rb new file mode 100644 index 000000000..b7b4897d0 --- /dev/null +++ b/app/models/i_calendar_event.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +# iCalendar (RFC 5545) event, belonging to an ICalendar object (its source) +class ICalendarEvent < ActiveRecord::Base + belongs_to :i_calendar +end diff --git a/app/services/i_calendar_import_service.rb b/app/services/i_calendar_import_service.rb new file mode 100644 index 000000000..06b122bfe --- /dev/null +++ b/app/services/i_calendar_import_service.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +# Import all events from a given remote RFC 5545 iCalendar +class ICalendarImportService + def import(i_calendar_id) + require 'net/http' + require 'uri' + require 'icalendar' + + events = [] + + i_cal = ICalendar.find(i_calendar_id) + ics = Net::HTTP.get(URI.parse(i_cal.url)) + cals = Icalendar::Calendar.parse(ics) + + cals.each do |cal| + cal.events.each do |evt| + events.push( + uid: evt.uid, + dtstart: evt.dtstart, + dtend: evt.dtend, + summary: evt.summary, + description: evt.description, + i_calendar_id: i_calendar_id + ) + end + end + + ICalendarEvent.create!(events) + end +end diff --git a/app/views/api/i_calendar/events.json.jbuilder b/app/views/api/i_calendar/events.json.jbuilder index 2029e8558..725d6f0f0 100644 --- a/app/views/api/i_calendar/events.json.jbuilder +++ b/app/views/api/i_calendar/events.json.jbuilder @@ -1,9 +1,9 @@ # frozen_string_literal: true json.array!(@events) do |event| - json.id event[:event].uid - json.title event[:calendar].text_hidden ? '' : event[:event].summary - json.start event[:event].dtstart.iso8601 - json.end event[:event].dtend.iso8601 + json.id event.uid + json.title event.i_calendar.text_hidden ? '' : event.summary + json.start event.dtstart.iso8601 + json.end event.dtend.iso8601 json.backgroundColor 'white' end diff --git a/app/workers/i_calendar_import_worker.rb b/app/workers/i_calendar_import_worker.rb new file mode 100644 index 000000000..cf571eaba --- /dev/null +++ b/app/workers/i_calendar_import_worker.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# Periodically import the iCalendar RFC 5545 events from the configured source +class ICalendarImportWorker + include Sidekiq::Worker + + def perform(calendar_ids = ICalendar.all.map(&:id)) + service = ICalendarImportService.new + + calendar_ids.each do |id| + service.import(id) + end + end +end diff --git a/config/routes.rb b/config/routes.rb index a8f97dcbc..f3f505121 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -113,7 +113,7 @@ Rails.application.routes.draw do end resources :i_calendar, only: %i[index create destroy] do - get 'events', on: :collection + get 'events', on: :member post 'sync', on: :member end diff --git a/config/schedule.yml b/config/schedule.yml index f2a8e114e..d89fe27cf 100644 --- a/config/schedule.yml +++ b/config/schedule.yml @@ -15,6 +15,11 @@ generate_statistic: class: "StatisticWorker" queue: default +i_calendar_import: + cron: "0 2 * * *" + class: "ICalendarImportWorker" + queue: default + open_api_trace_calls_count: cron: "0 4 * * 0" # every sunday at 4am class: "OpenAPITraceCallsCountWorker" diff --git a/db/migrate/20191202135507_create_i_calendar_events.rb b/db/migrate/20191202135507_create_i_calendar_events.rb new file mode 100644 index 000000000..748349cd5 --- /dev/null +++ b/db/migrate/20191202135507_create_i_calendar_events.rb @@ -0,0 +1,15 @@ +class CreateICalendarEvents < ActiveRecord::Migration + def change + create_table :i_calendar_events do |t| + t.string :uid + t.datetime :dtstart + t.datetime :dtend + t.string :summary + t.string :description + t.string :attendee + t.belongs_to :i_calendar, index: true, foreign_key: true + + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 30b47937e..1d370074c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20191127153729) do +ActiveRecord::Schema.define(version: 20191202135507) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -251,6 +251,20 @@ ActiveRecord::Schema.define(version: 20191127153729) do add_index "history_values", ["invoicing_profile_id"], name: "index_history_values_on_invoicing_profile_id", using: :btree add_index "history_values", ["setting_id"], name: "index_history_values_on_setting_id", using: :btree + create_table "i_calendar_events", force: :cascade do |t| + t.string "uid" + t.datetime "dtstart" + t.datetime "dtend" + t.string "summary" + t.string "description" + t.string "attendee" + t.integer "i_calendar_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "i_calendar_events", ["i_calendar_id"], name: "index_i_calendar_events_on_i_calendar_id", using: :btree + create_table "i_calendars", force: :cascade do |t| t.string "url" t.string "name" @@ -933,6 +947,7 @@ ActiveRecord::Schema.define(version: 20191127153729) do add_foreign_key "exports", "users" add_foreign_key "history_values", "invoicing_profiles" add_foreign_key "history_values", "settings" + add_foreign_key "i_calendar_events", "i_calendars" add_foreign_key "invoices", "coupons" add_foreign_key "invoices", "invoicing_profiles" add_foreign_key "invoices", "invoicing_profiles", column: "operator_profile_id" diff --git a/test/fixtures/i_calendar_events.yml b/test/fixtures/i_calendar_events.yml new file mode 100644 index 000000000..490aa79a1 --- /dev/null +++ b/test/fixtures/i_calendar_events.yml @@ -0,0 +1,19 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + uid: MyString + dtstart: 2019-12-02 14:55:07 + dtend: 2019-12-02 14:55:07 + summary: MyString + description: MyString + attendee: MyString + i_calendar_id: + +two: + uid: MyString + dtstart: 2019-12-02 14:55:07 + dtend: 2019-12-02 14:55:07 + summary: MyString + description: MyString + attendee: MyString + i_calendar_id: diff --git a/test/models/i_calendar_test.rb b/test/models/i_calendar_test.rb deleted file mode 100644 index 0ab530e7c..000000000 --- a/test/models/i_calendar_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class ICalendarTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end From 22be9f6a08fa041b09f2d0f6c786df910551991e Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 2 Dec 2019 16:49:20 +0100 Subject: [PATCH 054/135] display external calendars list in public calendar + pull availabilities --- .../javascripts/controllers/calendar.js | 41 ++++++++++++------- app/assets/javascripts/router.js.erb | 1 + app/assets/templates/calendar/filter.html.erb | 11 +++++ app/controllers/api/i_calendar_controller.rb | 7 +++- app/models/i_calendar.rb | 2 +- config/locales/app.public.fr.yml | 1 + 6 files changed, 47 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/controllers/calendar.js b/app/assets/javascripts/controllers/calendar.js index 33d811f55..ce0e1298d 100644 --- a/app/assets/javascripts/controllers/calendar.js +++ b/app/assets/javascripts/controllers/calendar.js @@ -16,8 +16,8 @@ * Controller used in the public calendar global */ -Application.Controllers.controller('CalendarController', ['$scope', '$state', '$aside', 'moment', 'Availability', 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise', 'spacesPromise', - function ($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise, spacesPromise) { +Application.Controllers.controller('CalendarController', ['$scope', '$state', '$aside', 'moment', 'Availability', 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise', 'spacesPromise', 'iCalendarPromise', + function ($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise, spacesPromise, iCalendarPromise) { /* PRIVATE STATIC CONSTANTS */ let currentMachineEvent = null; machinesPromise.forEach(m => m.checked = true); @@ -38,12 +38,11 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ // List of spaces $scope.spaces = spacesPromise.filter(t => !t.disabled); + // List of external iCalendar sources + $scope.externals = iCalendarPromise; + // add availabilities source to event sources $scope.eventSources = []; - $scope.eventSources.push({ - url: '/api/i_calendar/events', - textColor: 'black' - }); // filter availabilities if have change $scope.filterAvailabilities = function (filter, scope) { @@ -52,10 +51,19 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ trainings: isSelectAll('trainings', scope), machines: isSelectAll('machines', scope), spaces: isSelectAll('spaces', scope), + externals: isSelectAll('externals', scope), evt: filter.evt, dispo: filter.dispo }); - return $scope.calendarConfig.events = availabilitySourceUrl(); + $scope.calendarConfig.events = availabilitySourceUrl(); + $scope.externals.filter(e => e.checked).forEach(e => { + $scope.eventSources.push({ + url: `/api/i_calendar/${e.id}/events`, + textColor: e.textColor, + color: e.color + }); + }); + return uiCalendarConfig.calendars.calendar.fullCalendar('refetchEvents'); }; // a variable for formation/machine/event/dispo checkbox is or not checked @@ -63,6 +71,7 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ trainings: isSelectAll('trainings', $scope), machines: isSelectAll('machines', $scope), spaces: isSelectAll('spaces', $scope), + externals: isSelectAll('externals', $scope), evt: true, dispo: true }; @@ -89,6 +98,9 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ spaces () { return $scope.spaces; }, + externals () { + return $scope.externals; + }, filter () { return $scope.filter; }, @@ -99,10 +111,11 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ return $scope.filterAvailabilities; } }, - controller: ['$scope', '$uibModalInstance', 'trainings', 'machines', 'spaces', 'filter', 'toggleFilter', 'filterAvailabilities', function ($scope, $uibModalInstance, trainings, machines, spaces, filter, toggleFilter, filterAvailabilities) { + controller: ['$scope', '$uibModalInstance', 'trainings', 'machines', 'spaces', 'externals', 'filter', 'toggleFilter', 'filterAvailabilities', function ($scope, $uibModalInstance, trainings, machines, spaces, externals, filter, toggleFilter, filterAvailabilities) { $scope.trainings = trainings; $scope.machines = machines; $scope.spaces = spaces; + $scope.externals = externals; $scope.filter = filter; $scope.toggleFilter = (type, filter) => toggleFilter(type, filter); @@ -124,19 +137,19 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ if (event.available_type === 'machines') { currentMachineEvent = event; calendar.fullCalendar('changeView', 'agendaDay'); - return calendar.fullCalendar('gotoDate', event.start); + calendar.fullCalendar('gotoDate', event.start); } else if (event.available_type === 'space') { calendar.fullCalendar('changeView', 'agendaDay'); - return calendar.fullCalendar('gotoDate', event.start); + calendar.fullCalendar('gotoDate', event.start); } else if (event.available_type === 'event') { - return $state.go('app.public.events_show', { id: event.event_id }); + $state.go('app.public.events_show', { id: event.event_id }); } else if (event.available_type === 'training') { - return $state.go('app.public.training_show', { id: event.training_id }); + $state.go('app.public.training_show', { id: event.training_id }); } else { if (event.machine_id) { - return $state.go('app.public.machines_show', { id: event.machine_id }); + $state.go('app.public.machines_show', { id: event.machine_id }); } else if (event.space_id) { - return $state.go('app.public.space_show', { id: event.space_id }); + $state.go('app.public.space_show', { id: event.space_id }); } } }; diff --git a/app/assets/javascripts/router.js.erb b/app/assets/javascripts/router.js.erb index 62503a9cf..7ebc08af3 100644 --- a/app/assets/javascripts/router.js.erb +++ b/app/assets/javascripts/router.js.erb @@ -638,6 +638,7 @@ angular.module('application.router', ['ui.router']) trainingsPromise: ['Training', function (Training) { return Training.query().$promise; }], machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }], spacesPromise: ['Space', function (Space) { return Space.query().$promise; }], + iCalendarPromise: ['ICalendar', function (ICalendar) { return ICalendar.query().$promise; }], translations: ['Translations', function (Translations) { return Translations.query(['app.public.calendar']).$promise; }] } }) diff --git a/app/assets/templates/calendar/filter.html.erb b/app/assets/templates/calendar/filter.html.erb index c275fbeb9..76172a3a5 100644 --- a/app/assets/templates/calendar/filter.html.erb +++ b/app/assets/templates/calendar/filter.html.erb @@ -36,3 +36,14 @@

    {{ 'calendar.show_unavailables' }}

    +
    +
    +

    {{ 'calendar.externals' }}

    + +
    + +
    + {{::e.name}} + +
    +
    diff --git a/app/controllers/api/i_calendar_controller.rb b/app/controllers/api/i_calendar_controller.rb index 5e17224f6..b828b709d 100644 --- a/app/controllers/api/i_calendar_controller.rb +++ b/app/controllers/api/i_calendar_controller.rb @@ -27,7 +27,12 @@ class API::ICalendarController < API::ApiController end def events - @events = ICalendarEvent.where(i_calendar_id: params[:id]).joins(:i_calendar) + start_date = ActiveSupport::TimeZone[params[:timezone]]&.parse(params[:start]) + end_date = ActiveSupport::TimeZone[params[:timezone]]&.parse(params[:end])&.end_of_day + + @events = ICalendarEvent.where(i_calendar_id: params[:id]) + .where('dtstart >= ? AND dtend <= ?', start_date, end_date) + .joins(:i_calendar) end def sync diff --git a/app/models/i_calendar.rb b/app/models/i_calendar.rb index d670e4986..1f63f6129 100644 --- a/app/models/i_calendar.rb +++ b/app/models/i_calendar.rb @@ -4,7 +4,7 @@ class ICalendar < ActiveRecord::Base has_many :i_calendar_events - after_create sync_events + after_create :sync_events private diff --git a/config/locales/app.public.fr.yml b/config/locales/app.public.fr.yml index 522562d10..c5493ad5f 100644 --- a/config/locales/app.public.fr.yml +++ b/config/locales/app.public.fr.yml @@ -306,6 +306,7 @@ fr: machines: "Machines" spaces: "Espaces" events: "Évènements" + externals: "Autres calendriers" spaces_list: # liste des espaces From 122ff54cd806aa96c06890809734842ffd832b74 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 3 Dec 2019 10:17:29 +0100 Subject: [PATCH 055/135] fix colors in ics imports --- .../javascripts/controllers/admin/calendar.js.erb | 14 +++++++------- app/assets/javascripts/controllers/calendar.js | 2 +- app/assets/templates/admin/calendar/icalendar.html | 6 +++--- app/models/i_calendar.rb | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/controllers/admin/calendar.js.erb b/app/assets/javascripts/controllers/admin/calendar.js.erb index 71fbd47b3..2ae01b869 100644 --- a/app/assets/javascripts/controllers/admin/calendar.js.erb +++ b/app/assets/javascripts/controllers/admin/calendar.js.erb @@ -770,10 +770,10 @@ Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCale // configuration of a new ICS source $scope.newCalendar = { color: undefined, - textColor: undefined, + text_color: undefined, url: undefined, name: undefined, - textHidden: false + text_hidden: false }; /** @@ -786,8 +786,8 @@ Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCale $scope.newCalendar.url = undefined; $scope.newCalendar.name = undefined; $scope.newCalendar.color = null; - $scope.newCalendar.textColor = null; - $scope.newCalendar.textHidden = false; + $scope.newCalendar.text_color = null; + $scope.newCalendar.text_hidden = false; }, function (error) { // failed growl.error(_t('icalendar.create_error')); @@ -802,9 +802,9 @@ Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCale $scope.calendarStyle = function (calendar) { return { 'border-color': calendar.color, - 'color': calendar.textColor, - 'width': calendar.textHidden ? '50px' : 'auto', - 'height': calendar.textHidden ? '21px' : 'auto' + 'color': calendar.text_color, + 'width': calendar.text_hidden ? '50px' : 'auto', + 'height': calendar.text_hidden ? '21px' : 'auto' }; } diff --git a/app/assets/javascripts/controllers/calendar.js b/app/assets/javascripts/controllers/calendar.js index ce0e1298d..fa5670550 100644 --- a/app/assets/javascripts/controllers/calendar.js +++ b/app/assets/javascripts/controllers/calendar.js @@ -59,7 +59,7 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ $scope.externals.filter(e => e.checked).forEach(e => { $scope.eventSources.push({ url: `/api/i_calendar/${e.id}/events`, - textColor: e.textColor, + textColor: e.text_color, color: e.color }); }); diff --git a/app/assets/templates/admin/calendar/icalendar.html b/app/assets/templates/admin/calendar/icalendar.html index 6c9411d48..c423e1aac 100644 --- a/app/assets/templates/admin/calendar/icalendar.html +++ b/app/assets/templates/admin/calendar/icalendar.html @@ -33,7 +33,7 @@ {{ 'icalendar.name' }} {{ 'icalendar.url' }} {{ 'icalendar.display' }} - + @@ -72,12 +72,12 @@
    - +
    Date: Tue, 3 Dec 2019 10:23:19 +0100 Subject: [PATCH 056/135] external calendars legend --- app/assets/javascripts/controllers/calendar.js | 11 +++++++++++ app/assets/stylesheets/modules/icalendar.scss | 5 +++++ app/assets/templates/calendar/filter.html.erb | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/controllers/calendar.js b/app/assets/javascripts/controllers/calendar.js index fa5670550..f307a9e35 100644 --- a/app/assets/javascripts/controllers/calendar.js +++ b/app/assets/javascripts/controllers/calendar.js @@ -66,6 +66,17 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ return uiCalendarConfig.calendars.calendar.fullCalendar('refetchEvents'); }; + /** + * Return a CSS-like style of the given calendar configuration + * @param calendar + */ + $scope.calendarStyle = function (calendar) { + return { + 'border-color': calendar.color, + 'color': calendar.text_color, + }; + }; + // a variable for formation/machine/event/dispo checkbox is or not checked $scope.filter = { trainings: isSelectAll('trainings', $scope), diff --git a/app/assets/stylesheets/modules/icalendar.scss b/app/assets/stylesheets/modules/icalendar.scss index 7c852fcd6..d97ed60f7 100644 --- a/app/assets/stylesheets/modules/icalendar.scss +++ b/app/assets/stylesheets/modules/icalendar.scss @@ -17,3 +17,8 @@ .calendar-url { overflow: hidden; } + +.external-calendar-legend { + border-left: 3px solid; + border-radius: 3px; +} \ No newline at end of file diff --git a/app/assets/templates/calendar/filter.html.erb b/app/assets/templates/calendar/filter.html.erb index 76172a3a5..8db10fd89 100644 --- a/app/assets/templates/calendar/filter.html.erb +++ b/app/assets/templates/calendar/filter.html.erb @@ -43,7 +43,7 @@
    - {{::e.name}} + {{::e.name}}
    From 32e7fc390049320b017a0d579cb52e3a81c0bb97 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 3 Dec 2019 10:48:53 +0100 Subject: [PATCH 057/135] confirm delete icalendar imports + fix display + add translations --- .../controllers/admin/calendar.js.erb | 40 +++++++++++++------ .../templates/admin/calendar/icalendar.html | 4 +- app/assets/templates/calendar/filter.html.erb | 2 +- config/locales/app.admin.en.yml | 22 ++++++++++ config/locales/app.admin.es.yml | 22 ++++++++++ config/locales/app.admin.fr.yml | 3 ++ config/locales/app.admin.pt.yml | 22 ++++++++++ 7 files changed, 100 insertions(+), 15 deletions(-) diff --git a/app/assets/javascripts/controllers/admin/calendar.js.erb b/app/assets/javascripts/controllers/admin/calendar.js.erb index 2ae01b869..220363e4d 100644 --- a/app/assets/javascripts/controllers/admin/calendar.js.erb +++ b/app/assets/javascripts/controllers/admin/calendar.js.erb @@ -762,8 +762,8 @@ Application.Controllers.controller('DeleteRecurrentAvailabilityController', ['$s * Controller used in the iCalendar (ICS) imports management page */ -Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCalendars', 'ICalendar', 'growl', '_t', - function ($scope, iCalendars, ICalendar, growl, _t) { +Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCalendars', 'ICalendar', 'dialogs', 'growl', '_t', + function ($scope, iCalendars, ICalendar, dialogs, growl, _t) { // list of ICS sources $scope.calendars = iCalendars; @@ -813,17 +813,33 @@ Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCale * @param calendar */ $scope.delete = function (calendar) { - ICalendar.delete( - { id: calendar.id }, + dialogs.confirm( + { + resolve: { + object () { + return { + title: _t('icalendar.confirmation_required'), + msg: _t('icalendar.confirm_delete_import') + }; + } + } + }, function () { - // success - const idx = $scope.calendars.indexOf(calendar); - $scope.calendars.splice(idx, 1); - }, function (error) { - // failed - growl.error(_t('icalendar.delete_failed')); - console.error(error); - }); + ICalendar.delete( + { id: calendar.id }, + function () { + // success + const idx = $scope.calendars.indexOf(calendar); + $scope.calendars.splice(idx, 1); + growl.info(_t('icalendar.delete_success')); + }, function (error) { + // failed + growl.error(_t('icalendar.delete_failed')); + console.error(error); + } + ); + } + ) } /** diff --git a/app/assets/templates/admin/calendar/icalendar.html b/app/assets/templates/admin/calendar/icalendar.html index c423e1aac..a91d5090a 100644 --- a/app/assets/templates/admin/calendar/icalendar.html +++ b/app/assets/templates/admin/calendar/icalendar.html @@ -40,7 +40,7 @@ {{calendar.name}} {{calendar.url}} - {{ calendar.textHidden ? '' : 'icalendar.example' }} + {{ calendar.text_hidden ? '' : 'icalendar.example' }} @@ -72,7 +72,7 @@
    - +
    diff --git a/app/assets/templates/calendar/filter.html.erb b/app/assets/templates/calendar/filter.html.erb index 8db10fd89..7242f3c47 100644 --- a/app/assets/templates/calendar/filter.html.erb +++ b/app/assets/templates/calendar/filter.html.erb @@ -36,7 +36,7 @@

    {{ 'calendar.show_unavailables' }}

    -
    +

    {{ 'calendar.externals' }}

    diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index 960196bbc..261408460 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -88,6 +88,28 @@ en: view_reservations: "View reservations" legend: "legend" + icalendar: + icalendar: + icalendar_import: "iCalendar import" + intro: "Fab-manager allows to automatically import calendar events, at RFC 5545 iCalendar format, from external URL. These URL are synchronized every nights and the events are shown in the public calendar." + new_import: "New ICS import" + color: "Colour" + text_color: "Text colour" + url: "URL" + name: "Name" + example: "Example" + display: "Display" + hide_text: "Hide the text" + hidden: "Hidden" + shown: "Shown" + create_error: "Unable to create iCalendar import. Please try again later" + delete_failed: "Unable to delete the iCalendar import. Please try again later" + refresh: "Updating..." + sync_failed: "Unable to synchronize the URL. Please try again later" + confirmation_required: "Confirmation required" + confirm_delete_import: "Do you really want to delete this iCalendar import?" + delete_success: "iCalendar import successfully deleted" + project_elements: # management of the projects' components project_elements: diff --git a/config/locales/app.admin.es.yml b/config/locales/app.admin.es.yml index ed24b3296..fb62110e7 100644 --- a/config/locales/app.admin.es.yml +++ b/config/locales/app.admin.es.yml @@ -88,6 +88,28 @@ es: view_reservations: "Ver reservas" # translation_missing legend: "leyenda" + icalendar: + icalendar: + icalendar_import: "iCalendar import" # translation_missing + intro: "Fab-manager allows to automatically import calendar events, at RFC 5545 iCalendar format, from external URL. These URL are synchronized every nights and the events are shown in the public calendar." # translation_missing + new_import: "New ICS import" # translation_missing + color: "Colour" # translation_missing + text_color: "Text colour" # translation_missing + url: "URL" # translation_missing + name: "Name" # translation_missing + example: "Example" # translation_missing + display: "Display" # translation_missing + hide_text: "Hide the text" # translation_missing + hidden: "Hidden" # translation_missing + shown: "Shown" # translation_missing + create_error: "Unable to create iCalendar import. Please try again later" # translation_missing + delete_failed: "Unable to delete the iCalendar import. Please try again later" # translation_missing + refresh: "Updating..." # translation_missing + sync_failed: "Unable to synchronize the URL. Please try again later" # translation_missing + confirmation_required: "Confirmation required" # translation_missing + confirm_delete_import: "Do you really want to delete this iCalendar import?" # translation_missing + delete_success: "iCalendar import successfully deleted" # translation_missing + project_elements: # management of the projects' components project_elements: diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index e5218afc6..4a5108f15 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -106,6 +106,9 @@ fr: delete_failed: "Impossible de supprimer l'import iCalendar. Veuillez réessayer ultérieurement" refresh: "Mise à jour en cours..." sync_failed: "Impossible de synchroniser l'URL. Veuillez réessayer ultérieurement" + confirmation_required: "Confirmation requise" + confirm_delete_import: "Êtes-vous sur de vouloir supprimer cet import iCalendar ?" + delete_success: "L'import iCalendar a bien été supprimé" project_elements: # gestion des éléments constituant les projets diff --git a/config/locales/app.admin.pt.yml b/config/locales/app.admin.pt.yml index d6795c2b4..a908d7da0 100755 --- a/config/locales/app.admin.pt.yml +++ b/config/locales/app.admin.pt.yml @@ -88,6 +88,28 @@ pt: view_reservations: "Ver reservas" # translation_missing legend: "legenda" + icalendar: + icalendar: + icalendar_import: "iCalendar import" # translation_missing + intro: "Fab-manager allows to automatically import calendar events, at RFC 5545 iCalendar format, from external URL. These URL are synchronized every nights and the events are shown in the public calendar." # translation_missing + new_import: "New ICS import" # translation_missing + color: "Colour" # translation_missing + text_color: "Text colour" # translation_missing + url: "URL" # translation_missing + name: "Name" # translation_missing + example: "Example" # translation_missing + display: "Display" # translation_missing + hide_text: "Hide the text" # translation_missing + hidden: "Hidden" # translation_missing + shown: "Shown" # translation_missing + create_error: "Unable to create iCalendar import. Please try again later" # translation_missing + delete_failed: "Unable to delete the iCalendar import. Please try again later" # translation_missing + refresh: "Updating..." # translation_missing + sync_failed: "Unable to synchronize the URL. Please try again later" # translation_missing + confirmation_required: "Confirmation required" # translation_missing + confirm_delete_import: "Do you really want to delete this iCalendar import?" # translation_missing + delete_success: "iCalendar import successfully deleted" # translation_missing + project_elements: # management of the projects' components project_elements: From 538b5cef7839194bad145a5a6818558b341ae837 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 3 Dec 2019 11:27:20 +0100 Subject: [PATCH 058/135] add/remove ical imports in public agenda also: a little of refacftoring in CalendarController --- .../javascripts/controllers/calendar.js | 111 ++++++++++++------ config/locales/app.public.en.yml | 1 + config/locales/app.public.es.yml | 1 + config/locales/app.public.pt.yml | 1 + 4 files changed, 76 insertions(+), 38 deletions(-) diff --git a/app/assets/javascripts/controllers/calendar.js b/app/assets/javascripts/controllers/calendar.js index f307a9e35..4f60257c5 100644 --- a/app/assets/javascripts/controllers/calendar.js +++ b/app/assets/javascripts/controllers/calendar.js @@ -39,7 +39,7 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ $scope.spaces = spacesPromise.filter(t => !t.disabled); // List of external iCalendar sources - $scope.externals = iCalendarPromise; + $scope.externals = iCalendarPromise.map(e => Object.assign(e, { checked: true })); // add availabilities source to event sources $scope.eventSources = []; @@ -56,14 +56,25 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ dispo: filter.dispo }); $scope.calendarConfig.events = availabilitySourceUrl(); - $scope.externals.filter(e => e.checked).forEach(e => { - $scope.eventSources.push({ - url: `/api/i_calendar/${e.id}/events`, - textColor: e.text_color, - color: e.color - }); + // external iCalendar events sources + $scope.externals.forEach(e => { + if (e.checked) { + if (!$scope.eventSources.some(evt => evt.id === e.id)) { + $scope.eventSources.push({ + id: e.id, + url: `/api/i_calendar/${e.id}/events`, + textColor: e.text_color || '#000', + color: e.color + }); + } + } else { + if ($scope.eventSources.some(evt => evt.id === e.id)) { + const idx = $scope.eventSources.findIndex(evt => evt.id === e.id); + $scope.eventSources.splice(idx, 1); + } + } }); - return uiCalendarConfig.calendars.calendar.fullCalendar('refetchEvents'); + uiCalendarConfig.calendars.calendar.fullCalendar('refetchEventSources'); }; /** @@ -73,7 +84,7 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ $scope.calendarStyle = function (calendar) { return { 'border-color': calendar.color, - 'color': calendar.text_color, + 'color': calendar.text_color }; }; @@ -142,8 +153,49 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ /* PRIVATE SCOPE */ + /** + * Kind of constructor: these actions will be realized first when the controller is loaded + */ + const initialize = () => { + // fullCalendar (v2) configuration + $scope.calendarConfig = CalendarConfig({ + events: availabilitySourceUrl(), + slotEventOverlap: true, + header: { + left: 'month agendaWeek agendaDay', + center: 'title', + right: 'today prev,next' + }, + minTime: moment.duration(moment(bookingWindowStart.setting.value).format('HH:mm:ss')), + maxTime: moment.duration(moment(bookingWindowEnd.setting.value).format('HH:mm:ss')), + defaultView: window.innerWidth <= 480 ? 'agendaDay' : 'agendaWeek', + eventClick (event, jsEvent, view) { + return calendarEventClickCb(event, jsEvent, view); + }, + viewRender (view, element) { + return viewRenderCb(view, element); + }, + eventRender (event, element, view) { + return eventRenderCb(event, element); + } + }); + $scope.externals.forEach(e => { + if (e.checked) { + $scope.eventSources.push({ + id: e.id, + url: `/api/i_calendar/${e.id}/events`, + textColor: e.text_color || '#000', + color: e.color + }); + } + }); + }; + + /** + * Callback triggered when an event object is clicked in the fullCalendar view + */ const calendarEventClickCb = function (event, jsEvent, view) { - // current calendar object + // current calendar object const { calendar } = uiCalendarConfig.calendars; if (event.available_type === 'machines') { currentMachineEvent = event; @@ -168,8 +220,8 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ // agendaDay view: disable slotEventOverlap // agendaWeek view: enable slotEventOverlap const toggleSlotEventOverlap = function (view) { - // set defaultView, because when we change slotEventOverlap - // ui-calendar will trigger rerender calendar + // set defaultView, because when we change slotEventOverlap + // ui-calendar will trigger rerender calendar $scope.calendarConfig.defaultView = view.type; const today = currentMachineEvent ? currentMachineEvent.start : moment().utc().startOf('day'); if ((today > view.intervalStart) && (today < view.intervalEnd) && (today !== view.intervalStart)) { @@ -184,15 +236,22 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ } }; - // function is called when calendar view is rendered or changed + /** + * This function is called when calendar view is rendered or changed + * @see https://fullcalendar.io/docs/v3/viewRender#v2 + */ const viewRenderCb = function (view, element) { toggleSlotEventOverlap(view); if (view.type === 'agendaDay') { - // get availabilties by 1 day for show machine slots + // get availabilties by 1 day for show machine slots return uiCalendarConfig.calendars.calendar.fullCalendar('refetchEvents'); } }; + /** + * Callback triggered by fullCalendar when it is about to render an event. + * @see https://fullcalendar.io/docs/v3/eventRender#v2 + */ const eventRenderCb = function (event, element) { if (event.tags && event.tags.length > 0) { let html = ''; @@ -212,30 +271,6 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ var availabilitySourceUrl = () => `/api/availabilities/public?${$.param(getFilter())}`; - const initialize = () => - // fullCalendar (v2) configuration - $scope.calendarConfig = CalendarConfig({ - events: availabilitySourceUrl(), - slotEventOverlap: true, - header: { - left: 'month agendaWeek agendaDay', - center: 'title', - right: 'today prev,next' - }, - minTime: moment.duration(moment(bookingWindowStart.setting.value).format('HH:mm:ss')), - maxTime: moment.duration(moment(bookingWindowEnd.setting.value).format('HH:mm:ss')), - defaultView: window.innerWidth <= 480 ? 'agendaDay' : 'agendaWeek', - eventClick (event, jsEvent, view) { - return calendarEventClickCb(event, jsEvent, view); - }, - viewRender (view, element) { - return viewRenderCb(view, element); - }, - eventRender (event, element, view) { - return eventRenderCb(event, element); - } - }); - // !!! MUST BE CALLED AT THE END of the controller return initialize(); } diff --git a/config/locales/app.public.en.yml b/config/locales/app.public.en.yml index ce9a26e9f..75575446a 100644 --- a/config/locales/app.public.en.yml +++ b/config/locales/app.public.en.yml @@ -306,6 +306,7 @@ en: machines: "Machines" spaces: "Spaces" events: "Events" + externals: "Other calendars" spaces_list: # list of spaces diff --git a/config/locales/app.public.es.yml b/config/locales/app.public.es.yml index 75aa29a37..b5bceebfc 100644 --- a/config/locales/app.public.es.yml +++ b/config/locales/app.public.es.yml @@ -306,6 +306,7 @@ es: machines: "Máquinas" spaces: "Espacios" events: "Eventos" + externals: "Otros calendarios" spaces_list: # list of spaces diff --git a/config/locales/app.public.pt.yml b/config/locales/app.public.pt.yml index ff0fb170f..bd8088c68 100755 --- a/config/locales/app.public.pt.yml +++ b/config/locales/app.public.pt.yml @@ -306,6 +306,7 @@ pt: machines: "Máquinas" spaces: "Espaços" events: "Eventos" + externals: "Outras agendas" spaces_list: # list of spaces From 64fe68b2b0ce0d9e8843565e4280fa1896a162ed Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 3 Dec 2019 12:16:07 +0100 Subject: [PATCH 059/135] icalendar source sync: create/update/delete ical events --- app/controllers/api/i_calendar_controller.rb | 3 +-- app/models/i_calendar_event.rb | 6 +++++ app/services/i_calendar_import_service.rb | 24 ++++++++++++-------- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/app/controllers/api/i_calendar_controller.rb b/app/controllers/api/i_calendar_controller.rb index b828b709d..76cd5beb1 100644 --- a/app/controllers/api/i_calendar_controller.rb +++ b/app/controllers/api/i_calendar_controller.rb @@ -36,8 +36,7 @@ class API::ICalendarController < API::ApiController end def sync - worker = ICalendarImportWorker.new - worker.perform([params[:id]]) + ICalendarImportWorker.perform_async([params[:id]]) render json: { processing: [params[:id]] }, status: :created end diff --git a/app/models/i_calendar_event.rb b/app/models/i_calendar_event.rb index b7b4897d0..bfa940e9b 100644 --- a/app/models/i_calendar_event.rb +++ b/app/models/i_calendar_event.rb @@ -3,4 +3,10 @@ # iCalendar (RFC 5545) event, belonging to an ICalendar object (its source) class ICalendarEvent < ActiveRecord::Base belongs_to :i_calendar + + def self.update_or_create_by(args, attributes) + obj = find_or_create_by(args) + obj.update(attributes) + obj + end end diff --git a/app/services/i_calendar_import_service.rb b/app/services/i_calendar_import_service.rb index 06b122bfe..2664436bd 100644 --- a/app/services/i_calendar_import_service.rb +++ b/app/services/i_calendar_import_service.rb @@ -7,25 +7,29 @@ class ICalendarImportService require 'uri' require 'icalendar' - events = [] + uids = [] i_cal = ICalendar.find(i_calendar_id) ics = Net::HTTP.get(URI.parse(i_cal.url)) cals = Icalendar::Calendar.parse(ics) + # create new events and update existings cals.each do |cal| cal.events.each do |evt| - events.push( - uid: evt.uid, - dtstart: evt.dtstart, - dtend: evt.dtend, - summary: evt.summary, - description: evt.description, - i_calendar_id: i_calendar_id + uids.push(evt.uid.to_s) + ICalendarEvent.update_or_create_by( + { uid: evt.uid.to_s }, + { + dtstart: evt.dtstart, + dtend: evt.dtend, + summary: evt.summary, + description: evt.description, + i_calendar_id: i_calendar_id + } ) end end - - ICalendarEvent.create!(events) + # remove deleted events + ICalendarEvent.where(i_calendar_id: i_calendar_id).where.not(uid: uids).destroy_all end end From 264f10e3e97cd9841732c4d73cde7522feb06ba9 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 3 Dec 2019 15:50:08 +0100 Subject: [PATCH 060/135] updated changelog --- CHANGELOG.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e36c9ad9b..abfc1150c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,9 @@ - Ability to create and delete periodic calendar availabilities (recurrence) - An administrator can delete a member - An event can be cancelled, if reservation cancellation is enabled -- Ability to configure the duration of a reservation slot. Previously, only 60 minutes slots were allowed -- Ability to show the scheduled events in the admin calendar +- Ability to import iCalendar agendas in the public calendar, through URLs to ICS files (RFC 5545) +- Ability to configure the duration of a reservation slot, using `SLOT_DURATION`. Previously, only 60 minutes slots were allowed +- Display the scheduled events in the admin calendar, depending on `EVENTS_IN_CALENDAR` configuration. - Display indications on required fields in new administrator form - Configuration of phone number in members registration forms: can be required or optional, depending on `PHONE_REQUIRED` configuration - Improved user experience in defining slots in the calendar management @@ -14,7 +15,7 @@ - Updated moment-timezone - Added freeCAD files as default allowed extensions - Fix a bug: unable to remove the picture from a training -- Fix a bug: report errors on admin creation +- Fix a bug: no alerts on errors during admin creation - Fix a security issue: updated loofah to fix [CVE-2019-15587](https://github.com/advisories/GHSA-c3gv-9cxf-6f57) - Fix a security issue: updated angular to 1.7.9 to fix [CVE-2019-10768](https://github.com/advisories/GHSA-89mq-4x47-5v83) - [TODO DEPLOY] add the `SLOT_DURATION` environment variable (see [doc/environment.md](doc/environment.md#SLOT_DURATION) for configuration details) From f50e45d2de3bc2aef29144fc295f02545594418b Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 3 Dec 2019 16:32:59 +0100 Subject: [PATCH 061/135] rake task to sync users with stripe --- CHANGELOG.md | 1 + app/workers/stripe_worker.rb | 18 ++++++++---------- doc/environment.md | 3 ++- lib/tasks/fablab/stripe.rake | 12 ++++++++++++ 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 370bd70f7..58b56bddb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Handle Ctrl^C in upgrade scripts - Updated moment-timezone - Added freeCAD files as default allowed extensions +- Rake task to sync local users with Stripe - Fix a bug: unable to remove the picture from a training - Fix a bug: no alerts on errors during admin creation - Fix a bug: replaces all Time.now by DateTime.current to prevent time zones issues [Taiga#134] diff --git a/app/workers/stripe_worker.rb b/app/workers/stripe_worker.rb index d3b8d0d5e..b45f50ce2 100644 --- a/app/workers/stripe_worker.rb +++ b/app/workers/stripe_worker.rb @@ -1,6 +1,9 @@ +# frozen_string_literal: true + +# This worker perform various requests to the Stripe API (payment service) class StripeWorker include Sidekiq::Worker - sidekiq_options :queue => :stripe + sidekiq_options queue: :stripe def perform(action, *params) send(action, *params) @@ -18,8 +21,8 @@ class StripeWorker def create_stripe_coupon(coupon_id) coupon = Coupon.find(coupon_id) stp_coupon = { - id: coupon.code, - duration: coupon.validity_per_user, + id: coupon.code, + duration: coupon.validity_per_user } if coupon.type == 'percent_off' stp_coupon[:percent_off] = coupon.percent_off @@ -28,13 +31,8 @@ class StripeWorker stp_coupon[:currency] = Rails.application.secrets.stripe_currency end - unless coupon.valid_until.nil? - stp_coupon[:redeem_by] = coupon.valid_until.to_i - end - stp_coupon - unless coupon.max_usages.nil? - stp_coupon[:max_redemptions] = coupon.max_usages - end + stp_coupon[:redeem_by] = coupon.valid_until.to_i unless coupon.valid_until.nil? + stp_coupon[:max_redemptions] = coupon.max_usages unless coupon.max_usages.nil? Stripe::Coupon.create(stp_coupon) end diff --git a/doc/environment.md b/doc/environment.md index a3c1fd0ca..23d962d94 100644 --- a/doc/environment.md +++ b/doc/environment.md @@ -61,6 +61,7 @@ Retrieve them from https://dashboard.stripe.com/account/apikeys. **MANDATORY**: Even if you don't want to charge your customers, you must fill this settings. For this purpose, you can use a stripe account in test mode, which will provide you test keys. +If you change these keys during the application lifecycle, you must run `rake fablab:stripe:sync_members`, otherwise your users won't be able to do card payments. STRIPE_CURRENCY @@ -229,7 +230,7 @@ The check will run every weeks and if the threshold is exceeded, an alert will b ADMIN_EMAIL, ADMIN_PASSWORD Credentials for the first admin user created when seeding the project. -By default, theses variables are not present in application.yml because they are only used once, when running the database seed with the command `rake db:seed. +By default, theses variables are not present in application.yml because they are only used once, when running the database seed with the command `rake db:seed`. SUPERADMIN_EMAIL diff --git a/lib/tasks/fablab/stripe.rake b/lib/tasks/fablab/stripe.rake index 6f1249e95..e406b4aee 100644 --- a/lib/tasks/fablab/stripe.rake +++ b/lib/tasks/fablab/stripe.rake @@ -45,5 +45,17 @@ namespace :fablab do File.write(cassette_file, cassette) end end + + desc 'sync users to the stripe database' + task sync_members: :environment do + User.with_role(:member).each do |member| + begin + stp_customer = Stripe::Customer.retrieve member.stp_customer_id + StripeWorker.perform_async(:create_stripe_customer, member.id) if stp_customer.nil? || stp_customer[:deleted] + rescue Stripe::InvalidRequestError + StripeWorker.perform_async(:create_stripe_customer, member.id) + end + end + end end end From 22ae8d5073c414f19ca0a191c70e952e4c736547 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 3 Dec 2019 17:15:32 +0100 Subject: [PATCH 062/135] updated and refactored readme updated instructions to setup a development environment splitted readme into multiple files to improve clarity --- CHANGELOG.md | 1 + README.md | 308 +----------------- doc/development_readme.md | 231 +++++++++++++ .../README.md => doc/docker-compose_readme.md | 0 doc/translation_readme.md | 75 +++++ 5 files changed, 319 insertions(+), 296 deletions(-) create mode 100644 doc/development_readme.md rename docker/README.md => doc/docker-compose_readme.md (100%) create mode 100644 doc/translation_readme.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 58b56bddb..b5271df47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - Updated moment-timezone - Added freeCAD files as default allowed extensions - Rake task to sync local users with Stripe +- Updated and refactored README - Fix a bug: unable to remove the picture from a training - Fix a bug: no alerts on errors during admin creation - Fix a bug: replaces all Time.now by DateTime.current to prevent time zones issues [Taiga#134] diff --git a/README.md b/README.md index 458c69baf..9948f4245 100644 --- a/README.md +++ b/README.md @@ -10,26 +10,13 @@ FabManager is the Fab Lab management solution. It provides a comprehensive, web- 1. [Software stack](#software-stack) 2. [Contributing](#contributing) 3. [Setup a production environment](#setup-a-production-environment) -4. [Setup a development environment](#setup-a-development-environment)
    -4.1. [General Guidelines](#general-guidelines)
    -5. [PostgreSQL](#postgresql)
    -5.1. [Install PostgreSQL 9.6](#setup-postgresql) -6. [ElasticSearch](#elasticsearch)
    -6.1. [Install ElasticSearch](#setup-elasticsearch)
    -6.2. [Rebuild statistics](#rebuild-stats)
    -6.3. [Backup and Restore](#backup-and-restore-elasticsearch) -7. [Internationalization (i18n)](#i18n)
    -7.1. [Translation](#i18n-translation)
    -7.1.1. [Front-end translations](#i18n-translation-front)
    -7.1.2. [Back-end translations](#i18n-translation-back)
    -7.2. [Configuration](#i18n-configuration)
    -7.2.1. [Settings](#i18n-settings)
    -7.2.2. [Applying changes](#i18n-apply) -8. [Open Projects](#open-projects) -9. [Plugins](#plugins) -10. [Single Sign-On](#sso) -11. [Known issues](#known-issues) -12. [Related Documentation](#related-documentation) +4. [Setup a development environment](#setup-a-development-environment) +5. [Internationalization (i18n)](#i18n) +6. [Open Projects](#open-projects) +7. [Plugins](#plugins) +8. [Single Sign-On](#sso) +9. [Known issues](#known-issues) +10. [Related Documentation](#related-documentation) @@ -54,293 +41,22 @@ Contributions are welcome. Please read [the contribution guidelines](CONTRIBUTIN ## Setup a production environment To run fab-manager as a production application, this is highly recommended to use [Docker-compose](https://docs.docker.com/compose/overview/). -The procedure to follow is described in the [docker-compose readme](docker/README.md). +The procedure to follow is described in the [docker-compose readme](doc/docker-compose_readme.md). ## Setup a development environment -In you intend to run fab-manager on your local machine to contribute to the project development, you can set it up with the following procedure. +In you intend to run fab-manager on your local machine to contribute to the project development, you can set it up by following the [development readme](doc/development_readme.md). +This procedure relies on docker to set-up the dependencies. -This procedure is not easy to follow so if you don't need to write some code for Fab-manager, please prefer the [docker-compose installation method](docker/README.md). - -Optionally, you can use a virtual development environment that relies on Vagrant and Virtual Box by following the [virtual machine instructions](doc/virtual-machine.md). - - -### General Guidelines - -1. Install RVM, with the ruby version specified in the [.ruby-version file](.ruby-version). - For more details about the process, please read the [official RVM documentation](http://rvm.io/rvm/install). - If you're using ArchLinux, you may have to [read this](doc/archlinux_readme.md) before. - -2. Install NVM, with the node.js version specified in the [.nvmrc file](.nvmrc). - For instructions about installing NVM, please refer to [the NVM readme](https://github.com/creationix/nvm#installation). - -3. Install Yarn, the front-end package manager. - Depending on your system, the installation process may differ, please read the [official Yarn documentation](https://yarnpkg.com/en/docs/install#debian-stable). - -4. Install docker. - Your system may provide a pre-packaged version of docker in its repositories, but this version may be outdated. - Please refer to [ubuntu](https://docs.docker.com/install/linux/docker-ce/ubuntu/), [debian](https://docs.docker.com/install/linux/docker-ce/debian/) or [MacOS](https://docs.docker.com/docker-for-mac/install/) documentation to setup a recent version of docker. - -5. Add your current user to the docker group, to allow using docker without `sudo`. - ```bash - # add the docker group if it doesn't already exist - sudo groupadd docker - # add the current user to the docker group - sudo usermod -aG docker $(whoami) - # restart to validate changes - sudo reboot - ``` - -6. Create a docker network for fab-manager. - You may have to change the network address if it is already in use. - ```bash - docker network create --subnet=172.18.0.0/16 fabmanager - ``` - -7. Retrieve the project from Git - - ```bash - git clone https://github.com/sleede/fab-manager.git - ``` - -8. Install the software dependencies. - First install [PostgreSQL](#postgresql) and [ElasticSearch](#elasticsearch) as specified in their respective documentations. - Then install the other dependencies: - - For Ubuntu/Debian: - - ```bash - # on Ubuntu 18.04 server, you may have to enable the "universe" repository - sudo add-apt-repository universe - # then, install the dependencies - sudo apt-get install libpq-dev redis-server imagemagick - ``` - - For MacOS X: - - ```bash - brew install redis imagemagick - ``` - -9. Init the RVM and NVM instances and check they were correctly configured - - ```bash - cd fab-manager - rvm current | grep -q `cat .ruby-version`@fab-manager && echo "ok" - # Must print ok - nvm use - node --version | grep -q `cat .nvmrc` && echo "ok" - # Must print ok - ``` - -10. Install bundler in the current RVM gemset - - ```bash - gem install bundler --version=1.17.3 - ``` - -11. Install the required ruby gems and javascript plugins - - ```bash - bundle install - yarn install - ``` - -12. Create the default configuration files **and configure them!** (see the [environment configuration documentation](doc/environment.md)) - - ```bash - cp config/database.yml.default config/database.yml - cp config/application.yml.default config/application.yml - vi config/application.yml - # or use your favorite text editor instead of vi (nano, ne...) - ``` - -13. Build the databases. - - **Warning**: **DO NOT** run `rake db:setup` instead of these commands, as this will not run some required raw SQL instructions. - - **Please note**: Your password length must be between 8 and 128 characters, otherwise db:seed will be rejected. This is configured in [config/initializers/devise.rb](config/initializers/devise.rb) - - ```bash - # for dev - rake db:create - rake db:migrate - ADMIN_EMAIL='youradminemail' ADMIN_PASSWORD='youradminpassword' rake db:seed - rake fablab:es:build_stats - # for tests - RAILS_ENV=test rake db:create - RAILS_ENV=test rake db:migrate - ``` - -14. Create the pids folder used by Sidekiq. If you want to use a different location, you can configure it in `config/sidekiq.yml` - - ```bash - mkdir -p tmp/pids - ``` - -15. Start the development web server - - ```bash - foreman s -p 3000 - ``` - -16. You should now be able to access your local development FabManager instance by accessing `http://localhost:3000` in your web browser. - -17. You can login as the default administrator using the credentials defined previously. - -18. Email notifications will be caught by MailCatcher. - To see the emails sent by the platform, open your web browser at `http://localhost:1080` to access the MailCatcher interface. - - - -## PostgreSQL - - -### Install PostgreSQL 9.6 - -We will use docker to easily install the required version of PostgreSQL. - -1. Create the docker binding folder - ```bash - mkdir -p .docker/postgresql - ``` - -2. Start the PostgreSQL container. - ```bash - docker run --restart=always -d --name fabmanager-postgres \ - -v $(pwd)/.docker/postgresql:/var/lib/postgresql/data \ - --network fabmanager --ip 172.18.0.2 \ - -p 5432:5432 \ - postgres:9.6 - ``` - -3. Configure fab-manager to use it. - On linux systems, PostgreSQL will be available at 172.18.0.2. - On MacOS, you'll have to set the host to 127.0.0.1 (or localhost). - See [environment.md](doc/environment.md) for more details. - -4 . Finally, you may want to have a look at detailed informations about PostgreSQL usage in fab-manager. - Some information about that is available in the [PostgreSQL Readme](doc/postgresql_readme.md). - - -## ElasticSearch - -ElasticSearch is a powerful search engine based on Apache Lucene combined with a NoSQL database used as a cache to index data and quickly process complex requests on it. - -In FabManager, it is used for the admin's statistics module and to perform searches in projects. - - -### Install ElasticSearch - -1. Create the docker binding folders - ```bash - mkdir -p .docker/elasticsearch/config - mkdir -p .docker/elasticsearch/plugins - mkdir -p .docker/elasticsearch/backups - ``` - -2. Copy the default configuration files - ```bash - cp docker/elasticsearch.yml .docker/elasticsearch/config - cp docker/log4j2.properties .docker/elasticsearch/config - ``` - -3. Start the ElasticSearch container. - ```bash - docker run --restart=always -d --name fabmanager-elastic \ - -v $(pwd)/.docker/elasticsearch/config:/usr/share/elasticsearch/config \ - -v $(pwd)/.docker/elasticsearch:/usr/share/elasticsearch/data \ - -v $(pwd)/.docker/elasticsearch/plugins:/usr/share/elasticsearch/plugins \ - -v $(pwd)/.docker/elasticsearch/backups:/usr/share/elasticsearch/backups \ - --network fabmanager --ip 172.18.0.3 \ - -p 9200:9200 -p 9300:9300 \ - elasticsearch:5.6 - ``` - -4. Configure fab-manager to use it. - On linux systems, ElasticSearch will be available at 172.18.0.3. - On MacOS, you'll have to set the host to 127.0.0.1 (or localhost). - See [environment.md](doc/environment.md) for more details. - - -### Rebuild statistics - -Every nights, the statistics for the day that just ended are built automatically at 01:00 (AM) and stored in ElastricSearch. -See [schedule.yml](config/schedule.yml) to modify this behavior. -If the scheduled task wasn't executed for any reason (eg. you are in a dev environment and your computer was turned off at 1 AM), you can force the statistics data generation in ElasticSearch, running the following command. - -```bash -# Here for the 50 last days -rake fablab:es:generate_stats[50] -``` - - -### Backup and Restore - -To backup and restore the ElasticSearch database, use the [elasticsearch-dump](https://github.com/taskrabbit/elasticsearch-dump) tool. - -Dump the database with: `elasticdump --input=http://localhost:9200/stats --output=fablab_stats.json`. -Restore it with: `elasticdump --input=fablab_stats.json --output=http://localhost:9200/stats`. +Optionally, you can use a virtual development environment that relies on Vagrant and Virtual Box by following the [virtual machine instructions](virtual-machine.md). ## Internationalization (i18n) The FabManager application can only run in a single language but this language can easily be changed. - -### Translation - -Check the files located in `config/locales`: - -- Front app translations (angular.js) are located in `config/locales/app.scope.XX.yml`. - Where scope has one the following meaning : - - admin: translations of the administrator views (manage and configure the FabLab). - - logged: translations of the end-user's views accessible only to connected users. - - public: translation of end-user's views publicly accessible to anyone. - - shared: translations shared by many views (like forms or buttons). -- Back app translations (Ruby on Rails) are located in `config/locales/XX.yml`. -- Emails translations are located in `config/locales/mails.XX.yml`. -- Messages related to the authentication system are located in `config/locales/devise.XX.yml`. - -If you plan to translate the application to a new locale, please consider that the reference translation is French. -Indeed, in some cases, the English texts/sentences can seems confuse or lack of context as they were originally translated from French. - -To prevent syntax mistakes while translating locale files, we **STRONGLY advise** you to use a text editor which support syntax coloration for YML and Ruby. - - -#### Front-end translations - -Front-end translations uses [angular-translate](http://angular-translate.github.io) with some interpolations interpreted by angular.js and other interpreted by [MessageFormat](https://github.com/SlexAxton/messageformat.js/). -**These two kinds of interpolation use a near but different syntax witch SHOULD NOT be confused.** -Please refer to the official [angular-translate documentation](http://angular-translate.github.io/docs/#/guide/14_pluralization) before translating. - - -#### Back-end translations - -Back-end translations uses the [Ruby on Rails syntax](http://guides.rubyonrails.org/i18n.html) but some complex interpolations are interpreted by [MessageFormat](https://github.com/format-message/message-format-rb) and are marked as it in comments. -**DO NOT confuse the syntaxes.** - -In each cases, some inline comments are included in the localisation files. -They can be recognized as they start with the sharp character (#). -These comments are not required to be translated, they are intended to help the translator to have some context information about the sentence to translate. - -You will also need to translate the invoice watermark, located in `app/pdfs/data/`. -You'll find there the [GIMP source of the image](app/pdfs/data/watermark.xcf), which is using [Rubik Mono One](https://fonts.google.com/specimen/Rubik+Mono+One) as font. -Use it to generate a similar localised PNG image which keep the default image size, as PDF are not responsive. - - - -### Configuration - -Locales configurations are made in `config/application.yml`. -If you are in a development environment, your can keep the default values, otherwise, in production, values must be configured carefully. - - -#### Settings - -Please refer to the [environment configuration documentation](doc/environment.md#internationalization-settings) - - -#### Applying changes - -After modifying any values concerning the localisation, restart the application (ie. web server) to apply these changes in the i18n configuration. +Please refer to the [translation readme](doc/translation_readme.md) for instructions about configuring the language or to contribute to the translation. ## Open Projects diff --git a/doc/development_readme.md b/doc/development_readme.md new file mode 100644 index 000000000..a3d3af8f3 --- /dev/null +++ b/doc/development_readme.md @@ -0,0 +1,231 @@ +# Install Fab-Manager in a development environment with Docker + +This document will guide you through all the steps needed to set up a development environment for Fab-Manager. + +##### Table of contents + +1. [General Guidelines](#general-guidelines)
    +2. [PostgreSQL](#postgresql)
    +2.1. [Install PostgreSQL 9.6](#setup-postgresql) +3. [ElasticSearch](#elasticsearch)
    +3.1. [Install ElasticSearch](#setup-elasticsearch)
    +3.2. [Rebuild statistics](#rebuild-stats)
    +3.3. [Backup and Restore](#backup-and-restore-elasticsearch) + +This procedure is not easy to follow so if you don't need to write some code for Fab-manager, please prefer the [docker-compose installation method](docker-compose_readme.md). + + + +## General Guidelines + +1. Install RVM, with the ruby version specified in the [.ruby-version file](../.ruby-version). + For more details about the process, please read the [official RVM documentation](http://rvm.io/rvm/install). + If you're using ArchLinux, you may have to [read this](archlinux_readme.md) before. + +2. Install NVM, with the node.js version specified in the [.nvmrc file](../.nvmrc). + For instructions about installing NVM, please refer to [the NVM readme](https://github.com/nvm-sh/nvm#installation-and-update). + +3. Install Yarn, the front-end package manager. + Depending on your system, the installation process may differ, please read the [official Yarn documentation](https://yarnpkg.com/en/docs/install#debian-stable). + +4. Install docker. + Your system may provide a pre-packaged version of docker in its repositories, but this version may be outdated. + Please refer to [ubuntu](https://docs.docker.com/install/linux/docker-ce/ubuntu/), [debian](https://docs.docker.com/install/linux/docker-ce/debian/) or [MacOS](https://docs.docker.com/docker-for-mac/install/) documentation to setup a recent version of docker. + +5. Add your current user to the docker group, to allow using docker without `sudo`. + ```bash + # add the docker group if it doesn't already exist + sudo groupadd docker + # add the current user to the docker group + sudo usermod -aG docker $(whoami) + # restart to validate changes + sudo reboot + ``` + +6. Create a docker network for fab-manager. + You may have to change the network address if it is already in use. + >  If you're using MacOS, this is not required. + ```bash + docker network create --subnet=172.18.0.0/16 fabmanager + ``` + +7. Retrieve the project from Git + + ```bash + git clone https://github.com/sleede/fab-manager.git + ``` + +8. Install the software dependencies. + First install [PostgreSQL](#postgresql) and [ElasticSearch](#elasticsearch) as specified in their respective documentations (see below). + Then install the other dependencies: + - For Ubuntu/Debian: + + ```bash + # on Ubuntu 18.04 server, you may have to enable the "universe" repository + sudo add-apt-repository universe + # then, install the dependencies + sudo apt-get install libpq-dev redis-server imagemagick + ``` + - For MacOS X: + + ```bash + brew install redis imagemagick + ``` + +9. Init the RVM and NVM instances and check they were correctly configured + + ```bash + cd fab-manager + rvm current | grep -q `cat .ruby-version`@fab-manager && echo "ok" + # Must print ok + nvm use + node --version | grep -q `cat .nvmrc` && echo "ok" + # Must print ok + ``` + +10. Install bundler in the current RVM gemset + + ```bash + gem install bundler --version=1.17.3 + ``` + +11. Install the required ruby gems and javascript plugins + + ```bash + bundle install + yarn install + ``` + +12. Create the default configuration files **and configure them!** (see the [environment configuration documentation](doc/environment.md)) + + ```bash + cp config/database.yml.default config/database.yml + cp config/application.yml.default config/application.yml + vi config/application.yml + # or use your favorite text editor instead of vi (nano, ne...) + ``` + +13. Build the databases. + - **Warning**: **DO NOT** run `rake db:setup` instead of these commands, as this will not run some required raw SQL instructions. + - **Please note**: Your password length must be between 8 and 128 characters, otherwise db:seed will be rejected. This is configured in [config/initializers/devise.rb](config/initializers/devise.rb) + + ```bash + # for dev + rake db:create + rake db:migrate + ADMIN_EMAIL='youradminemail' ADMIN_PASSWORD='youradminpassword' rake db:seed + rake fablab:es:build_stats + # for tests + RAILS_ENV=test rake db:create + RAILS_ENV=test rake db:migrate + ``` + +14. Create the pids folder used by Sidekiq. If you want to use a different location, you can configure it in `config/sidekiq.yml` + + ```bash + mkdir -p tmp/pids + ``` + +15. Start the development web server + + ```bash + foreman s -p 3000 + ``` + +16. You should now be able to access your local development FabManager instance by accessing `http://localhost:3000` in your web browser. + +17. You can login as the default administrator using the credentials defined previously. + +18. Email notifications will be caught by MailCatcher. + To see the emails sent by the platform, open your web browser at `http://localhost:1080` to access the MailCatcher interface. + + + +## PostgreSQL + + +### Install PostgreSQL 9.6 + +We will use docker to easily install the required version of PostgreSQL. + +1. Create the docker binding folder + ```bash + mkdir -p .docker/postgresql + ``` + +2. Start the PostgreSQL container. + >  If you're using MacOS, remove the --network and --ip parameters, and add -p 5432:5432. + ```bash + docker run --restart=always -d --name fabmanager-postgres \ + -v $(pwd)/.docker/postgresql:/var/lib/postgresql/data \ + --network fabmanager --ip 172.18.0.2 \ + postgres:9.6 + ``` + +3. Configure fab-manager to use it. + On linux systems, PostgreSQL will be available at 172.18.0.2. + On MacOS, you'll have to set the host to 127.0.0.1 (or localhost). + See [environment.md](doc/environment.md) for more details. + +4 . Finally, you may want to have a look at detailed informations about PostgreSQL usage in fab-manager. + Some information about that is available in the [PostgreSQL Readme](doc/postgresql_readme.md). + + +## ElasticSearch + +ElasticSearch is a powerful search engine based on Apache Lucene combined with a NoSQL database used as a cache to index data and quickly process complex requests on it. + +In FabManager, it is used for the admin's statistics module and to perform searches in projects. + + +### Install ElasticSearch + +1. Create the docker binding folders + ```bash + mkdir -p .docker/elasticsearch/config + mkdir -p .docker/elasticsearch/plugins + mkdir -p .docker/elasticsearch/backups + ``` + +2. Copy the default configuration files + ```bash + cp docker/elasticsearch.yml .docker/elasticsearch/config + cp docker/log4j2.properties .docker/elasticsearch/config + ``` + +3. Start the ElasticSearch container. + >  If you're using MacOS, remove the --network and --ip parameters, and add -p 9200:9200. + ```bash + docker run --restart=always -d --name fabmanager-elastic \ + -v $(pwd)/.docker/elasticsearch/config:/usr/share/elasticsearch/config \ + -v $(pwd)/.docker/elasticsearch:/usr/share/elasticsearch/data \ + -v $(pwd)/.docker/elasticsearch/plugins:/usr/share/elasticsearch/plugins \ + -v $(pwd)/.docker/elasticsearch/backups:/usr/share/elasticsearch/backups \ + --network fabmanager --ip 172.18.0.3 \ + elasticsearch:5.6 + ``` + +4. Configure fab-manager to use it. + On linux systems, ElasticSearch will be available at 172.18.0.3. + On MacOS, you'll have to set the host to 127.0.0.1 (or localhost). + See [environment.md](doc/environment.md) for more details. + + +### Rebuild statistics + +Every nights, the statistics for the day that just ended are built automatically at 01:00 (AM) and stored in ElastricSearch. +See [schedule.yml](config/schedule.yml) to modify this behavior. +If the scheduled task wasn't executed for any reason (eg. you are in a dev environment and your computer was turned off at 1 AM), you can force the statistics data generation in ElasticSearch, running the following command. + +```bash +# Here for the 50 last days +rake fablab:es:generate_stats[50] +``` + + +### Backup and Restore + +To backup and restore the ElasticSearch database, use the [elasticsearch-dump](https://github.com/taskrabbit/elasticsearch-dump) tool. + +Dump the database with: `elasticdump --input=http://localhost:9200/stats --output=fablab_stats.json`. +Restore it with: `elasticdump --input=fablab_stats.json --output=http://localhost:9200/stats`. diff --git a/docker/README.md b/doc/docker-compose_readme.md similarity index 100% rename from docker/README.md rename to doc/docker-compose_readme.md diff --git a/doc/translation_readme.md b/doc/translation_readme.md new file mode 100644 index 000000000..17667785f --- /dev/null +++ b/doc/translation_readme.md @@ -0,0 +1,75 @@ +# Fab-Manager translation documentation + +This document will explain you what you need to know to contribute to the translation process of Fab-Manager. + +##### Table of contents + +1. [Translation](#i18n-translation)
    +1.1. [Front-end translations](#i18n-translation-front)
    +1.2. [Back-end translations](#i18n-translation-back) +2. [Configuration](#i18n-configuration)
    +2.1. [Settings](#i18n-settings)
    +2.2. [Applying changes](#i18n-apply) + + +## Translation + +First, consider that it can be a good idea to setup a development environment to contribute to the software translation. +This is not mandatory, but this will allow you to test your changes in context and see if anything went wrong, especially with the special syntaxes. +Please refer to the [development readme](development_readme.md) or to the [virtual machine instructions](virtual-machine.md) to setup such an environment. + +Once done, check the files located in `config/locales`: + +- Front app translations (angular.js) are located in `config/locales/app.scope.XX.yml`. + Where scope has one the following meaning : + - admin: translations of the administrator views (manage and configure the FabLab). + - logged: translations of the end-user's views accessible only to connected users. + - public: translation of end-user's views publicly accessible to anyone. + - shared: translations shared by many views (like forms or buttons). +- Back app translations (Ruby on Rails) are located in `config/locales/XX.yml`. +- Emails translations are located in `config/locales/mails.XX.yml`. +- Messages related to the authentication system are located in `config/locales/devise.XX.yml`. + +If you plan to translate the application to a new locale, please consider that the reference translation is French. +Indeed, in some cases, the English texts/sentences can seems confuse or lack of context as they were originally translated from French. + +To prevent syntax mistakes while translating locale files, we **STRONGLY advise** you to use a text editor which support syntax coloration for YML and Ruby. +As an example, [Visual Studio Code](https://code.visualstudio.com/), with the [Ruby extension](https://marketplace.visualstudio.com/items?itemName=rebornix.Ruby) and the [YAML extension](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml) will do the job. + + +### Front-end translations + +Front-end translations uses [angular-translate](http://angular-translate.github.io) with some interpolations interpreted by angular.js and other interpreted by [MessageFormat](https://github.com/SlexAxton/messageformat.js/). +**These two kinds of interpolation use a near but different syntax witch SHOULD NOT be confused.** +Please refer to the official [angular-translate documentation](http://angular-translate.github.io/docs/#/guide/14_pluralization) before translating. + + +### Back-end translations + +Back-end translations uses the [Ruby on Rails syntax](http://guides.rubyonrails.org/i18n.html) but some complex interpolations are interpreted by [MessageFormat](https://github.com/format-message/message-format-rb) and are marked as it in comments. +**DO NOT confuse the syntaxes.** + +In each cases, some inline comments are included in the localisation files. +They can be recognized as they start with the sharp character (#). +These comments are not required to be translated, they are intended to help the translator to have some context information about the sentence to translate. + +You will also need to translate the invoice watermark, located in `app/pdfs/data/`. +You'll find there the [GIMP source of the image](app/pdfs/data/watermark.xcf), which is using [Rubik Mono One](https://fonts.google.com/specimen/Rubik+Mono+One) as font. +Use it to generate a similar localised PNG image which keep the default image size, as PDF are not responsive. + + + +## Configuration + +Locales configurations are made in `config/application.yml`. +If you are in a development environment, your can keep the default values, otherwise, in production, values must be configured carefully. + + +### Settings + +Please refer to the [environment configuration documentation](doc/environment.md#internationalization-settings) + + +### Applying changes + +After modifying any values concerning the localisation, restart the application (ie. web server) to apply these changes in the i18n configuration. From 33ab2ec7d7f9dfe313d3070bda96aec002489719 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 3 Dec 2019 17:32:27 +0100 Subject: [PATCH 063/135] moved known issues to a separate doc --- README.md | 48 +--------------------------- doc/development_readme.md | 14 ++++----- doc/docker-compose_readme.md | 14 ++++----- doc/known-issues.md | 61 ++++++++++++++++++++++++++++++++++++ doc/translation_readme.md | 5 +-- 5 files changed, 79 insertions(+), 63 deletions(-) create mode 100644 doc/known-issues.md diff --git a/README.md b/README.md index 9948f4245..886171eb0 100644 --- a/README.md +++ b/README.md @@ -106,53 +106,7 @@ Developers may find information on how to implement their own authentication pro ## Known issues -- When browsing a machine page, you may encounter an "InterceptError" in the console and the loading bar will stop loading before reaching its ending. - This may happen if the machine was created through a seed file without any image. - To solve this, simply add an image to the machine's profile and refresh the web page. - -- When starting the Ruby on Rails server (eg. `foreman s`) you may receive the following error: - - worker.1 | invalid url: redis::6379 - web.1 | Exiting - worker.1 | ...lib/redis/client.rb...:in `_parse_options' - - This may happen when the `application.yml` file is missing. - To solve this issue copy `config/application.yml.default` to `config/application.yml`. - This is required before the first start. - -- Due to a stripe limitation, you won't be able to create plans longer than one year. - -- When running the tests suite with `rake test`, all tests may fail with errors similar to the following: - - Error: - ... - ActiveRecord::InvalidForeignKey: PG::ForeignKeyViolation: ERROR: insert or update on table "..." violates foreign key constraint "fk_rails_..." - DETAIL: Key (group_id)=(1) is not present in table "...". - : ... - test_after_commit (1.0.0) lib/test_after_commit/database_statements.rb:11:in `block in transaction' - test_after_commit (1.0.0) lib/test_after_commit/database_statements.rb:5:in `transaction' - - This is due to an ActiveRecord behavior witch disable referential integrity in PostgreSQL to load the fixtures. - PostgreSQL will prevent any users to disable referential integrity on the fly if they doesn't have the `SUPERUSER` role. - To fix that, logon as the `postgres` user and run the PostgreSQL shell (see [the dedicated section](#run-postgresql-cli) for instructions). - Then, run the following command (replace `sleede` with your test database user, as specified in your database.yml): - - ALTER ROLE sleede WITH SUPERUSER; - - DO NOT do this in a production environment, unless you know what you're doing: this could lead to a serious security issue. - -- With Ubuntu 16.04, ElasticSearch may refuse to start even after having configured the service with systemd. - To solve this issue, you may have to set `START_DAEMON` to `true` in `/etc/default/elasticsearch`. - Then reload ElasticSearch with: - - ```bash - sudo systemctl restart elasticsearch.service - ``` -- In some cases, the invoices won't be generated. This can be due to the image included in the invoice header not being supported. - To fix this issue, change the image in the administrator interface (manage the invoices / invoices settings). - See [this thread](https://forum.fab-manager.com/t/resolu-erreur-generation-facture/428) for more info. - -- In the excel exports, if the cells expected to contain dates are showing strange numbers, check that you have correctly configured the [EXCEL_DATE_FORMAT](doc/environment.md#EXCEL_DATE_FORMAT) variable. +Before reporting an issue, please check if your issue is not listed in the [know issues](doc/known-issues.md) with its solution. ## Related Documentation diff --git a/doc/development_readme.md b/doc/development_readme.md index a3d3af8f3..afdb92a4e 100644 --- a/doc/development_readme.md +++ b/doc/development_readme.md @@ -44,7 +44,7 @@ This procedure is not easy to follow so if you don't need to write some code for 6. Create a docker network for fab-manager. You may have to change the network address if it is already in use. - >  If you're using MacOS, this is not required. + > 🍏 If you're using MacOS, this is not required. ```bash docker network create --subnet=172.18.0.0/16 fabmanager ``` @@ -96,7 +96,7 @@ This procedure is not easy to follow so if you don't need to write some code for yarn install ``` -12. Create the default configuration files **and configure them!** (see the [environment configuration documentation](doc/environment.md)) +12. Create the default configuration files **and configure them!** (see the [environment configuration documentation](environment.md)) ```bash cp config/database.yml.default config/database.yml @@ -154,7 +154,7 @@ We will use docker to easily install the required version of PostgreSQL. ``` 2. Start the PostgreSQL container. - >  If you're using MacOS, remove the --network and --ip parameters, and add -p 5432:5432. + > 🍏 If you're using MacOS, remove the --network and --ip parameters, and add -p 5432:5432. ```bash docker run --restart=always -d --name fabmanager-postgres \ -v $(pwd)/.docker/postgresql:/var/lib/postgresql/data \ @@ -165,10 +165,10 @@ We will use docker to easily install the required version of PostgreSQL. 3. Configure fab-manager to use it. On linux systems, PostgreSQL will be available at 172.18.0.2. On MacOS, you'll have to set the host to 127.0.0.1 (or localhost). - See [environment.md](doc/environment.md) for more details. + See [environment.md](environment.md) for more details. 4 . Finally, you may want to have a look at detailed informations about PostgreSQL usage in fab-manager. - Some information about that is available in the [PostgreSQL Readme](doc/postgresql_readme.md). + Some information about that is available in the [PostgreSQL Readme](postgresql_readme.md). ## ElasticSearch @@ -194,7 +194,7 @@ In FabManager, it is used for the admin's statistics module and to perform searc ``` 3. Start the ElasticSearch container. - >  If you're using MacOS, remove the --network and --ip parameters, and add -p 9200:9200. + > 🍏 If you're using MacOS, remove the --network and --ip parameters, and add -p 9200:9200. ```bash docker run --restart=always -d --name fabmanager-elastic \ -v $(pwd)/.docker/elasticsearch/config:/usr/share/elasticsearch/config \ @@ -208,7 +208,7 @@ In FabManager, it is used for the admin's statistics module and to perform searc 4. Configure fab-manager to use it. On linux systems, ElasticSearch will be available at 172.18.0.3. On MacOS, you'll have to set the host to 127.0.0.1 (or localhost). - See [environment.md](doc/environment.md) for more details. + See [environment.md](environment.md) for more details. ### Rebuild statistics diff --git a/doc/docker-compose_readme.md b/doc/docker-compose_readme.md index b7b08b334..8b833fdea 100644 --- a/doc/docker-compose_readme.md +++ b/doc/docker-compose_readme.md @@ -1,9 +1,9 @@ -# Install Fabmanager app in production with Docker +# Install Fab-Manager in production with docker-compose -This README tries to describe all the steps to put a fabmanager app into production on a server, based on a solution using Docker and Docker-compose. +This document will guide you through all the steps needed to set up your Fab-Manager app on a production server, based on a solution using Docker and Docker-compose. We recommend DigitalOcean, but these steps will work on any Docker-compatible cloud provider or local server. -In order to make it work, please use the same directories structure as described in this guide in your fabmanager app folder. +In order to make it work, please use the same directories structure as described in this guide in your Fab-Manager app folder. You will need to be root through the rest of the setup. ##### Table of contents @@ -57,7 +57,7 @@ On DigitalOcean, create a Droplet with One-click apps **"Docker on Ubuntu 16.04 This way, Docker and Docker-compose are preinstalled. Choose a datacenter and set the hostname as your domain name. -With other providers, choose a [supported operating system](https://github.com/sleede/fab-manager/blob/master/README.md#software-stack) and install docker on it: +With other providers, choose a [supported operating system](../README.md#software-stack) and install docker on it: - [Debian](https://docs.docker.com/engine/installation/linux/docker-ce/debian/) - [Ubuntu](https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/) @@ -68,9 +68,9 @@ Then install [Docker Compose](https://docs.docker.com/compose/install/) There are many domain name registrars on the internet, you may choose one that fit your needs. You can find an exhaustive list [on the ICANN website](https://www.icann.org/registrar-reports/accredited-list.html) -1. Once done, buy a domain name on it +1. Once done, buy a domain name on it 2. Replace the IP address of the domain with the IP address of your VPS (This is a DNS record type A) -3. **Do not** try to access your domain name right away, DNS are not aware of the change yet so **WAIT** and be patient. +3. **Do not** try to access your domain name right away, DNS are not aware of the change yet so **WAIT** and be patient. ### Connect through SSH @@ -111,7 +111,7 @@ vi config/env # or use your favorite text editor instead of vi (nano, ne...) ``` You need to carefully configure each variable before starting fab-manager. -Please refer to the [environment configuration documentation](../doc/environment.md) for explanations about those variables. +Please refer to the [environment configuration documentation](environment.md) for explanations about those variables. ### Setup nginx configuration diff --git a/doc/known-issues.md b/doc/known-issues.md new file mode 100644 index 000000000..64ed867c7 --- /dev/null +++ b/doc/known-issues.md @@ -0,0 +1,61 @@ +# Known issues + +This document is listing common known issues. + +> Production issues may also apply to development environments. + +##### Table of contents + +1. [Development](#development) +2. [Production](#production) + + +## Development + +- When starting the Ruby on Rails server (eg. `foreman s`) you may receive the following error: + + worker.1 | invalid url: redis::6379 + web.1 | Exiting + worker.1 | ...lib/redis/client.rb...:in `_parse_options' + + This may happen when the `application.yml` file is missing. + To solve this issue copy `config/application.yml.default` to `config/application.yml`. + This is required before the first start. + +- When running the tests suite with `rake test`, all tests may fail with errors similar to the following: + + Error: + ... + ActiveRecord::InvalidForeignKey: PG::ForeignKeyViolation: ERROR: insert or update on table "..." violates foreign key constraint "fk_rails_..." + DETAIL: Key (group_id)=(1) is not present in table "...". + : ... + test_after_commit (1.0.0) lib/test_after_commit/database_statements.rb:11:in `block in transaction' + test_after_commit (1.0.0) lib/test_after_commit/database_statements.rb:5:in `transaction' + + This is due to an ActiveRecord behavior witch disable referential integrity in PostgreSQL to load the fixtures. + PostgreSQL will prevent any users to disable referential integrity on the fly if they doesn't have the `SUPERUSER` role. + To fix that, logon as the `postgres` user and run the PostgreSQL shell (see [the dedicated section](#run-postgresql-cli) for instructions). + Then, run the following command (replace `sleede` with your test database user, as specified in your database.yml): + + ALTER ROLE sleede WITH SUPERUSER; + + DO NOT do this in a production environment, unless you know what you're doing: this could lead to a serious security issue. + + +## Production + +- Due to a stripe limitation, you won't be able to create plans longer than one year. + +- With Ubuntu 16.04, ElasticSearch may refuse to start even after having configured the service with systemd. + To solve this issue, you may have to set `START_DAEMON` to `true` in `/etc/default/elasticsearch`. + Then reload ElasticSearch with: + + ```bash + sudo systemctl restart elasticsearch.service + ``` + +- In some cases, the invoices won't be generated. This can be due to the image included in the invoice header not being supported. + To fix this issue, change the image in the administrator interface (manage the invoices / invoices settings). + See [this thread](https://forum.fab-manager.com/t/resolu-erreur-generation-facture/428) for more info. + +- In the excel exports, if the cells expected to contain dates are showing strange numbers, check that you have correctly configured the [EXCEL_DATE_FORMAT](environment.md#EXCEL_DATE_FORMAT) variable. diff --git a/doc/translation_readme.md b/doc/translation_readme.md index 17667785f..7607e4b27 100644 --- a/doc/translation_readme.md +++ b/doc/translation_readme.md @@ -61,13 +61,14 @@ Use it to generate a similar localised PNG image which keep the default image si ## Configuration -Locales configurations are made in `config/application.yml`. +In development, locales configurations are made in [config/application.yml](../config/application.yml.default). +In production, locales configuration are made in the [config/env](../docker/env.example) file. If you are in a development environment, your can keep the default values, otherwise, in production, values must be configured carefully. ### Settings -Please refer to the [environment configuration documentation](doc/environment.md#internationalization-settings) +Please refer to the [environment configuration documentation](environment.md#internationalization-settings) ### Applying changes From 2602010770eb6f6c6cb467b37efc2e83bdaff79c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Dec 2019 23:00:14 +0000 Subject: [PATCH 064/135] Bump puma from 3.10.0 to 3.12.2 Bumps [puma](https://github.com/puma/puma) from 3.10.0 to 3.12.2. - [Release notes](https://github.com/puma/puma/releases) - [Changelog](https://github.com/puma/puma/blob/master/History.md) - [Commits](https://github.com/puma/puma/compare/v3.10.0...v3.12.2) Signed-off-by: dependabot[bot] --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index b69b1c8f9..736b8d43d 100644 --- a/Gemfile +++ b/Gemfile @@ -4,7 +4,7 @@ gem 'compass-rails', '2.0.4' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '4.2.11.1' # Use Puma as web server -gem 'puma', '3.10.0' +gem 'puma', '3.12.2' # Use SCSS for stylesheets gem 'sass-rails', '5.0.1' diff --git a/Gemfile.lock b/Gemfile.lock index 0c1c06a6b..b9f503543 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -153,7 +153,7 @@ GEM execjs (2.7.0) faker (1.4.3) i18n (~> 0.5) - faraday (0.17) + faraday (0.17.0) multipart-post (>= 1.2, < 3) ffi (1.9.24) figaro (1.1.0) @@ -284,7 +284,7 @@ GEM protected_attributes (1.1.3) activemodel (>= 4.0.1, < 5.0) public_suffix (3.0.2) - puma (3.10.0) + puma (3.12.2) pundit (1.0.0) activesupport (>= 3.0.0) raabro (1.1.6) @@ -516,7 +516,7 @@ DEPENDENCIES prawn prawn-table protected_attributes - puma (= 3.10.0) + puma (= 3.12.2) pundit rack-protection (= 1.5.5) railroady From 177c4c3ace6cc583174705ba9cd679d614112ade Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 9 Dec 2019 08:33:39 +0100 Subject: [PATCH 065/135] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5271df47..5d8eb994f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - Fix a bug: replaces all Time.now by DateTime.current to prevent time zones issues [Taiga#134] - Fix a security issue: updated loofah to fix [CVE-2019-15587](https://github.com/advisories/GHSA-c3gv-9cxf-6f57) - Fix a security issue: updated angular to 1.7.9 to fix [CVE-2019-10768](https://github.com/advisories/GHSA-89mq-4x47-5v83) +- Fix a security issue: updated puma to 3.12.2 to fix [GHSA-7xx3-m584-x994](https://github.com/advisories/GHSA-7xx3-m584-x994) - [TODO DEPLOY] add the `SLOT_DURATION` environment variable (see [doc/environment.md](doc/environment.md#SLOT_DURATION) for configuration details) - [TODO DEPLOY] add the `PHONE_REQUIRED` environment variable (see [doc/environment.md](doc/environment.md#PHONE_REQUIRED) for configuration details) - [TODO DEPLOY] add the `EVENTS_IN_CALENDAR` environment variable (see [doc/environment.md](doc/environment.md#EVENTS_IN_CALENDAR) for configuration details) From 510c802ab1ce029e0f78a9adf7319a2622215c0b Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 9 Dec 2019 10:27:50 +0100 Subject: [PATCH 066/135] messages variations depending on slot duration --- app/assets/javascripts/app.js | 2 ++ .../templates/admin/pricing/credits.html.erb | 6 +++--- .../templates/admin/pricing/machine_hours.html.erb | 4 ++-- app/assets/templates/admin/pricing/spaces.html.erb | 4 ++-- config/locales/app.admin.en.yml | 12 ++++++------ config/locales/app.admin.es.yml | 12 ++++++------ config/locales/app.admin.fr.yml | 12 ++++++------ config/locales/app.admin.pt.yml | 14 +++++++------- config/locales/app.shared.en.yml | 2 +- config/locales/app.shared.es.yml | 2 +- config/locales/app.shared.fr.yml | 2 +- config/locales/app.shared.pt.yml | 2 +- config/locales/en.yml | 4 ++-- config/locales/es.yml | 4 ++-- config/locales/fr.yml | 4 ++-- config/locales/mails.en.yml | 2 +- config/locales/mails.es.yml | 7 ++++--- config/locales/mails.fr.yml | 2 +- config/locales/mails.pt.yml | 2 +- config/locales/pt.yml | 5 +++-- db/seeds.rb | 4 ++-- test/fixtures/history_values.yml | 2 +- test/fixtures/statistic_indices.yml | 2 +- 23 files changed, 58 insertions(+), 54 deletions(-) diff --git a/app/assets/javascripts/app.js b/app/assets/javascripts/app.js index f932fd84f..134f71ab6 100644 --- a/app/assets/javascripts/app.js +++ b/app/assets/javascripts/app.js @@ -90,6 +90,8 @@ angular.module('application', ['ngCookies', 'ngResource', 'ngSanitize', 'ui.rout $rootScope.phoneRequired = Fablab.phoneRequired; // Global config: if true, the events are shown in the admin calendar $rootScope.eventsInCalendar = Fablab.eventsInCalendar; + // Global config: machine/space slot duration + $rootScope.slotDuration = Fablab.slotDuration; // Global function to allow the user to navigate to the previous screen (ie. $state). // If no previous $state were recorded, navigate to the home page diff --git a/app/assets/templates/admin/pricing/credits.html.erb b/app/assets/templates/admin/pricing/credits.html.erb index 07c23d359..0dfd69f79 100644 --- a/app/assets/templates/admin/pricing/credits.html.erb +++ b/app/assets/templates/admin/pricing/credits.html.erb @@ -52,7 +52,7 @@ {{ 'pricing.machine' }} - {{ 'pricing.hours' }} + {{ 'pricing.hours' | translate:{DURATION:slotDuration} }} {{ 'pricing.related_subscriptions' }} @@ -104,7 +104,7 @@ {{ 'pricing.space' }} - {{ 'pricing.hours' }} + {{ 'pricing.hours' | translate:{DURATION:slotDuration} }} {{ 'pricing.related_subscriptions' }} @@ -146,4 +146,4 @@ - \ No newline at end of file + diff --git a/app/assets/templates/admin/pricing/machine_hours.html.erb b/app/assets/templates/admin/pricing/machine_hours.html.erb index 636219c63..3823e42cc 100644 --- a/app/assets/templates/admin/pricing/machine_hours.html.erb +++ b/app/assets/templates/admin/pricing/machine_hours.html.erb @@ -1,5 +1,5 @@
    - {{ 'pricing.these_prices_match_machine_hours_rates_' | translate }} {{ 'pricing._without_subscriptions' }}. + {{ 'pricing.these_prices_match_machine_hours_rates_' | translate:{DURATION:slotDuration} }} {{ 'pricing._without_subscriptions' }}.
    @@ -24,4 +24,4 @@ -
    \ No newline at end of file + diff --git a/app/assets/templates/admin/pricing/spaces.html.erb b/app/assets/templates/admin/pricing/spaces.html.erb index 25417cb6e..1395f3f83 100644 --- a/app/assets/templates/admin/pricing/spaces.html.erb +++ b/app/assets/templates/admin/pricing/spaces.html.erb @@ -1,5 +1,5 @@
    - {{ 'pricing.these_prices_match_space_hours_rates_' | translate }} {{ 'pricing._without_subscriptions' }}. + {{ 'pricing.these_prices_match_space_hours_rates_' | translate:{DURATION:slotDuration} }} {{ 'pricing._without_subscriptions' }}.
    @@ -24,4 +24,4 @@ -
    \ No newline at end of file + diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index 261408460..fa2dc79f3 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -242,8 +242,8 @@ en: group: "Group" prominence: "Prominence" price: "Price" - machine_hours: "Machine hours" - these_prices_match_machine_hours_rates_: "These prices match machine hours rates" + machine_hours: "Machine slots" + these_prices_match_machine_hours_rates_: "These prices match {{DURATION}} minutes of machine usage, " _without_subscriptions: "without subscriptions" machines: "Machines" credits: "Credits" @@ -251,7 +251,7 @@ en: related_trainings: "Related trainings" add_a_machine_credit: "Add a machine credit" machine: "Machine" - hours: "Hours" + hours: "Slots of {{DURATION}} minutes" related_subscriptions: "Related subscriptions" please_specify_a_number: "Please specify a number." none: "None" # grammar note: concordance with "training". @@ -296,7 +296,7 @@ en: forever: "Each use" valid_until: "Valid until (included)" spaces: "Spaces" - these_prices_match_space_hours_rates_: "These prices match space hours rates" + these_prices_match_space_hours_rates_: "These prices match {{DURATION}} minutes of space usage, " add_a_space_credit: "Add a Space credit" space: "Espace" error_a_credit_linking_this_space_with_that_subscription_already_exists: "Error : a credit linking this space with that subscription already exists." @@ -322,14 +322,14 @@ en: successfully_created_subscriptions_dont_forget_to_redefine_prices: "Subscription(s) successfully created. Don't forget to redefine prices." unable_to_save_this_user_check_that_there_isnt_an_already_a_user_with_the_same_name: "Unable to save this user. Check that there isn't an already defined user with the same name." edit: - # edit a subscription plan / machine hours prices + # edit a subscription plan / machine slots prices edit_plan: subscription_plan: "Subscription plan:" prices: "Prices" copy_prices_from: "Copy prices from" machines: "Machines" machine: "Machine" - hourly_rate: "Hourly rate" + hourly_rate: "Price by slot" spaces: "Spaces" space: "Space" unable_to_save_subscription_changes_please_try_again: "Unable to save subscription changes. Please try again." diff --git a/config/locales/app.admin.es.yml b/config/locales/app.admin.es.yml index fb62110e7..6f9521cc1 100644 --- a/config/locales/app.admin.es.yml +++ b/config/locales/app.admin.es.yml @@ -242,8 +242,8 @@ es: group: "Grupo" prominence: "Prominencia" price: "Precio" - machine_hours: "Horas de la máquina" - these_prices_match_machine_hours_rates_: "Estos precios se ajustan a la tarifas de máquina por horas" + machine_hours: "Machine slots" # translation_missing + these_prices_match_machine_hours_rates_: "Estos precios se ajustan a la tarifas de máquina por {{DURATION}} minutas" _without_subscriptions: "Sin suscripciones" machines: "Máquinas" credits: "Créditos" @@ -251,7 +251,7 @@ es: related_trainings: "Formación relacionada" add_a_machine_credit: "Agregar un crédito de máquina" machine: "Máquina" - hours: "Horas" + hours: "Slots of {{DURATION}} minutes" # translation_missing related_subscriptions: "Suscripciónes relacionada" please_specify_a_number: "Por favor, especifique un número." none: "Nada" # nota gramatical: concordancia con "formación". @@ -296,7 +296,7 @@ es: forever: "Cada uso" valid_until: "Válido hasta (incluido)" spaces: "Espacios" - these_prices_match_space_hours_rates_: "Estos precios coinciden con las tarifas de espacio por horas" + these_prices_match_space_hours_rates_: "Estos precios coinciden con las tarifas de espacio por {{DURATION}} minutas" add_a_space_credit: "Añadir un crédito de espacio" space: "Espacio" error_a_credit_linking_this_space_with_that_subscription_already_exists: "Error: un crédito que vincula este espacio con esa suscripción ya existe." @@ -322,14 +322,14 @@ es: successfully_created_subscriptions_dont_forget_to_redefine_prices: "Suscripción(es) creada correctamente. No olvide redefinir los precios." unable_to_save_this_user_check_that_there_isnt_an_already_a_user_with_the_same_name: "No se puede guardar este usuario. Compruebe que no hay un usuario ya definido con el mismo nombre." edit: - # edit a subscription plan / machine hours prices + # edit a subscription plan / machine slot prices edit_plan: subscription_plan: "Plan de suscripción:" prices: "Precios" copy_prices_from: "Copia los precios desde" machines: "Máquinas" machine: "Máquina" - hourly_rate: "Tarifa por hora" + hourly_rate: "Tarifa por slot" # translation_missing spaces: "Espacios" space: "Espacio" unable_to_save_subscription_changes_please_try_again: "No se pueden guardar los cambios de suscripción. Por favor, inténtelo de nuevo." diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 4a5108f15..0a9cc3791 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -242,8 +242,8 @@ fr: group: "Groupe" prominence: "Importance" price: "Prix" - machine_hours: "Heures machines" - these_prices_match_machine_hours_rates_: "Ces tarifs correspondent au prix d'une heure machine" + machine_hours: "Créneaux machines" + these_prices_match_machine_hours_rates_: "Ces tarifs correspondent au prix de {{DURATION}} minutes d'utilisation d'une machine, " # angular interpolation _without_subscriptions: "sans abonnement" machines: "Machines" credits: "Crédits" @@ -251,7 +251,7 @@ fr: related_trainings: "Formations associées" add_a_machine_credit: "Ajouter un crédit Machine" machine: "Machine" - hours: "Heures" + hours: "Créneaux de {{DURATION}} minutes" related_subscriptions: "Abonnements associés" please_specify_a_number: "Veuillez spécifier un nombre." none: "Aucune" # grammar note: concordance with "training". @@ -296,7 +296,7 @@ fr: forever: "À chaque utilisation" valid_until: "Valable jusqu'au (inclus)" spaces: "Espaces" - these_prices_match_space_hours_rates_: "Ces tarifs correspondent au prix d'une heure espace" + these_prices_match_space_hours_rates_: "Ces tarifs correspondent au prix de {{DURATION}} minutes d'utilisation d'un espace, " add_a_space_credit: "Ajouter un crédit Espace" space: "Espace" error_a_credit_linking_this_space_with_that_subscription_already_exists: "Erreur : un crédit associant cet espace et cet abonnement existe déjà." @@ -322,14 +322,14 @@ fr: successfully_created_subscriptions_dont_forget_to_redefine_prices: "Création du/des abonnement(s) réussie. N'oubliez pas de redéfinir les tarifs." unable_to_save_this_user_check_that_there_isnt_an_already_a_user_with_the_same_name: "Impossible d'enregistrer cet utilisateur. Vérifiez qu'il n'existe pas déjà un utilisateur du même nom." edit: - # modifier une formule d'abonnement / les prix des heures machines + # modifier une formule d'abonnement / les prix des créneaux machines edit_plan: subscription_plan: "Formule d'abonnement :" prices: "Tarifs" copy_prices_from: "Copier les prix depuis" machines: "Machines" machine: "Machine" - hourly_rate: "Tarif horaire" + hourly_rate: "Tarif par créneau" spaces: "Espaces" space: "Espace" unable_to_save_subscription_changes_please_try_again: "Les modifications de l'abonnement n'ont pas pu être enregistrées. Veuillez réessayer." diff --git a/config/locales/app.admin.pt.yml b/config/locales/app.admin.pt.yml index a908d7da0..fd2409743 100755 --- a/config/locales/app.admin.pt.yml +++ b/config/locales/app.admin.pt.yml @@ -242,8 +242,8 @@ pt: group: "Grupo" prominence: "Relevância" price: "Preço" - machine_hours: "Horas máquina" - these_prices_match_machine_hours_rates_: "Estes preços correspondem às horas de trabalho" + machine_hours: "Machine slots" # translation_missing + these_prices_match_machine_hours_rates_: "Estes preços correspondem às {{DURATION}} minutos de trabalho" _without_subscriptions: "sem assinaturas" machines: "Máquinas" credits: "Créditos" @@ -251,7 +251,7 @@ pt: related_trainings: "Treinamentos relacionados" add_a_machine_credit: "Adicionar crédito de máquina" machine: "Máquina" - hours: "Horas" + hours: "Slots of {{DURATION}} minutes" # translation_missing related_subscriptions: "Assinaturas relacionadas" please_specify_a_number: "Por favor especifique um número." none: "Vazio" # grammar note: concordance with "training". @@ -296,7 +296,7 @@ pt: forever: "Cada uso" valid_until: "Válido até (incluindo)" spaces: "Espaços" - these_prices_match_space_hours_rates_: "Estes preços correspondem às taxas de horários de espaço" + these_prices_match_space_hours_rates_: "Estes preços correspondem às {{DURATION}} minutos de espaço" add_a_space_credit: "Adicionar espaço de crédito" space: "Espaço" error_a_credit_linking_this_space_with_that_subscription_already_exists: "Erro: um crédito que vincula esse espaço com essa assinatura já existe." @@ -322,14 +322,14 @@ pt: successfully_created_subscriptions_dont_forget_to_redefine_prices: "Assinaturas criadas com sucesso. Não se esqueça de redefinir os preços." unable_to_save_this_user_check_that_there_isnt_an_already_a_user_with_the_same_name: "Impossível salvar este usuário. Certifique-se que ele não possui o mesmo nome de outro usuário." edit: - # edit a subscription plan / machine hours prices + # edit a subscription plan / machine slot prices edit_plan: subscription_plan: "Plano de assinatura:" prices: "Preços" copy_prices_from: "Copiar preços de" machines: "Máquinas" machine: "Máquina" - hourly_rate: "Taxa horária" + hourly_rate: "Price by slot" # translation_missing spaces: "Espaços" space: "Espaço" unable_to_save_subscription_changes_please_try_again: "Impossível salvar mudanças da assinatura. Por favor tente novamente." @@ -339,7 +339,7 @@ pt: invoices: # list of all invoices & invoicing parameters invoices: "Faturas" - accounting_periods: "Accounting periods" # missing translation + accounting_periods: "Accounting periods" # missing translation invoices_list: "Lista de faturas" filter_invoices: "Filtrar faturas" invoice_num_: "Fatura #:" diff --git a/config/locales/app.shared.en.yml b/config/locales/app.shared.en.yml index efd840816..45c24864a 100644 --- a/config/locales/app.shared.en.yml +++ b/config/locales/app.shared.en.yml @@ -443,7 +443,7 @@ en: _subscription: "subscription" cost_of_the_subscription: "Cost of the subscription" confirm_and_pay: "Confirm and pay" - you_have_settled_the_following_TYPE: "You have settled the following {TYPE, select, Machine{machine hours} Training{training} other{elements}}:" # messageFormat interpolation + you_have_settled_the_following_TYPE: "You have settled the following {TYPE, select, Machine{machine slots} Training{training} other{elements}}:" # messageFormat interpolation you_have_settled_a_: "You have settled a" total_: "TOTAL :" thank_you_your_payment_has_been_successfully_registered: "Thank you. Your payment has been successfully registered !" diff --git a/config/locales/app.shared.es.yml b/config/locales/app.shared.es.yml index 4f3bbb25a..108a25acf 100644 --- a/config/locales/app.shared.es.yml +++ b/config/locales/app.shared.es.yml @@ -443,7 +443,7 @@ es: _subscription: "suscripción" cost_of_the_subscription: "Coste de la suscripción" confirm_and_pay: "Confirmar y pagar" - you_have_settled_the_following_TYPE: "Acaba de seleccionar {TYPE, select, Machine{machine hours} Training{training} other{elements}}:" # messageFormat interpolation + you_have_settled_the_following_TYPE: "Acaba de seleccionar {TYPE, select, Machine{machine slots} Training{training} other{elements}}:" # messageFormat interpolation #translation_missing you_have_settled_a_: "Ha establecido una" total_: "TOTAL :" thank_you_your_payment_has_been_successfully_registered: "Gracias. Su pago se ha registrado con éxito." diff --git a/config/locales/app.shared.fr.yml b/config/locales/app.shared.fr.yml index 9116b6daf..fb67ea2df 100644 --- a/config/locales/app.shared.fr.yml +++ b/config/locales/app.shared.fr.yml @@ -443,7 +443,7 @@ fr: _subscription: "abonnement" cost_of_the_subscription: "Coût de l'abonnement" confirm_and_pay: "Valider et payer" - you_have_settled_the_following_TYPE: "Vous avez réglé {TYPE, select, Machine{les heures machines suivantes} Training{la formation suivante} other{les éléments suivants}} :" # messageFormat interpolation + you_have_settled_the_following_TYPE: "Vous avez réglé {TYPE, select, Machine{les créneaux machines suivants} Training{la formation suivante} other{les éléments suivants}} :" # messageFormat interpolation you_have_settled_a_: "Vous avez réglé un" total_: "TOTAL :" thank_you_your_payment_has_been_successfully_registered: "Merci. Votre paiement a bien été pris en compte !" diff --git a/config/locales/app.shared.pt.yml b/config/locales/app.shared.pt.yml index 725f06c58..0316392c5 100755 --- a/config/locales/app.shared.pt.yml +++ b/config/locales/app.shared.pt.yml @@ -443,7 +443,7 @@ pt: _subscription: "inscrição" cost_of_the_subscription: "Custo da inscrição" confirm_and_pay: "Confirmar e pagar" - you_have_settled_the_following_TYPE: "Você liquidou o seguinte {TYPE, select, Machine{horas máquina} Training{training} other{elements}}:" # messageFormat interpolation + you_have_settled_the_following_TYPE: "Você liquidou o seguinte {TYPE, select, Machine{slots de máquina} Training{training} other{elements}}:" # messageFormat interpolation you_have_settled_a_: "Você tem liquidado:" total_: "TOTAL :" thank_you_your_payment_has_been_successfully_registered: "Obrigado. Seu pagamento foi registrado com sucesso !" diff --git a/config/locales/en.yml b/config/locales/en.yml index ab0e1d645..3868fd2c0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -322,7 +322,7 @@ en: statistics_global: "of all the statistics" statistics_account: "of the registration statistics" statistics_event: "of statistics about events" - statistics_machine: "of statistics about machine hours" + statistics_machine: "of statistics about machine slots" statistics_project: "of statistics about projects" statistics_subscription: "of subscription statistics" statistics_training: "of statistics about trainings" @@ -354,7 +354,7 @@ en: statistics: # statistics tools for admins subscriptions: "Subscriptions" - machines_hours: "Machines hours" + machines_hours: "Machines slots" spaces: "Spaces" trainings: "Trainings" events: "Events" diff --git a/config/locales/es.yml b/config/locales/es.yml index a117f19a0..d6eac0d60 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -322,7 +322,7 @@ es: statistics_global: "de todas las estadísticas" statistics_account: "de las estadísticas de registro" statistics_event: "de estadísticas sobre eventos" - statistics_machine: "de estadísticas sobre horas de máquina" + statistics_machine: "de estadísticas sobre slots de máquina" # missing translation statistics_project: "de estadísticas sobre proyectos" statistics_subscription: "de estadísticas de suscripción" statistics_training: "de estadísticas de cursos" @@ -354,7 +354,7 @@ es: statistics: # statistics tools for admins subscriptions: "Suscripciones" - machines_hours: "Horario de máquinas" + machines_hours: "Machine slots" # missing translation spaces: "Espacios" trainings: "Cursos" events: "Eventos" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 6a71f1e2c..fef290c6d 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -322,7 +322,7 @@ fr: statistics_global: "de toutes les statistiques" statistics_account: "des statistiques d'inscriptions" statistics_event: "des statistiques sur les évènements" - statistics_machine: "des statistiques d'heures machines" + statistics_machine: "des statistiques de créneaux machines" statistics_project: "des statistiques sur les projets" statistics_subscription: "des statistiques d'abonnements" statistics_training: "des statistiques sur les formations" @@ -355,7 +355,7 @@ fr: statistics: # outil de statistiques pour les administrateurs subscriptions: "Abonnements" - machines_hours: "Heures machines" + machines_hours: "Créneaux machines" spaces: "Espaces" trainings: "Formations" events: "Évènements" diff --git a/config/locales/mails.en.yml b/config/locales/mails.en.yml index e50ef8934..a97595db0 100644 --- a/config/locales/mails.en.yml +++ b/config/locales/mails.en.yml @@ -259,7 +259,7 @@ en: statistics_global: "of all the statistics" statistics_account: "of the registration statistics" statistics_event: "of statistics about events" - statistics_machine: "of statistics about machine hours" + statistics_machine: "of statistics about machine slots" statistics_project: "of statistics about projects" statistics_subscription: "of subscription statistics" statistics_training: "of statistics about trainings" diff --git a/config/locales/mails.es.yml b/config/locales/mails.es.yml index 0341c8b66..edf65a3c0 100644 --- a/config/locales/mails.es.yml +++ b/config/locales/mails.es.yml @@ -229,6 +229,7 @@ es: subject: "Una cuenta importada ha completado su perfil" body: account_completed: "Una cuenta de usuario importada previamente %{PROVIDER} ha completado su perfil:" + imported_account_completed: "An user account previously imported through %{PROVIDER} has just completed his/her profile data:" # translation missing provider_id: "su ID de proveedor es:" notify_admin_abuse_reported: @@ -239,10 +240,10 @@ es: signaled_by: "marcado por:" signaled_on: "marcado el:" message: "Mensaje:" - visit_management_interface: "Refer to the Reporting Management Interface for more information." # missing translation + visit_management_interface: "Refer to the Reporting Management Interface for more information." # missing translation notify_user_wallet_is_credited: - subject: "" # translation_missing + subject: "Your wallet has been credited" # translation missing body: wallet_credit_html: "Se han ingresado %{AMOUNT} por el administrador." @@ -258,7 +259,7 @@ es: statistics_global: "de todas las estadísticas" statistics_account: "de las estadísticas de registro" statistics_event: "de estadísticas sobre eventos" - statistics_machine: "de estadísticas sobre horas de máquina" + statistics_machine: "de estadísticas sobre slots de máquina" # translation missing statistics_project: "de estadísticas sobre proyectos" statistics_subscription: "de estadísticas de suscripción" statistics_training: "de estadísticas sobre entrenamientos" diff --git a/config/locales/mails.fr.yml b/config/locales/mails.fr.yml index 5f1601c6a..e4d0c2d1b 100644 --- a/config/locales/mails.fr.yml +++ b/config/locales/mails.fr.yml @@ -259,7 +259,7 @@ fr: statistics_global: "de toutes les statistiques" statistics_account: "des statistiques d'inscriptions" statistics_event: "des statistiques sur les évènements" - statistics_machine: "des statistiques d'heures machines" + statistics_machine: "des statistiques de créneaux machines" statistics_project: "des statistiques sur les projets" statistics_subscription: "des statistiques d'abonnements" statistics_training: "des statistiques sur les formations" diff --git a/config/locales/mails.pt.yml b/config/locales/mails.pt.yml index ae2c14662..fee5cf5e6 100755 --- a/config/locales/mails.pt.yml +++ b/config/locales/mails.pt.yml @@ -259,7 +259,7 @@ pt: statistics_global: "de todas as estatísticas" statistics_account: "das estatísticas de registro" statistics_event: "das estatísticas sobre eventos" - statistics_machine: "das estatísticas de hora máquina" + statistics_machine: "das estatísticas de slot máquina" #translation_missing statistics_project: "das estatísticas de projetos" statistics_subscription: "das estatísticas de assinaturas" statistics_training: "das estatísticas de treinamento" diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 6230f49a0..4e71e1dde 100755 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -322,7 +322,7 @@ pt: statistics_global: "de todas as estatísticas" statistics_account: "das estatísticas de registro" statistics_event: "de estatísticas sobre eventos" - statistics_machine: "de estatísticas sobre horas de máquina" + statistics_machine: "de estatísticas sobre slots de máquina" # missing translation statistics_project: "de estatísticas sobre projetos" statistics_subscription: "de estatísticas de assinatura" statistics_training: "de estatísticas sobre treinamentos" @@ -351,10 +351,11 @@ pt: notify_privacy_policy_changed: policy_updated: "Privacy policy updated." # missing translation click_to_show: "Click here to consult" # missing translation + statistics: # statistics tools for admins subscriptions: "Assinaturas" - machines_hours: "Horas de máquina" + machines_hours: "Slots de máquina" # missing translation spaces: "Espaços" trainings: "Treinamentos" events: "Eventos" diff --git a/db/seeds.rb b/db/seeds.rb index da3de1060..e13ae4cdb 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -425,8 +425,8 @@ Stylesheet.build_sheet! unless Setting.find_by(name: 'training_information_message').try(:value) setting = Setting.find_or_initialize_by(name: 'training_information_message') - setting.value = "Avant de réserver une formation, nous vous conseillons de consulter nos offres d'abonnement qui"+ - ' proposent des conditions avantageuses sur le prix des formations et les heures machines.' + setting.value = "Avant de réserver une formation, nous vous conseillons de consulter nos offres d'abonnement qui" \ + ' proposent des conditions avantageuses sur le prix des formations et les créneaux machines.' setting.save end diff --git a/test/fixtures/history_values.yml b/test/fixtures/history_values.yml index 0940b6d94..be2063477 100644 --- a/test/fixtures/history_values.yml +++ b/test/fixtures/history_values.yml @@ -250,7 +250,7 @@ value_history_26: invoicing_profile_id: 1 value: Avant de réserver une formation, nous vous conseillons de consulter nos offres d'abonnement qui proposent des conditions avantageuses sur le prix des formations - et les heures machines. + et les créneaux machines. created_at: 2018-12-17 11:23:01.569316000 Z updated_at: 2018-12-17 11:23:01.717053000 Z footprint: 4413b595bfbb0ec9ac2637997b1731eb06e516dea606e6a2f1acc4b9bf323f98 diff --git a/test/fixtures/statistic_indices.yml b/test/fixtures/statistic_indices.yml index b3b6fbe60..aab5aa638 100644 --- a/test/fixtures/statistic_indices.yml +++ b/test/fixtures/statistic_indices.yml @@ -11,7 +11,7 @@ statistic_index_1: statistic_index_2: id: 2 es_type_key: machine - label: Heures machines + label: Créneaux machines created_at: 2016-04-04 14:11:33.359367000 Z updated_at: 2016-04-04 14:11:33.359367000 Z table: true From 6aa3c0caebe52f7b60cfa300e5f534034b2a4275 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 9 Dec 2019 11:55:31 +0100 Subject: [PATCH 067/135] Notify all admins on the creation of a refund invoice --- CHANGELOG.md | 1 + app/models/avoir.rb | 10 ++++++++++ app/models/notification_type.rb | 1 + .../_notify_admin_refund_created.json.jbuilder | 5 +++++ .../notify_admin_refund_created.html.erb | 8 ++++++++ config/locales/en.yml | 3 +++ config/locales/es.yml | 3 +++ config/locales/fr.yml | 2 ++ config/locales/mails.en.yml | 6 ++++++ config/locales/mails.es.yml | 10 ++++++++-- config/locales/mails.fr.yml | 6 ++++++ config/locales/mails.pt.yml | 10 ++++++++-- config/locales/pt.yml | 6 ++++-- 13 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 app/views/api/notifications/_notify_admin_refund_created.json.jbuilder create mode 100644 app/views/notifications_mailer/notify_admin_refund_created.html.erb diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d8eb994f..61a9ec7e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Configuration of phone number in members registration forms: can be required or optional, depending on `PHONE_REQUIRED` configuration - Improved user experience in defining slots in the calendar management - Improved notification email to the member when a rolling subscription is taken +- Notify all admins on the creation of a refund invoice - Calendar management: improved legend display and visual behavior - Handle Ctrl^C in upgrade scripts - Updated moment-timezone diff --git a/app/models/avoir.rb b/app/models/avoir.rb index e8d6bd98f..641a80ce9 100644 --- a/app/models/avoir.rb +++ b/app/models/avoir.rb @@ -5,6 +5,8 @@ class Avoir < Invoice belongs_to :invoice + after_create :notify_admins_refund_created + validates :payment_method, inclusion: { in: %w[stripe cheque transfer none cash wallet] } attr_accessor :invoice_items_ids @@ -16,4 +18,12 @@ class Avoir < Invoice def expire_subscription user.subscription.expire(DateTime.current) end + + private + + def notify_admins_refund_created + NotificationCenter.call type: 'notify_admin_refund_created', + receiver: User.admins, + attached_object: self + end end diff --git a/app/models/notification_type.rb b/app/models/notification_type.rb index f73f76463..45efb7ae3 100644 --- a/app/models/notification_type.rb +++ b/app/models/notification_type.rb @@ -47,6 +47,7 @@ class NotificationType notify_admin_archive_complete notify_privacy_policy_changed notify_admin_import_complete + notify_admin_refund_created ] # deprecated: # - notify_member_subscribed_plan_is_changed diff --git a/app/views/api/notifications/_notify_admin_refund_created.json.jbuilder b/app/views/api/notifications/_notify_admin_refund_created.json.jbuilder new file mode 100644 index 000000000..17b8588de --- /dev/null +++ b/app/views/api/notifications/_notify_admin_refund_created.json.jbuilder @@ -0,0 +1,5 @@ +json.title notification.notification_type +json.description t('.refund_created', + AMOUNT: number_to_currency(notification.attached_object.total / 100.00), + USER: notification.attached_object.invoicing_profile&.full_name) +json.url notification_url(notification, format: :json) diff --git a/app/views/notifications_mailer/notify_admin_refund_created.html.erb b/app/views/notifications_mailer/notify_admin_refund_created.html.erb new file mode 100644 index 000000000..d0ec9e8ad --- /dev/null +++ b/app/views/notifications_mailer/notify_admin_refund_created.html.erb @@ -0,0 +1,8 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t('.body.refund_created', + AMOUNT: number_to_currency(@attached_object.total / 100.00), + INVOICE: @attached_object.invoice.reference, + USER: @attached_object.invoicing_profile&.full_name) %> +

    +

    " target="_blank"><%= t('.body.download') %>

    diff --git a/config/locales/en.yml b/config/locales/en.yml index 3868fd2c0..c54560d8d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -351,6 +351,9 @@ en: notify_privacy_policy_changed: policy_updated: "Privacy policy updated." click_to_show: "Click here to consult" + notify_admin_refund_created: + refund_created: "A refund of %{AMOUNT} has been created for user %{USER}" + statistics: # statistics tools for admins subscriptions: "Subscriptions" diff --git a/config/locales/es.yml b/config/locales/es.yml index d6eac0d60..05aefa471 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -351,6 +351,9 @@ es: notify_privacy_policy_changed: policy_updated: "Privacy policy updated." # missing translation click_to_show: "Click here to consult" # missing translation + notify_admin_refund_created: + refund_created: "A refund of %{AMOUNT} has been created for user %{USER}" # missing translation + statistics: # statistics tools for admins subscriptions: "Suscripciones" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index fef290c6d..44585e45b 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -351,6 +351,8 @@ fr: notify_privacy_policy_changed: policy_updated: "Nouvelle mise à jour de la Politique de confidentialité." click_to_show: "Cliquez ici pour la consulter" + notify_admin_refund_created: + refund_created: "Un avoir de %{AMOUNT} a été généré pour l'utilisateur %{USER}" statistics: # outil de statistiques pour les administrateurs diff --git a/config/locales/mails.en.yml b/config/locales/mails.en.yml index a97595db0..1709f0226 100644 --- a/config/locales/mails.en.yml +++ b/config/locales/mails.en.yml @@ -313,5 +313,11 @@ en: content_html: "

    We wish to inform you that we have just updated our privacy policy.

    We may change our privacy policy regularly. In accordance with the regulations, you will receive a notification for each update.

    By accessing or using our services after the privacy policy update, we will consider that you agree its terms, updates included.

    " link_to_policy: "Cliquez ici pour consultez la politique de confidentialité." + notify_admin_refund_created: + subject: "A refund has been generated" + body: + refund_created: "A refund of %{AMOUNT} has been generated on invoice %{INVOICE} of user %{USER}" + download: "Click here to download this refund invoice" + shared: hello: "Hello %{user_name}" diff --git a/config/locales/mails.es.yml b/config/locales/mails.es.yml index edf65a3c0..4e3e70175 100644 --- a/config/locales/mails.es.yml +++ b/config/locales/mails.es.yml @@ -299,7 +299,7 @@ es: warning_last_closed_period_over_1_year: "Please remind to periodically close your accounting periods. Last closed period ended at %{LAST_END}." warning_no_closed_periods: "Please remind to periodically close your accounting periods. You have to close periods from %{FIRST_DATE}." - notify_admin_archive_complete: #translation_missing + notify_admin_archive_complete: #translation_missing subject: "Archiving completed" body: archive_complete: "You have closed the accounting period from %{START} to %{END}. Archiving of data is now complete." @@ -307,11 +307,17 @@ es: here: "here." save_on_secured: "Remember that you must save this archive on a secured external support, which may be requested by the tax authorities during a check." - notify_privacy_policy_changed: #translation_missing + notify_privacy_policy_changed: #translation_missing subject: "Privacy policy updated" body: content_html: "

    We wish to inform you that we have just updated our privacy policy.

    We may change our privacy policy regularly. In accordance with the regulations, you will receive a notification for each update.

    By accessing or using our services after the privacy policy update, we will consider that you agree its terms, updates included.

    " link_to_policy: "Cliquez ici pour consultez la politique de confidentialité." + notify_admin_refund_created: #translation_missing + subject: "A refund has been generated" + body: + refund_created: "A refund of %{AMOUNT} has been generated on invoice %{INVOICE} of user %{USER}" + download: "Click here to download this refund invoice" + shared: hello: "¡Hola %{user_name}!" diff --git a/config/locales/mails.fr.yml b/config/locales/mails.fr.yml index e4d0c2d1b..0c7fc4852 100644 --- a/config/locales/mails.fr.yml +++ b/config/locales/mails.fr.yml @@ -313,5 +313,11 @@ fr: content_html: "

    Nous souhaitons vous signaler que nous venons de mettre à jour notre politique de confidentialité.

    Nous pouvons apporter régulièrement des modifications à notre politique de confidentialité. Conformément à la réglementation, une notification vous sera envoyée à chaque mise à jour.

    En accédant ou en utilisant nos services après la mise à jour de la Politique de confidentialité, nous considérerons que vous acceptez les termes de celle-ci, mises à jour comprises.

    " link_to_policy: "Cliquez ici pour consultez la politique de confidentialité." + notify_admin_refund_created: + subject: "Un avoir a été généré" + body: + refund_created: "Un avoir de %{AMOUNT} a été généré sur la facture %{INVOICE} de l'utilisateur %{USER}" + download: "Cliquez ici pour télécharger cet avoir" + shared: hello: "Bonjour %{user_name}" diff --git a/config/locales/mails.pt.yml b/config/locales/mails.pt.yml index fee5cf5e6..32cbee51a 100755 --- a/config/locales/mails.pt.yml +++ b/config/locales/mails.pt.yml @@ -299,7 +299,7 @@ pt: warning_last_closed_period_over_1_year: "Please remind to periodically close your accounting periods. Last closed period ended at %{LAST_END}." warning_no_closed_periods: "Please remind to periodically close your accounting periods. You have to close periods from %{FIRST_DATE}." - notify_admin_archive_complete: #translation_missing + notify_admin_archive_complete: #translation_missing subject: "Archiving completed" body: archive_complete: "You have closed the accounting period from %{START} to %{END}. Archiving of data is now complete." @@ -307,11 +307,17 @@ pt: here: "here." save_on_secured: "Remember that you must save this archive on a secured external support, which may be requested by the tax authorities during a check." - notify_privacy_policy_changed: #translation_missing + notify_privacy_policy_changed: #translation_missing subject: "Privacy policy updated" body: content_html: "

    We wish to inform you that we have just updated our privacy policy.

    We may change our privacy policy regularly. In accordance with the regulations, you will receive a notification for each update.

    By accessing or using our services after the privacy policy update, we will consider that you agree its terms, updates included.

    " link_to_policy: "Cliquez ici pour consultez la politique de confidentialité." + notify_admin_refund_created: #translation_missing + subject: "A refund has been generated" + body: + refund_created: "A refund of %{AMOUNT} has been generated on invoice %{INVOICE} of user %{USER}" + download: "Click here to download this refund invoice" + shared: hello: "Olá %{user_name}" diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 4e71e1dde..b6ece70a6 100755 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -349,8 +349,10 @@ pt: notify_admin_archive_complete: # missing translation archive_complete: "Data archiving from %{START} to %{END} is done. click here to download. Remember to save it on an external secured media." # missing translation notify_privacy_policy_changed: - policy_updated: "Privacy policy updated." # missing translation - click_to_show: "Click here to consult" # missing translation + policy_updated: "Privacy policy updated." # missing translation + click_to_show: "Click here to consult" # missing translation + notify_admin_refund_created: + refund_created: "A refund of %{AMOUNT} has been created for user %{USER}" # missing translation statistics: # statistics tools for admins From 0c11f61010f707e239c0100776115d22de233ce8 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 10 Dec 2019 12:16:26 +0100 Subject: [PATCH 068/135] Prevent event reservation in the past --- CHANGELOG.md | 1 + app/assets/javascripts/controllers/events.js.erb | 3 +++ app/assets/templates/events/show.html.erb | 7 ++++++- config/locales/app.public.en.yml | 3 +++ config/locales/app.public.es.yml | 3 +++ config/locales/app.public.fr.yml | 3 +++ config/locales/app.public.pt.yml | 3 +++ 7 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61a9ec7e3..885ab0d97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Improved notification email to the member when a rolling subscription is taken - Notify all admins on the creation of a refund invoice - Calendar management: improved legend display and visual behavior +- Prevent event reservation in the past [Taiga#127] - Handle Ctrl^C in upgrade scripts - Updated moment-timezone - Added freeCAD files as default allowed extensions diff --git a/app/assets/javascripts/controllers/events.js.erb b/app/assets/javascripts/controllers/events.js.erb index fa2129a3a..5b183029f 100644 --- a/app/assets/javascripts/controllers/events.js.erb +++ b/app/assets/javascripts/controllers/events.js.erb @@ -133,6 +133,9 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', ' // reservations for the currently shown event $scope.reservations = []; + // current date & time + $scope.now = moment(); + // user to deal with $scope.ctrl = { member: {} }; diff --git a/app/assets/templates/events/show.html.erb b/app/assets/templates/events/show.html.erb index 1c3ff1f4b..bd9965530 100644 --- a/app/assets/templates/events/show.html.erb +++ b/app/assets/templates/events/show.html.erb @@ -171,7 +171,12 @@
    - +
    + {{ 'event_is_over' }} + {{ 'thanks_for_coming' }} + {{ 'view_event_list' }} +
    +
    diff --git a/config/locales/app.public.en.yml b/config/locales/app.public.en.yml index 75575446a..3db122cd4 100644 --- a/config/locales/app.public.en.yml +++ b/config/locales/app.public.en.yml @@ -295,6 +295,9 @@ en: cancel_the_reservation: "Cancel the reservation" do_you_really_want_to_cancel_this_reservation_this_apply_to_all_booked_tickets: "Do you really want to cancel this reservation? This apply to ALL booked tickets." cancellation_failed: "Cancellation failed." + event_is_over: "The event is over" + thanks_for_coming: "Thanks for coming!" + view_event_list: "View events to come" calendar: # public calendar diff --git a/config/locales/app.public.es.yml b/config/locales/app.public.es.yml index b5bceebfc..ebe8fb16b 100644 --- a/config/locales/app.public.es.yml +++ b/config/locales/app.public.es.yml @@ -295,6 +295,9 @@ es: cancel_the_reservation: "Cancel the reservation" #translation_missing do_you_really_want_to_cancel_this_reservation_this_apply_to_all_booked_tickets: "Do you really want to cancel this reservation? This apply to ALL booked tickets." #translation_missing cancellation_failed: "Cancellation failed." #translation_missing + event_is_over: "The event is over" #translation_missing + thanks_for_coming: "Thanks for coming!" #translation_missing + view_event_list: "View events to come" #translation_missing calendar: # public calendar diff --git a/config/locales/app.public.fr.yml b/config/locales/app.public.fr.yml index c5493ad5f..653fdf691 100644 --- a/config/locales/app.public.fr.yml +++ b/config/locales/app.public.fr.yml @@ -295,6 +295,9 @@ fr: cancel_the_reservation: "Annuler la réservation" do_you_really_want_to_cancel_this_reservation_this_apply_to_all_booked_tickets: "Êtes vous sur de vouloir annuler cette réservation? Ceci s'applique à TOUTES les places réservées." cancellation_failed: "L'annulation a échoué." + event_is_over: "L'évènement est terminé." + thanks_for_coming: "Merci d'avoir participé !" + view_event_list: "Voir les évènements à venir" calendar: # calendrier publique diff --git a/config/locales/app.public.pt.yml b/config/locales/app.public.pt.yml index bd8088c68..8cf7a2bda 100755 --- a/config/locales/app.public.pt.yml +++ b/config/locales/app.public.pt.yml @@ -295,6 +295,9 @@ pt: cancel_the_reservation: "Cancel the reservation" #translation_missing do_you_really_want_to_cancel_this_reservation_this_apply_to_all_booked_tickets: "Do you really want to cancel this reservation? This apply to ALL booked tickets." #translation_missing cancellation_failed: "Cancellation failed." #translation_missin + event_is_over: "The event is over" #translation_missing + thanks_for_coming: "Thanks for coming!" #translation_missing + view_event_list: "View events to come" #translation_missing calendar: # public calendar From 34a2d2277161640a588c0eb84a190110510b4418 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 10 Dec 2019 16:32:15 +0100 Subject: [PATCH 069/135] updated GitHub issue template with link to roadmap --- .github/ISSUE_TEMPLATE.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 08f52b2da..0580a8c37 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,8 +1,14 @@ -This issue tracker is **reserved** for bug reports and feature requests. +This issue tracker is **reserved** for bug reports. -The place to ask a question or call for help is at Fab-manager forums at https://forum.fab-manager.com/. +The place to ask a question or call for help is at [Fab-manager forums](https://forum.fab-manager.com) + +The place to request or vote for new feature is on the [roadmap website](https://roadmap.fab-manager.com) To report a bug, please describe: - Expected behavior and actual behavior. - Steps to reproduce the problem. - Specifications like the version of the project, operating system, or hardware. + +The following elements may help to quickly resolve your issue: +- Server logs `tail -f /apps/fabmanager/log/app-stdout.log` on the server +- Client logs `Ctrl`+`Maj`+`i` > `Console` in the browser From 3c0e3a5f6c8196b451834a121ffdc6855de218f5 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 11 Dec 2019 12:42:47 +0100 Subject: [PATCH 070/135] Update Crowdin configuration file --- crowdin.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 crowdin.yml diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 000000000..0bc049730 --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,17 @@ +files: + - source: /config/locales/app.admin.en.yml + translation: /config/locales/app.admin.%two_letters_code%.yml + - source: /config/locales/app.logged.en.yml + translation: /config/locales/app.logged.%two_letters_code%.yml + - source: /config/locales/app.public.en.yml + translation: /config/locales/app.public.%two_letters_code%.yml + - source: /config/locales/app.shared.en.yml + translation: /config/locales/app.shared.%two_letters_code%.yml + - source: /config/locales/en.yml + translation: /config/locales/%two_letters_code%.yml + - source: /config/locales/devise.en.yml + translation: /config/locales/devise.%two_letters_code%.yml + - source: /config/locales/mails.en.yml + translation: /config/locales/mails.%two_letters_code%.yml + - source: /config/locales/rails.en.yml + translation: /config/locales/rails.%two_letters_code%.yml From ba99b0bda2c4bd4388e140cfae3f8034b8c88cf0 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 11 Dec 2019 15:02:57 +0100 Subject: [PATCH 071/135] change roadmap by feedback --- .github/ISSUE_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 0580a8c37..6f9669cf3 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -2,7 +2,7 @@ This issue tracker is **reserved** for bug reports. The place to ask a question or call for help is at [Fab-manager forums](https://forum.fab-manager.com) -The place to request or vote for new feature is on the [roadmap website](https://roadmap.fab-manager.com) +The place to request or vote for new feature is on the [feedback website](https://feedback.fab-manager.com) To report a bug, please describe: - Expected behavior and actual behavior. From c565d9ff2b544b7fa1647ee4a7237cf242427069 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 16 Dec 2019 10:16:11 +0100 Subject: [PATCH 072/135] unified front-end i18n interpolations syntax to messageformat --- app/assets/javascripts/app.js | 4 +- .../controllers/admin/calendar.js.erb | 11 +-- .../controllers/admin/events.js.erb | 4 +- .../javascripts/controllers/admin/groups.js | 6 +- .../controllers/admin/members.js.erb | 2 +- .../controllers/application.js.erb | 2 +- .../javascripts/controllers/events.js.erb | 4 +- .../javascripts/controllers/plans.js.erb | 4 +- app/assets/javascripts/directives/cart.js.erb | 4 +- .../admin/calendar/eventModal.html.erb | 2 +- .../templates/admin/members/edit.html.erb | 4 +- .../templates/admin/trainings/index.html.erb | 2 +- .../templates/dashboard/events.html.erb | 6 +- app/assets/templates/events/show.html.erb | 8 +- app/assets/templates/plans/index.html.erb | 8 +- .../templates/profile/complete.html.erb | 2 +- app/assets/templates/projects/show.html.erb | 2 +- app/assets/templates/shared/_cart.html.erb | 8 +- app/assets/templates/shared/header.html.erb | 2 +- .../templates/stripe/payment_modal.html.erb | 2 +- config/locales/app.admin.en.yml | 82 +++++++++--------- config/locales/app.admin.es.yml | 84 +++++++++---------- config/locales/app.admin.fr.yml | 82 +++++++++--------- config/locales/app.admin.pt.yml | 78 ++++++++--------- config/locales/app.logged.en.yml | 12 +-- config/locales/app.logged.es.yml | 12 +-- config/locales/app.logged.fr.yml | 12 +-- config/locales/app.logged.pt.yml | 12 +-- config/locales/app.public.en.yml | 32 +++---- config/locales/app.public.es.yml | 32 +++---- config/locales/app.public.fr.yml | 32 +++---- config/locales/app.public.pt.yml | 32 +++---- config/locales/app.shared.en.yml | 30 +++---- config/locales/app.shared.es.yml | 30 +++---- config/locales/app.shared.fr.yml | 30 +++---- config/locales/app.shared.pt.yml | 30 +++---- 36 files changed, 351 insertions(+), 358 deletions(-) diff --git a/app/assets/javascripts/app.js b/app/assets/javascripts/app.js index 134f71ab6..53d5e99b3 100644 --- a/app/assets/javascripts/app.js +++ b/app/assets/javascripts/app.js @@ -59,8 +59,8 @@ angular.module('application', ['ngCookies', 'ngResource', 'ngSanitize', 'ui.rout $translateProvider.useLoaderCache(true); // Secure i18n module against XSS attacks by escaping the output $translateProvider.useSanitizeValueStrategy('escapeParameters'); - // Enable the MessageFormat interpolation (used for pluralization) - $translateProvider.addInterpolation('$translateMessageFormatInterpolation'); + // Use the MessageFormat interpolation by default (used for pluralization) + $translateProvider.useMessageFormatInterpolation(); // Set the langage of the instance (from ruby configuration) $translateProvider.preferredLanguage(Fablab.locale); }]).run(['$rootScope', '$log', 'AuthService', 'Auth', 'amMoment', '$state', 'editableOptions', 'Analytics', diff --git a/app/assets/javascripts/controllers/admin/calendar.js.erb b/app/assets/javascripts/controllers/admin/calendar.js.erb index 220363e4d..1ea8ada06 100644 --- a/app/assets/javascripts/controllers/admin/calendar.js.erb +++ b/app/assets/javascripts/controllers/admin/calendar.js.erb @@ -87,8 +87,7 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state return { title: _t('admin_calendar.confirmation_required'), msg: _t('admin_calendar.do_you_really_want_to_cancel_the_USER_s_reservation_the_DATE_at_TIME_concerning_RESERVATION' - , { GENDER: getGender($scope.currentUser), USER: slot.user.name, DATE: moment(slot.start_at).format('L'), TIME: moment(slot.start_at).format('LT'), RESERVATION: slot.reservable.name } - , 'messageformat') + , { GENDER: getGender($scope.currentUser), USER: slot.user.name, DATE: moment(slot.start_at).format('L'), TIME: moment(slot.start_at).format('LT'), RESERVATION: slot.reservable.name }) }; } } @@ -131,7 +130,7 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state object () { return { title: _t('admin_calendar.confirmation_required'), - msg: _t('admin_calendar.do_you_really_want_to_remove_MACHINE_from_this_slot', { GENDER: getGender($scope.currentUser), MACHINE: machine.name }, 'messageformat') + ' ' + + msg: _t('admin_calendar.do_you_really_want_to_remove_MACHINE_from_this_slot', { GENDER: getGender($scope.currentUser), MACHINE: machine.name }) + ' ' + _t('admin_calendar.this_will_prevent_any_new_reservation_on_this_slot_but_wont_cancel_those_existing') + ' ' + _t('admin_calendar.beware_this_cannot_be_reverted') }; @@ -711,8 +710,7 @@ Application.Controllers.controller('DeleteRecurrentAvailabilityController', ['$s if (res.deleted > 1) { growl.success(_t( 'admin_calendar.slots_deleted', - {START: moment(start_at).format('LL LT'), COUNT: res.deleted - 1}, - 'messageformat' + {START: moment(start_at).format('LL LT'), COUNT: res.deleted - 1} )); } else { growl.success(_t( @@ -731,8 +729,7 @@ Application.Controllers.controller('DeleteRecurrentAvailabilityController', ['$s if (data.total > 1) { growl.warning(_t( 'admin_calendar.slots_not_deleted', - {TOTAL: data.total, COUNT: data.total - data.deleted}, - 'messageformat' + {TOTAL: data.total, COUNT: data.total - data.deleted} )); } else { growl.error(_t( diff --git a/app/assets/javascripts/controllers/admin/events.js.erb b/app/assets/javascripts/controllers/admin/events.js.erb index f1893864f..e2706ce85 100644 --- a/app/assets/javascripts/controllers/admin/events.js.erb +++ b/app/assets/javascripts/controllers/admin/events.js.erb @@ -232,7 +232,7 @@ Application.Controllers.controller('AdminEventsController', ['$scope', '$state', return false; } if (getModel(model)[1][index].related_to > 0) { - growl.error(_t('unable_to_delete_ELEMENT_already_in_use_NUMBER_times', { ELEMENT: model, NUMBER: getModel(model)[1][index].related_to }, 'messageformat')); + growl.error(_t('unable_to_delete_ELEMENT_already_in_use_NUMBER_times', { ELEMENT: model, NUMBER: getModel(model)[1][index].related_to })); return false; } return dialogs.confirm({ @@ -240,7 +240,7 @@ Application.Controllers.controller('AdminEventsController', ['$scope', '$state', object () { return { title: _t('confirmation_required'), - msg: _t('do_you_really_want_to_delete_this_ELEMENT', { ELEMENT: model }, 'messageformat') + msg: _t('do_you_really_want_to_delete_this_ELEMENT', { ELEMENT: model }) }; } } diff --git a/app/assets/javascripts/controllers/admin/groups.js b/app/assets/javascripts/controllers/admin/groups.js index 1f174ba5b..221d64d8f 100644 --- a/app/assets/javascripts/controllers/admin/groups.js +++ b/app/assets/javascripts/controllers/admin/groups.js @@ -86,13 +86,13 @@ Application.Controllers.controller('GroupsController', ['$scope', 'groupsPromise return $scope.toggleDisableGroup = function (index) { const group = $scope.groups[index]; if (!group.disabled && (group.users > 0)) { - return growl.error(_t('group_form.unable_to_disable_group_with_users', { USERS: group.users }, 'messageformat')); + return growl.error(_t('group_form.unable_to_disable_group_with_users', { USERS: group.users })); } else { return Group.update({ id: group.id }, { group: { disabled: !group.disabled } }, function (response) { $scope.groups[index] = response; - return growl.success(_t('group_form.group_successfully_enabled_disabled', { STATUS: response.disabled }, 'messageformat')); + return growl.success(_t('group_form.group_successfully_enabled_disabled', { STATUS: response.disabled })); } - , error => growl.error(_t('group_form.unable_to_enable_disable_group', { STATUS: !group.disabled }, 'messageformat'))); + , error => growl.error(_t('group_form.unable_to_enable_disable_group', { STATUS: !group.disabled }))); } }; } diff --git a/app/assets/javascripts/controllers/admin/members.js.erb b/app/assets/javascripts/controllers/admin/members.js.erb index bacbd58aa..ce7b16c41 100644 --- a/app/assets/javascripts/controllers/admin/members.js.erb +++ b/app/assets/javascripts/controllers/admin/members.js.erb @@ -750,7 +750,7 @@ Application.Controllers.controller('NewAdminController', ['$state', '$scope', 'A {}, { admin: $scope.admin }, function () { - growl.success(_t('administrator_successfully_created_he_will_receive_his_connection_directives_by_email', { GENDER: getGender($scope.admin) }, 'messageformat')); + growl.success(_t('administrator_successfully_created_he_will_receive_his_connection_directives_by_email', { GENDER: getGender($scope.admin) })); return $state.go('app.admin.members'); } , function (error) { diff --git a/app/assets/javascripts/controllers/application.js.erb b/app/assets/javascripts/controllers/application.js.erb index f048af8dd..834b3529b 100644 --- a/app/assets/javascripts/controllers/application.js.erb +++ b/app/assets/javascripts/controllers/application.js.erb @@ -325,7 +325,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco angular.forEach(notifications.notifications, function (n) { toDisplay.push(n); }); if (toDisplay.length < notifications.totals.unread) { - toDisplay.push({ message: { description: _t('and_NUMBER_other_notifications', { NUMBER: notifications.totals.unread - toDisplay.length }, 'messageformat') } }); + toDisplay.push({ message: { description: _t('and_NUMBER_other_notifications', { NUMBER: notifications.totals.unread - toDisplay.length }) } }); } angular.forEach(toDisplay, function (notification) { growl.info(notification.message.description); }); diff --git a/app/assets/javascripts/controllers/events.js.erb b/app/assets/javascripts/controllers/events.js.erb index 5b183029f..fa70501b1 100644 --- a/app/assets/javascripts/controllers/events.js.erb +++ b/app/assets/javascripts/controllers/events.js.erb @@ -750,10 +750,10 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', ' // Button label if ($scope.amount > 0) { - $scope.validButtonName = _t('confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')($scope.amount) }, 'messageformat'); + $scope.validButtonName = _t('confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')($scope.amount) }); } else { if ((price.price > 0) && ($scope.walletAmount === 0)) { - $scope.validButtonName = _t('confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')(price.price) }, 'messageformat'); + $scope.validButtonName = _t('confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')(price.price) }); } else { $scope.validButtonName = _t('confirm'); } diff --git a/app/assets/javascripts/controllers/plans.js.erb b/app/assets/javascripts/controllers/plans.js.erb index be4a19e7e..6db1445eb 100644 --- a/app/assets/javascripts/controllers/plans.js.erb +++ b/app/assets/javascripts/controllers/plans.js.erb @@ -318,10 +318,10 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop // Button label if ($scope.amount > 0) { - $scope.validButtonName = _t('confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')($scope.amount) }, 'messageformat'); + $scope.validButtonName = _t('confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')($scope.amount) }); } else { if ((price.price > 0) && ($scope.walletAmount === 0)) { - $scope.validButtonName = _t('confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')(price.price) }, 'messageformat'); + $scope.validButtonName = _t('confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')(price.price) }); } else { $scope.validButtonName = _t('confirm'); } diff --git a/app/assets/javascripts/directives/cart.js.erb b/app/assets/javascripts/directives/cart.js.erb index 75dcce2f0..0eb1dd94e 100644 --- a/app/assets/javascripts/directives/cart.js.erb +++ b/app/assets/javascripts/directives/cart.js.erb @@ -556,10 +556,10 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', // Button label if ($scope.amount > 0) { - $scope.validButtonName = _t('cart.confirm_payment_of_html', { ROLE: $rootScope.currentUser.role, AMOUNT: $filter('currency')($scope.amount) }, 'messageformat'); + $scope.validButtonName = _t('cart.confirm_payment_of_html', { ROLE: $rootScope.currentUser.role, AMOUNT: $filter('currency')($scope.amount) }); } else { if ((price.price > 0) && ($scope.walletAmount === 0)) { - $scope.validButtonName = _t('cart.confirm_payment_of_html', { ROLE: $rootScope.currentUser.role, AMOUNT: $filter('currency')(price.price) }, 'messageformat'); + $scope.validButtonName = _t('cart.confirm_payment_of_html', { ROLE: $rootScope.currentUser.role, AMOUNT: $filter('currency')(price.price) }); } else { $scope.validButtonName = _t('confirm'); } diff --git a/app/assets/templates/admin/calendar/eventModal.html.erb b/app/assets/templates/admin/calendar/eventModal.html.erb index 6dc0c822f..64ce005a4 100644 --- a/app/assets/templates/admin/calendar/eventModal.html.erb +++ b/app/assets/templates/admin/calendar/eventModal.html.erb @@ -154,7 +154,7 @@

    {{ 'admin_calendar.summary' }}

    - {{ 'admin_calendar.about_to_create' | translate:{NUMBER:occurrences.length,TYPE:availability.available_type}:'messageformat'}} + {{ 'admin_calendar.about_to_create' | translate:{NUMBER:occurrences.length,TYPE:availability.available_type}}}
    • {{slot.start_at | amDateFormat:'L LT'}} - {{slot.end_at | amDateFormat:'LT'}}
    diff --git a/app/assets/templates/admin/members/edit.html.erb b/app/assets/templates/admin/members/edit.html.erb index 90d8de79a..b9d249d22 100644 --- a/app/assets/templates/admin/members/edit.html.erb +++ b/app/assets/templates/admin/members/edit.html.erb @@ -158,11 +158,11 @@ {{r.reservable.title}} - {{ r.start_at | amDateFormat:'LLL' }} - {{ r.end_at | amDateFormat:'LT' }}
    - {{ 'NUMBER_full_price_tickets_reserved' }} + {{ 'NUMBER_full_price_tickets_reserved' }}

    - {{ 'NUMBER_NAME_tickets_reserved' }} + {{ 'NUMBER_NAME_tickets_reserved' }}
    diff --git a/app/assets/templates/admin/trainings/index.html.erb b/app/assets/templates/admin/trainings/index.html.erb index 647098869..3eb92b3e4 100644 --- a/app/assets/templates/admin/trainings/index.html.erb +++ b/app/assets/templates/admin/trainings/index.html.erb @@ -93,7 +93,7 @@ diff --git a/app/assets/templates/dashboard/events.html.erb b/app/assets/templates/dashboard/events.html.erb index 2c199becf..8adbb1cab 100644 --- a/app/assets/templates/dashboard/events.html.erb +++ b/app/assets/templates/dashboard/events.html.erb @@ -23,15 +23,13 @@ {{ r.start_at | amDateFormat:'LLL' }} - {{ r.end_at | amDateFormat:'LT' }}
    + translate-values="{NUMBER: r.nb_reserve_places}"> {{ 'NUMBER_normal_places_reserved' }}
    + translate-values="{NUMBER: ticket.booked, NAME: ticket.price_category.name}"> {{ 'NUMBER_of_NAME_places_reserved' }}
    diff --git a/app/assets/templates/events/show.html.erb b/app/assets/templates/events/show.html.erb index bd9965530..0d8c9e845 100644 --- a/app/assets/templates/events/show.html.erb +++ b/app/assets/templates/events/show.html.erb @@ -115,14 +115,14 @@
    {{ 'ticket' | translate:{NUMBER:reserve.nbReservePlaces}:"messageformat" }} + {{ 'ticket' | translate:{NUMBER:reserve.nbReservePlaces} }}
    {{ 'ticket' | translate:{NUMBER:reserve.tickets[price.id]}:"messageformat" }} + {{ 'ticket' | translate:{NUMBER:reserve.tickets[price.id]} }}
    @@ -154,9 +154,9 @@
    {{ 'you_booked_DATE' | translate:{DATE:(reservation.created_at | amDateFormat:'L LT')} }}
    -
    {{ 'full_price_' | translate }} {{reservation.nb_reserve_places}} {{ 'ticket' | translate:{NUMBER:reservation.nb_reserve_places}:"messageformat" }}
    +
    {{ 'full_price_' | translate }} {{reservation.nb_reserve_places}} {{ 'ticket' | translate:{NUMBER:reservation.nb_reserve_places} }}
    - {{ticket.event_price_category.price_category.name}} : {{ticket.booked}} {{ 'ticket' | translate:{NUMBER:ticket.booked}:"messageformat" }} + {{ticket.event_price_category.price_category.name}} : {{ticket.booked}} {{ 'ticket' | translate:{NUMBER:ticket.booked} }}
    {{ 'change' }} diff --git a/app/assets/templates/plans/index.html.erb b/app/assets/templates/plans/index.html.erb index 174a71d62..0b47bd74c 100644 --- a/app/assets/templates/plans/index.html.erb +++ b/app/assets/templates/plans/index.html.erb @@ -88,7 +88,7 @@

    {{ 'my_group' }}

    -

    {{ 'his_group' }}

    +

    {{ 'his_group' }}

    @@ -99,16 +99,14 @@ ng-click="group.change = !group.change" ng-show="(!selectedPlan && ctrl.member && !ctrl.member.subscribed_plan && ctrl.member.subscription) || (!paid.plan)" translate - translate-values="{ROLE:currentUser.role}" - translate-interpolation="messageformat">{{ 'he_wants_to_change_group' }} + translate-values="{ROLE:currentUser.role}">{{ 'he_wants_to_change_group' }}
    + translate-values="{ROLE:currentUser.role, GENDER:getGender(currentUser)}">{{ 'change_my_group' }}
    diff --git a/app/assets/templates/profile/complete.html.erb b/app/assets/templates/profile/complete.html.erb index a024ce6ef..57a104e1f 100644 --- a/app/assets/templates/profile/complete.html.erb +++ b/app/assets/templates/profile/complete.html.erb @@ -25,7 +25,7 @@
    - {{ 'you_ve_just_created_a_new_account_on_the_fablab_by_logging_from' | translate:{ GENDER: nameGenre, NAME: fablabName }:"messageformat" }}
    + {{ 'you_ve_just_created_a_new_account_on_the_fablab_by_logging_from' | translate:{ GENDER: nameGenre, NAME: fablabName } }}
    {{activeProvider.name}} ({{ssoEmail()}})

    {{ 'we_need_some_more_details' }}.

    diff --git a/app/assets/templates/projects/show.html.erb b/app/assets/templates/projects/show.html.erb index 0918d3a98..ebb456876 100644 --- a/app/assets/templates/projects/show.html.erb +++ b/app/assets/templates/projects/show.html.erb @@ -90,7 +90,7 @@
    {{project.project_caos_attributes.length}} -

    {{ 'CAD_file_to_download' }}

    +

    {{ 'CAD_file_to_download' }}

    - + '">
    diff --git a/app/assets/templates/dashboard/trainings.html.erb b/app/assets/templates/dashboard/trainings.html.erb index e7282172d..de757fff1 100644 --- a/app/assets/templates/dashboard/trainings.html.erb +++ b/app/assets/templates/dashboard/trainings.html.erb @@ -2,7 +2,7 @@
    - + '">
    @@ -13,7 +13,7 @@
    -

    {{ 'your_next_trainings' | translate }}

    +

    {{ 'app.logged.dashboard.trainings.your_next_trainings' | translate }}

      @@ -21,14 +21,14 @@ {{r.reservable.name}} - {{ r.start_at | amDateFormat:'LLL' }} - {{ r.end_at | amDateFormat:'LT' }}
    -
    {{ 'no_trainings' }}
    +
    {{ 'app.logged.dashboard.trainings.no_trainings' }}
    -

    {{ 'your_previous_trainings' | translate }}

    +

    {{ 'app.logged.dashboard.trainings.your_previous_trainings' | translate }}

      @@ -36,14 +36,14 @@ {{r.reservable.name}} - {{ r.start_at | amDateFormat:'LLL' }} - {{ r.end_at | amDateFormat:'LT' }}
    -
    {{ 'no_trainings' }}
    +
    {{ 'app.logged.dashboard.trainings.no_trainings' }}
    -

    {{ 'your_approved_trainings' | translate }}

    +

    {{ 'app.logged.dashboard.trainings.your_approved_trainings' | translate }}

      @@ -51,7 +51,7 @@ {{t.name}}
    -
    {{ 'no_trainings' }}
    +
    {{ 'app.logged.dashboard.trainings.no_trainings' }}
    diff --git a/app/assets/templates/dashboard/wallet.html.erb b/app/assets/templates/dashboard/wallet.html.erb index 175504c64..3eb89eb71 100644 --- a/app/assets/templates/dashboard/wallet.html.erb +++ b/app/assets/templates/dashboard/wallet.html.erb @@ -2,7 +2,7 @@
    - + '">
    @@ -10,11 +10,11 @@
    - + '">
    - + '">
    diff --git a/app/assets/templates/events/index.html.erb b/app/assets/templates/events/index.html.erb index 49b85f4a3..84fc67b1f 100644 --- a/app/assets/templates/events/index.html.erb +++ b/app/assets/templates/events/index.html.erb @@ -7,13 +7,13 @@
    -

    {{ 'the_fablab_s_events' }}

    +

    {{ 'app.public.events_list.the_fablab_s_events' }}

    @@ -23,19 +23,19 @@
    @@ -53,10 +53,10 @@
    {{event.category.name}}

    {{event.title}}

    {{event.start_date | amDateFormat:'L'}}

    -

    {{event.start_date | amDateFormat:'L'}} {{ 'to_date' }} {{event.end_date | amDateFormat:'L'}}

    +

    {{event.start_date | amDateFormat:'L'}} {{ 'app.public.events_list.to_date' }} {{event.end_date | amDateFormat:'L'}}

    -
    {{ 'free_admission' }}
    -
    {{ 'full_price_' | translate }} {{event.amount | currency}} / {{ price.category.name }} {{price.amount | currency}}
    +
    {{ 'app.public.events_list.free_admission' }}
    +
    {{ 'app.public.events_list.full_price_' | translate }} {{event.amount | currency}} / {{ price.category.name }} {{price.amount | currency}}
    {{event.event_themes[0].name}} @@ -64,10 +64,10 @@
    - {{event.nb_free_places}} {{ 'still_available' | translate }} - {{ 'sold_out' }} - {{ 'cancelled' }} - {{ 'free_entry' }} + {{event.nb_free_places}} {{ 'app.public.events_list.still_available' | translate }} + {{ 'app.public.events_list.sold_out' }} + {{ 'app.public.events_list.cancelled' }} + {{ 'app.public.events_list.free_entry' }}
    @@ -86,7 +86,7 @@ diff --git a/app/assets/templates/home.html.erb b/app/assets/templates/home.html.erb index f11b15937..9d0bbf796 100644 --- a/app/assets/templates/home.html.erb +++ b/app/assets/templates/home.html.erb @@ -5,7 +5,7 @@
    -

    {{ 'latest_documented_projects' }}

    +

    {{ 'app.public.home.latest_documented_projects' }}

    @@ -24,14 +24,14 @@
    -

    {{ 'latest_tweets' }}

    +

    {{ 'app.public.home.latest_tweets' }}

      @@ -44,7 +44,7 @@
      -

      {{ 'latest_registered_members' }}

      +

      {{ 'app.public.home.latest_registered_members' }}

      @@ -64,11 +64,11 @@
      - +
      - +
      @@ -80,7 +80,7 @@
    -

    {{ 'fablab_s_next_events' | translate }} {{ 'every_events' | translate }}

    +

    {{ 'app.public.home.fablab_s_next_events' | translate }} {{ 'app.public.home.every_events' | translate }}

    @@ -106,14 +106,14 @@
    -
    {{ 'from_date_to_date' | translate:{START:(event.start_date | amDateFormat:'L'), END:(event.end_date | amDateFormat:'L')} }}
    -
    {{ 'on_the_date' | translate:{DATE:(event.start_date | amDateFormat:'L')} }}
    +
    {{ 'app.public.home.from_date_to_date' | translate:{START:(event.start_date | amDateFormat:'L'), END:(event.end_date | amDateFormat:'L')} }}
    +
    {{ 'app.public.home.on_the_date' | translate:{DATE:(event.start_date | amDateFormat:'L')} }}
    - {{ 'all_day' }} - {{ 'from_time_to_time' | translate:{START:(event.start_date | amDateFormat:'LT'), END:(event.end_date | amDateFormat:'LT')} }} + {{ 'app.public.home.all_day' }} + {{ 'app.public.home.from_time_to_time' | translate:{START:(event.start_date | amDateFormat:'LT'), END:(event.end_date | amDateFormat:'LT')} }}
    @@ -122,22 +122,22 @@
    - {{ 'still_available' | translate }} {{event.nb_free_places}} - {{ 'free_entry' }} - {{ 'event_full' }} + {{ 'app.public.home.still_available' | translate }} {{event.nb_free_places}} + {{ 'app.public.home.free_entry' }} + {{ 'app.public.home.event_full' }}
    - {{ 'free_admission' }} - {{ 'full_price' | translate }} {{event.amount | currency}} + {{ 'app.public.home.free_admission' }} + {{ 'app.public.home.full_price' | translate }} {{event.amount | currency}}
    -
    {{ 'consult' }}
    +
    {{ 'app.shared.buttons.consult' }}
    diff --git a/app/assets/templates/machines/index.html.erb b/app/assets/templates/machines/index.html.erb index 112f4dbdb..39cd2968d 100644 --- a/app/assets/templates/machines/index.html.erb +++ b/app/assets/templates/machines/index.html.erb @@ -7,13 +7,13 @@
    -

    {{ 'machines_list.the_fablab_s_machines' }}

    +

    {{ 'app.public.machines_list.the_fablab_s_machines' }}

    @@ -26,7 +26,7 @@
    @@ -48,13 +48,13 @@
    - +
    - +
    diff --git a/app/assets/templates/machines/show.html.erb b/app/assets/templates/machines/show.html.erb index 0e332c35b..2e4849842 100644 --- a/app/assets/templates/machines/show.html.erb +++ b/app/assets/templates/machines/show.html.erb @@ -15,15 +15,15 @@
    - {{ 'book_this_machine' }} + translate>{{ 'app.public.machines_show.book_this_machine' }} - {{ 'edit' | translate }} + {{ 'app.shared.buttons.edit' | translate }} - +
    @@ -48,7 +48,7 @@
    -

    {{ 'technical_specifications' }}

    +

    {{ 'app.public.machines_show.technical_specifications' }}

    @@ -59,19 +59,19 @@
    {{machine.machine_files_attributes.length}} -

    {{ 'files_to_download' }}

    +

    {{ 'app.public.machines_show.files_to_download' }}

    - -
    -

    {{ 'projects_using_the_machine' }}

    +

    {{ 'app.public.machines_show.projects_using_the_machine' }}

      diff --git a/app/assets/templates/members/index.html.erb b/app/assets/templates/members/index.html.erb index e9f5e2124..4a68a1514 100644 --- a/app/assets/templates/members/index.html.erb +++ b/app/assets/templates/members/index.html.erb @@ -7,7 +7,7 @@
    -

    {{ 'the_fablab_members' }}

    +

    {{ 'app.logged.members.the_fablab_members' }}

    @@ -21,10 +21,10 @@ - - - - + + + + @@ -41,7 +41,7 @@ @@ -49,9 +49,9 @@
    {{ 'avatar' }}{{ 'user' }}{{ 'pseudonym' }}{{ 'email_address' }}{{ 'app.logged.members.avatar' }}{{ 'app.logged.members.user' }}{{ 'app.logged.members.pseudonym' }}{{ 'app.logged.members.email_address' }}
    - +
    -

    {{ 'no_members_for_now' }}

    +

    {{ 'app.logged.members.no_members_for_now' }}

    diff --git a/app/assets/templates/members/show.html.erb b/app/assets/templates/members/show.html.erb index 355c83924..7205b8424 100644 --- a/app/assets/templates/members/show.html.erb +++ b/app/assets/templates/members/show.html.erb @@ -15,11 +15,11 @@ - + '"> diff --git a/app/assets/templates/notifications/index.html.erb b/app/assets/templates/notifications/index.html.erb index 1d244d4cd..18a1505aa 100644 --- a/app/assets/templates/notifications/index.html.erb +++ b/app/assets/templates/notifications/index.html.erb @@ -7,7 +7,7 @@
    -

    {{ 'notifications_center' }}

    +

    {{ 'app.logged.notifications.notifications_center' }}

    @@ -19,14 +19,14 @@
    - + - - + + @@ -42,13 +42,13 @@ - +
    {{ 'date' }}{{ 'notif_title' }}{{ 'app.logged.notifications.date' }}{{ 'app.logged.notifications.notif_title' }}
    {{ 'no_new_notifications' }}{{ 'app.logged.notifications.no_new_notifications' }}
    -
    {{ 'archives' }}
    +
    {{ 'app.logged.notifications.archives' }}
    @@ -73,7 +73,7 @@ - + @@ -81,7 +81,7 @@
    {{ 'no_archived_notifications' }}{{ 'app.logged.notifications.no_archived_notifications' }}
    - {{ 'load_the_next_notifications' }} + {{ 'app.logged.notifications.load_the_next_notifications' }}
    diff --git a/app/assets/templates/plans/index.html.erb b/app/assets/templates/plans/index.html.erb index 0b47bd74c..69c2ac6c3 100644 --- a/app/assets/templates/plans/index.html.erb +++ b/app/assets/templates/plans/index.html.erb @@ -7,7 +7,7 @@
    -

    {{ 'subcriptions' }}

    +

    {{ 'app.public.plans.subcriptions' }}

    @@ -44,24 +44,24 @@
    - +

    - {{ 'more_information' }} + {{ 'app.public.plans.more_information' }} @@ -69,7 +69,7 @@
    - {{ 'your_subscription_expires_on_the_DATE' | translate:{DATE:(currentUser.subscription.expired_at | amDateFormat:'L' )} }} + {{ 'app.public.plans.your_subscription_expires_on_the_DATE' | translate:{DATE:(currentUser.subscription.expired_at | amDateFormat:'L' )} }}
    @@ -87,8 +87,8 @@
    -

    {{ 'my_group' }}

    -

    {{ 'his_group' }}

    +

    {{ 'app.public.plans.my_group' }}

    +

    {{ 'app.public.plans.his_group' }}

    @@ -99,70 +99,70 @@ ng-click="group.change = !group.change" ng-show="(!selectedPlan && ctrl.member && !ctrl.member.subscribed_plan && ctrl.member.subscription) || (!paid.plan)" translate - translate-values="{ROLE:currentUser.role}">{{ 'he_wants_to_change_group' }} + translate-values="{ROLE:currentUser.role}">{{ 'app.public.plans.he_wants_to_change_group' }}
    + translate-values="{ROLE:currentUser.role, GENDER:getGender(currentUser)}">{{ 'app.public.plans.change_my_group' }}
    -

    {{ 'summary' }}

    +

    {{ 'app.public.plans.summary' }}

    - {{ 'your_subscription_has_expired_on_the_DATE' | translate:{DATE:(ctrl.member.subscription.expired_at | amDateFormat:'LL')} }} + {{ 'app.public.plans.your_subscription_has_expired_on_the_DATE' | translate:{DATE:(ctrl.member.subscription.expired_at | amDateFormat:'LL')} }}
    {{ctrl.member.subscription.plan | humanReadablePlanName }} -
    {{ 'subscription_price' | translate }} {{ctrl.member.subscription.plan.amount | currency}}
    +
    {{ 'app.public.plans.subscription_price' | translate }} {{ctrl.member.subscription.plan.amount | currency}}
    -

    {{ 'summary' }}

    +

    {{ 'app.public.plans.summary' }}

    - {{ 'you_ve_just_selected_a_' | translate }} {{ '_subscription' }} : + {{ 'app.public.plans.you_ve_just_selected_a_subscription_html' }}
    {{ selectedPlan | humanReadablePlanName }} -
    {{ 'subscription_price' | translate }} {{selectedPlan.amount | currency}}
    +
    {{ 'app.public.plans.subscription_price' | translate }} {{selectedPlan.amount | currency}}
    -

    {{ 'summary' }}

    +

    {{ 'app.public.plans.summary' }}

    - {{ 'you_ve_just_payed_the_' | translate }} {{ '_subscription' }} : + {{ 'app.public.plans.you_ve_just_payed_the_subscription_html' }}
    {{ paid.plan | humanReadablePlanName }} -
    {{ 'subscription_price' | translate }} {{paid.plan.amount | currency}}
    +
    {{ 'app.public.plans.subscription_price' | translate }} {{paid.plan.amount | currency}}
    -
    {{ 'thank_you_your_subscription_is_successful' | translate }}
    - {{ 'your_invoice_will_be_available_soon_from_your_' | translate }} {{ 'dashboard' }}
    +
    {{ 'app.public.plans.thank_you_your_subscription_is_successful' | translate }}
    + {{ 'app.public.plans.your_invoice_will_be_available_soon_from_your_dashboard' }}
    diff --git a/app/assets/templates/plans/payment_modal.html.erb b/app/assets/templates/plans/payment_modal.html.erb index 7b1f97185..db6145684 100644 --- a/app/assets/templates/plans/payment_modal.html.erb +++ b/app/assets/templates/plans/payment_modal.html.erb @@ -1,15 +1,15 @@ diff --git a/app/assets/templates/projects/index.html.erb b/app/assets/templates/projects/index.html.erb index f927f20b2..7d5f00cba 100644 --- a/app/assets/templates/projects/index.html.erb +++ b/app/assets/templates/projects/index.html.erb @@ -7,13 +7,13 @@
    -

    {{ 'projects_list.the_fablab_projects' }}

    +

    {{ 'app.public.projects_list.the_fablab_projects' }}

    @@ -23,16 +23,16 @@
    - {{ 'projects_list.reset_all_filters' | translate }} + {{ 'app.public.projects_list.reset_all_filters' | translate }} - - + + @@ -44,7 +44,7 @@
    - +
    @@ -53,27 +53,27 @@
    @@ -81,7 +81,7 @@
    - {{ 'projects_list.project_search_result_is_empty' | translate }} + {{ 'app.public.projects_list.project_search_result_is_empty' | translate }}
    @@ -99,7 +99,7 @@
    - {{ 'projects_list.rough_draft' }} + {{ 'app.public.projects_list.rough_draft' }}
    diff --git a/app/assets/templates/shared/_member_form.html.erb b/app/assets/templates/shared/_member_form.html.erb index f2678e33f..e42b39a11 100644 --- a/app/assets/templates/shared/_member_form.html.erb +++ b/app/assets/templates/shared/_member_form.html.erb @@ -21,8 +21,8 @@ - {{ 'add_an_avatar' }} - {{ 'change' }} + {{ 'app.shared.user.add_an_avatar' }} + {{ 'app.shared.buttons.change' }} @@ -47,7 +47,7 @@ value="true" ng-disabled="preventField['profile.gender'] && user.statistic_profile.gender && !userForm['user[statistic_profile_attributes][gender]'].$dirty" required/> - {{ 'man' | translate }} + {{ 'app.shared.user.man' | translate }} - + - {{ 'gender_is_required' }} + {{ 'app.shared.user.gender_is_required' }}
    - +
    - {{ 'pseudonym_is_required' }} + {{ 'app.shared.user.pseudonym_is_required' }}
    - +
    - {{ 'surname_is_required' }} + {{ 'app.shared.user.surname_is_required' }}
    - +
    - {{ 'first_name_is_required' }} + {{ 'app.shared.user.first_name_is_required' }}
    - +
    - {{ 'email_address_is_required' }} + {{ 'app.shared.user.email_address_is_required' }}
    + translate>{{ 'app.shared.user.change_password' }}
    @@ -141,12 +141,12 @@ ng-model="user.password" class="form-control" id="user_password" - placeholder="{{ 'new_password' | translate }}" + placeholder="{{ 'app.shared.user.new_password' | translate }}" ng-minlength="8" required/> - {{ 'password_is_required' }} - {{ 'password_is_too_short' }} + {{ 'app.shared.user.password_is_required' }} + {{ 'app.shared.user.password_is_too_short' }}
    @@ -157,19 +157,19 @@ ng-model="user.password_confirmation" class="form-control" id="user_password_confirmation" - placeholder="{{ 'confirmation_of_new_password' | translate }}" + placeholder="{{ 'app.shared.user.confirmation_of_new_password' | translate }}" ng-minlength="8" required match="user.password"/>
    - {{ 'confirmation_of_password_is_required' }} - {{ 'confirmation_of_password_is_too_short' }} - {{ 'confirmation_mismatch_with_password' }} + {{ 'app.shared.user.confirmation_of_password_is_required' }} + {{ 'app.shared.user.confirmation_of_password_is_too_short' }} + {{ 'app.shared.user.confirmation_mismatch_with_password' }}
    - + @@ -177,16 +177,16 @@ name="user[invoicing_profile_attributes][organization_attributes][name]" ng-model="user.invoicing_profile.organization.name" class="form-control" - placeholder="{{ 'organization_name' | translate }}" + placeholder="{{ 'app.shared.user.organization_name' | translate }}" ng-required="user.invoicing_profile.organization" ng-disabled="preventField['profile.organization_name'] && user.invoicing_profile.organization.name && !userForm['user[invoicing_profile_attributes][organization_attributes][name]'].$dirty">
    - {{ 'organization_name_is_required' }} + {{ 'app.shared.user.organization_name_is_required' }}
    - + @@ -194,16 +194,16 @@ name="user[invoicing_profile_attributes][organization_attributes][address_attributes][address]" ng-model="user.invoicing_profile.organization.address.address" class="form-control" - placeholder="{{ 'organization_address' | translate }}" + placeholder="{{ 'app.shared.user.organization_address' | translate }}" ng-required="user.invoicing_profile.organization" ng-disabled="preventField['profile.organization_address'] && user.invoicing_profile.organization.address.address && !userForm['user[invoicing_profile_attributes][organization_attributes][address_attributes][address]'].$dirty">
    - {{ 'organization_address_is_required' }} + {{ 'app.shared.user.organization_address_is_required' }}
    - + @@ -219,12 +219,12 @@ name="user[statistic_profile_attributes][birthday]" value="{{user.statistic_profile.birthday | toIsoDate}}" />
    - {{ 'date_of_birth_is_required' }} + {{ 'app.shared.user.date_of_birth_is_required' }}
    - + @@ -234,35 +234,35 @@ class="form-control" id="user_address" ng-disabled="preventField['profile.address'] && user.invoicing_profile.address.address && !userForm['user[invoicing_profile_attributes][address_attributes][address]'].$dirty" - placeholder="{{ 'address' | translate }}"/> + placeholder="{{ 'app.shared.user.address' | translate }}"/>
    - +
    - {{ 'phone_number_is_required' }} + {{ 'app.shared.user.phone_number_is_required' }}
    - +
    @@ -275,13 +275,13 @@ ng-model="user.profile.job" class="form-control" id="user_job" - placeholder="{{ 'job' | translate }}" + placeholder="{{ 'app.shared.user.job' | translate }}" ng-disabled="preventField['profile.job'] && user.profile.job && !userForm['user[profile_attributes][job]'].$dirty"/>
    - +
    diff --git a/app/assets/templates/admin/invoices/closePeriodModal.html.erb b/app/assets/templates/admin/invoices/closePeriodModal.html.erb index f2ada5808..7c909bd2c 100644 --- a/app/assets/templates/admin/invoices/closePeriodModal.html.erb +++ b/app/assets/templates/admin/invoices/closePeriodModal.html.erb @@ -1,10 +1,10 @@
    -

    {{ 'invoices.previous_closings' }}

    +

    {{ 'app.admin.invoices.previous_closings' }}

    - - + + @@ -72,10 +72,10 @@
    {{ 'invoices.start_date' }}{{ 'invoices.end_date' }}{{ 'app.admin.invoices.start_date' }}{{ 'app.admin.invoices.end_date' }}
    -
    {{ 'invoices.no_periods'}}
    +
    {{ 'app.admin.invoices.no_periods'}}
    diff --git a/app/assets/templates/admin/invoices/index.html.erb b/app/assets/templates/admin/invoices/index.html.erb index 9ca28e005..e6ebf5f7d 100644 --- a/app/assets/templates/admin/invoices/index.html.erb +++ b/app/assets/templates/admin/invoices/index.html.erb @@ -7,14 +7,14 @@
    -

    {{ 'invoices.invoices' }}

    +

    {{ 'app.admin.invoices.invoices' }}

    @@ -24,14 +24,14 @@
    - -

    {{ 'invoices.filter_invoices' | translate }}

    + +

    {{ 'app.admin.invoices.filter_invoices' | translate }}

    @@ -116,10 +116,10 @@ - +
    - {{ 'invoices.john_smith' }} -
    {{ 'invoices.john_smith_at_example_com' }}
    + {{ 'app.admin.invoices.john_smith' }} +
    {{ 'app.admin.invoices.john_smith_at_example_com' }}
    -
    {{ 'invoices.invoice_reference_' | translate }} {{mkReference()}}
    -
    {{ 'invoices.code_' | translate }} {{invoice.code.model}}
    -
    {{ 'invoices.code_disabled' }}
    -
    {{ 'invoices.order_num' | translate }} {{mkNumber()}}
    -
    {{ 'invoices.invoice_issued_on_DATE_at_TIME' | translate:{DATE:(today | amDateFormat:'L'), TIME:(today | amDateFormat:'LT')} }}
    +
    {{ 'app.admin.invoices.invoice_reference_' | translate }} {{mkReference()}}
    +
    {{ 'app.admin.invoices.code_' | translate }} {{invoice.code.model}}
    +
    {{ 'app.admin.invoices.code_disabled' }}
    +
    {{ 'app.admin.invoices.order_num' | translate }} {{mkNumber()}}
    +
    {{ 'app.admin.invoices.invoice_issued_on_DATE_at_TIME' | translate:{DATE:(today | amDateFormat:'L'), TIME:(today | amDateFormat:'LT')} }}
    - {{ 'invoices.object_reservation_of_john_smith_on_DATE_at_TIME' | translate:{DATE:(inOneWeek | amDateFormat:'L'), TIME:(inOneWeek | amDateFormat:'LT')} }} + {{ 'app.admin.invoices.object_reservation_of_john_smith_on_DATE_at_TIME' | translate:{DATE:(inOneWeek | amDateFormat:'L'), TIME:(inOneWeek | amDateFormat:'LT')} }}
    - {{ 'invoices.order_summary' | translate }} + {{ 'app.admin.invoices.order_summary' | translate }} - - + + - + - - + + - + - + - + - +
    {{ 'invoices.details' }}{{ 'invoices.amount' }}{{ 'app.admin.invoices.details' }}{{ 'app.admin.invoices.amount' }}
    {{ 'invoices.machine_booking-3D_printer' | translate }} {{inOneWeek | amDateFormat:'LLL'}} - {{inOneWeekAndOneHour | amDateFormat:'LT'}}{{ 'app.admin.invoices.machine_booking-3D_printer' | translate }} {{inOneWeek | amDateFormat:'LLL'}} - {{inOneWeekAndOneHour | amDateFormat:'LT'}} {{30.0 | currency}}
    {{ 'invoices.total_amount' }}{{ 'invoices.total_including_all_taxes' }}{{ 'app.admin.invoices.total_amount' }}{{ 'app.admin.invoices.total_including_all_taxes' }} {{30.0 | currency}}
    {{ 'invoices.VAT_disabled' }}{{ 'app.admin.invoices.VAT_disabled' }}
    {{ 'invoices.including_VAT' | translate }} {{invoice.VAT.rate}} %{{ 'app.admin.invoices.including_VAT' | translate }} {{invoice.VAT.rate}} % {{30-(30/(invoice.VAT.rate/100+1)) | currency}}
    {{ 'invoices.including_total_excluding_taxes' }}{{ 'app.admin.invoices.including_total_excluding_taxes' }} {{30/(invoice.VAT.rate/100+1) | currency}}
    {{ 'invoices.including_amount_payed_on_ordering' }}{{ 'app.admin.invoices.including_amount_payed_on_ordering' }} {{30.0 | currency}}

    - {{ 'invoices.settlement_by_debit_card_on_DATE_at_TIME_for_an_amount_of_AMOUNT' }} + {{ 'app.admin.invoices.settlement_by_debit_card_on_DATE_at_TIME_for_an_amount_of_AMOUNT' }}

    +
    - - + +
    - - + +
    - - + +
    - - + +
    - - + +
    - - + +
    - - + +
    - - + +
    - - + +
    - - + +
    - - + +
    - - + +
    - - + +
    - - + +
    - - + +
    - - + +
    - - + +
    - - + +
    - - + +
    - - + +
    - - + +
    - +
    @@ -333,125 +333,125 @@ @@ -461,33 +461,33 @@ @@ -496,23 +496,23 @@ diff --git a/app/assets/templates/admin/members/_form.html.erb b/app/assets/templates/admin/members/_form.html.erb index a6d9fc7d6..7c311aff5 100644 --- a/app/assets/templates/admin/members/_form.html.erb +++ b/app/assets/templates/admin/members/_form.html.erb @@ -1,18 +1,18 @@
    - {{ 'group_is_required' }} + {{ 'app.shared.user_admin.group_is_required' }}
    - +
    @@ -28,7 +28,7 @@
    - +
    diff --git a/app/assets/templates/admin/members/administrators.html.erb b/app/assets/templates/admin/members/administrators.html.erb index fa2cc4c79..31ce9c3b0 100644 --- a/app/assets/templates/admin/members/administrators.html.erb +++ b/app/assets/templates/admin/members/administrators.html.erb @@ -2,23 +2,23 @@
    - +
    \ No newline at end of file +
    diff --git a/app/assets/templates/admin/members/edit.html.erb b/app/assets/templates/admin/members/edit.html.erb index b9d249d22..25fb2add6 100644 --- a/app/assets/templates/admin/members/edit.html.erb +++ b/app/assets/templates/admin/members/edit.html.erb @@ -9,8 +9,8 @@
    -

    {{ 'user' | translate }} {{ user.name }}

    - {{ 'incomplete_profile' }} +

    {{ 'app.shared.user_admin.user' | translate }} {{ user.name }}

    + {{ 'app.shared.user_admin.incomplete_profile' }}
    @@ -18,7 +18,7 @@
    - {{ 'cancel' }} + {{ 'app.shared.buttons.cancel' }}
    @@ -34,11 +34,11 @@ - +
    - {{ 'warning_incomplete_user_profile_probably_imported_from_sso' }} + {{ 'app.shared.user_admin.warning_incomplete_user_profile_probably_imported_from_sso' }}
    @@ -46,21 +46,21 @@
    - + '"> - + '">
    - +
    @@ -68,24 +68,24 @@

    {{ subscription.plan | humanReadablePlanName }}

    - {{ 'duration' | translate }} {{ subscription.plan.interval | planIntervalFilter: subscription.plan.interval_count }} + {{ 'app.admin.members_edit.duration' | translate }} {{ subscription.plan.interval | planIntervalFilter: subscription.plan.interval_count }}

    - {{ 'expires_at' | translate }} {{ subscription.expired_at | amDateFormat: 'L' }} + {{ 'app.admin.members_edit.expires_at' | translate }} {{ subscription.expired_at | amDateFormat: 'L' }}

    - {{ 'price_' | translate }} {{ subscription.plan.amount | currency}} + {{ 'app.admin.members_edit.price_' | translate }} {{ subscription.plan.amount | currency}}

    - - + +

    - {{ 'user_has_no_current_subscription' }} + {{ 'app.admin.members_edit.user_has_no_current_subscription' }}

    - +
    @@ -93,11 +93,11 @@ - +
    -

    {{ 'next_trainings' | translate }}

    +

    {{ 'app.admin.members_edit.next_trainings' | translate }}

      @@ -105,14 +105,14 @@ {{r.reservable.name}} - {{ r.start_at | amDateFormat:'LLL' }} - {{ r.end_at | amDateFormat:'LT' }}
    -
    {{ 'no_trainings' }}
    +
    {{ 'app.admin.members_edit.no_trainings' }}
    -

    {{ 'passed_trainings' | translate }}

    +

    {{ 'app.admin.members_edit.passed_trainings' | translate }}

      @@ -125,14 +125,14 @@
    --> -
    {{ 'no_trainings' }}
    +
    {{ 'app.admin.members_edit.no_trainings' }}
    -

    {{ 'validated_trainings' | translate }}

    +

    {{ 'app.admin.members_edit.validated_trainings' | translate }}

      @@ -140,17 +140,17 @@ {{t.name}}
    -
    {{ 'no_trainings' }}
    +
    {{ 'app.admin.members_edit.no_trainings' }}
    - +
    -

    {{ 'next_events' | translate }}

    +

    {{ 'app.admin.members_edit.next_events' | translate }}

      @@ -158,22 +158,22 @@ {{r.reservable.title}} - {{ r.start_at | amDateFormat:'LLL' }} - {{ r.end_at | amDateFormat:'LT' }}
      - {{ 'NUMBER_full_price_tickets_reserved' }} + {{ 'app.admin.members_edit.NUMBER_full_price_tickets_reserved' }}

      - {{ 'NUMBER_NAME_tickets_reserved' }} + {{ 'app.admin.members_edit.NUMBER_NAME_tickets_reserved' }}
    -
    {{ 'no_upcoming_events' }}
    +
    {{ 'app.admin.members_edit.no_upcoming_events' }}
    -

    {{ 'passed_events' | translate }}

    +

    {{ 'app.admin.members_edit.passed_events' | translate }}

      @@ -181,22 +181,22 @@ {{r.reservable.title}} - {{ r.start_at | amDateFormat:'LLL' }} - {{ r.end_at | amDateFormat:'LT' }}
    -
    {{ 'no_passed_events' }}
    +
    {{ 'app.admin.members_edit.no_passed_events' }}
    - +
    - - - + + + @@ -209,34 +209,34 @@
    {{ 'invoice_num' }}{{ 'date' }}{{ 'price' }}{{ 'app.admin.members_edit.invoice_num' }}{{ 'app.admin.members_edit.date' }}{{ 'app.admin.members_edit.price' }}
    -

    {{ 'no_invoices_for_now' }}

    +

    {{ 'app.admin.members_edit.no_invoices_for_now' }}

    - +
    - + '">
    - +
    - + '">
    diff --git a/app/assets/templates/admin/members/import.html.erb b/app/assets/templates/admin/members/import.html.erb index 8ce03827e..d7c46e128 100644 --- a/app/assets/templates/admin/members/import.html.erb +++ b/app/assets/templates/admin/members/import.html.erb @@ -11,14 +11,14 @@
    -

    {{ 'members_import.import_members' }}

    +

    {{ 'app.admin.members_import.import_members' }}

    @@ -29,7 +29,7 @@

    - {{ 'members_import.info' }} + {{ 'app.admin.members_import.info' }}

    @@ -37,12 +37,12 @@
    -

    {{ 'members_import.groups' }}

    +

    {{ 'app.admin.members_import.groups' }}

    - - + + @@ -59,12 +59,12 @@
    -

    {{ 'members_import.trainings' }}

    +

    {{ 'app.admin.members_import.trainings' }}

    {{ 'members_import.group_name' }}{{ 'members_import.group_identifier' }}{{ 'app.admin.members_import.group_name' }}{{ 'app.admin.members_import.group_identifier' }}
    - - + + @@ -86,12 +86,12 @@
    -

    {{ 'members_import.tags' }}

    +

    {{ 'app.admin.members_import.tags' }}

    {{ 'members_import.training_name' }}{{ 'members_import.training_identifier' }}{{ 'app.admin.members_import.training_name' }}{{ 'app.admin.members_import.training_identifier' }}
    - - + + @@ -119,10 +119,10 @@

    - {{ 'members_import.required_fields' }} + {{ 'app.admin.members_import.required_fields' }}

    - {{ 'members_import.about_example' }} + {{ 'app.admin.members_import.about_example' }}

    @@ -130,8 +130,8 @@
    {{file.attachment}}
    - {{ 'members_import.select_file' }} - {{ 'change' }} + {{ 'app.admin.members_import.select_file' }} + {{ 'app.shared.buttons.change' }}
    - {{ 'members_import.update_field' }} + {{ 'app.admin.members_import.update_field' }}
    @@ -165,7 +165,7 @@ diff --git a/app/assets/templates/admin/members/import_result.html b/app/assets/templates/admin/members/import_result.html index 16e7ae9e3..231db53c2 100644 --- a/app/assets/templates/admin/members/import_result.html +++ b/app/assets/templates/admin/members/import_result.html @@ -11,7 +11,7 @@
    -

    {{ 'members_import_result.import_results' }}

    +

    {{ 'app.admin.members_import_result.import_results' }}

    @@ -22,11 +22,11 @@
    -

    {{ 'members_import_result.import_details' | translate:{DATE:(import.created_at | amDateFormat:'L'), USER:import.user.full_name, ID:import.id} }}

    +

    {{ 'app.admin.members_import_result.import_details' | translate:{DATE:(import.created_at | amDateFormat:'L'), USER:import.user.full_name, ID:import.id} }}

    -

    {{ 'members_import_result.pending' }}

    +

    {{ 'app.admin.members_import_result.pending' }}

    -

    {{ 'members_import_result.results' }}

    +

    {{ 'app.admin.members_import_result.results' }}

    @@ -41,21 +41,21 @@
    - {{ 'members_import_result.status_' + resultRow.status | translate:{ID:resultRow.user} }} + {{ 'app.admin.members_import_result.status_' + resultRow.status | translate:{ID:resultRow.user} }} - {{ 'members_import_result.success' }} + {{ 'app.admin.members_import_result.success' }} - {{ 'members_import_result.failed' }} + {{ 'app.admin.members_import_result.failed' }}
    - {{ 'members_import_result.error_details' }}{{resultRow}} + {{ 'app.admin.members_import_result.error_details' }}{{resultRow}}
    diff --git a/app/assets/templates/admin/members/new.html.erb b/app/assets/templates/admin/members/new.html.erb index a4b233c19..4c01fae74 100644 --- a/app/assets/templates/admin/members/new.html.erb +++ b/app/assets/templates/admin/members/new.html.erb @@ -9,7 +9,7 @@
    -

    {{ 'members_new.add_a_member' }}

    +

    {{ 'app.admin.members_new.add_a_member' }}

    @@ -17,7 +17,7 @@
    - {{ 'cancel' }} + {{ 'app.shared.buttons.cancel' }}
    @@ -45,19 +45,19 @@ ng-model="user.organization" ng-change="toggleOrganization()" value="false"/> - +
    - + '"> - + '"> diff --git a/app/assets/templates/admin/plans/_form.html.erb b/app/assets/templates/admin/plans/_form.html.erb index c555aefac..a1165c296 100644 --- a/app/assets/templates/admin/plans/_form.html.erb +++ b/app/assets/templates/admin/plans/_form.html.erb @@ -1,19 +1,19 @@ -

    {{ 'plan_form.general_information' }}

    +

    {{ 'app.shared.plan.general_information' }}

    - + - {{ 'plan_form.name_is_required' }} - {{ 'plan_form.name_length_must_be_less_than_24_characters' }} + {{ 'app.shared.plan.name_is_required' }} + {{ 'app.shared.plan.name_length_must_be_less_than_24_characters' }}
    - + - {{ 'plan_form.type_is_required' }} + {{ 'app.shared.plan.type_is_required' }}
    - + - {{ 'plan_form.group_is_required' }} + {{ 'app.shared.plan.group_is_required' }}
    - + - {{ 'plan_form.period_is_required' }} + {{ 'app.shared.plan.period_is_required' }}
    - + - {{ 'plan_form.number_of_periods_is_required' }} + {{ 'app.shared.plan.number_of_periods_is_required' }}
    - +
    {{currencySymbol}}
    - {{ 'plan_form.price_is_required' }} + {{ 'app.shared.plan.price_is_required' }}
    - + - {{ 'plan_form.on_the_subscriptions_page_the_most_prominent_subscriptions_will_be_placed_at_the_top_of_the_list' | translate }} - {{ 'plan_form.an_evelated_number_means_a_higher_prominence' | translate }} + {{ 'app.shared.plan.on_the_subscriptions_page_the_most_prominent_subscriptions_will_be_placed_at_the_top_of_the_list' | translate }} + {{ 'app.shared.plan.an_evelated_number_means_a_higher_prominence' | translate }}
    - + {{ (plan.is_rolling ? 'yes' : 'no') | translate }} - {{ 'plan_form.a_rolling_subscription_will_begin_the_day_of_the_first_training' | translate }} - {{ 'plan_form.otherwise_it_will_begin_as_soon_as_it_is_bought' | translate }} + {{ 'app.shared.plan.a_rolling_subscription_will_begin_the_day_of_the_first_training' | translate }} + {{ 'app.shared.plan.otherwise_it_will_begin_as_soon_as_it_is_bought' | translate }}
    @@ -122,12 +122,12 @@ - +
    {{file.attachment || plan.plan_file_attributes.attachment_identifier}}
    - {{ 'plan_form.attach_an_information_sheet' }} + {{ 'app.shared.plan.attach_an_information_sheet' }} {{ 'change' }} @@ -136,7 +136,7 @@
    - +
    - +
    - {{ 'plan_form.as_part_of_a_partner_subscription_some_notifications_may_be_sent_to_this_user' }} + {{ 'app.shared.plan.as_part_of_a_partner_subscription_some_notifications_may_be_sent_to_this_user' }}
    diff --git a/app/assets/templates/admin/plans/edit.html.erb b/app/assets/templates/admin/plans/edit.html.erb index 509b4c626..3d27d64c6 100644 --- a/app/assets/templates/admin/plans/edit.html.erb +++ b/app/assets/templates/admin/plans/edit.html.erb @@ -7,13 +7,13 @@
    -

    {{ 'edit_plan.subscription_plan' | translate }} {{ plan.base_name }}

    +

    {{ 'app.admin.plans.edit.subscription_plan' | translate }} {{ plan.base_name }}

    @@ -28,7 +28,7 @@
    - + '">
    @@ -37,8 +37,8 @@ id="plan[disabled]" type="checkbox" class="form-control" - switch-on-text="{{ 'yes' | translate }}" - switch-off-text="{{ 'no' | translate }}" + switch-on-text="{{ 'app.shared.buttons.yes' | translate }}" + switch-off-text="{{ 'app.shared.buttons.no' | translate }}" switch-animate="true" ng-true-value="'true'" ng-false-value="'false'"/> @@ -46,19 +46,19 @@ {{ 'plan_form.disable_plan_will_not_unsubscribe_users' }}
    -

    {{ 'edit_plan.prices' }}

    +

    {{ 'app.admin.plans.edit.prices' }}

    - +
    -

    {{ 'edit_plan.machines' }}

    +

    {{ 'app.admin.plans.edit.machines' }}

    {{ 'members_import.tag_name' }}{{ 'members_import.tag_identifier' }}{{ 'app.admin.members_import.tag_name' }}{{ 'app.admin.members_import.tag_identifier' }}
    - - + + @@ -75,11 +75,11 @@
    {{ 'edit_plan.machine' }}{{ 'edit_plan.hourly_rate' }}{{ 'app.admin.plans.edit.machine' }}{{ 'app.admin.plans.edit.hourly_rate' }}
    -

    {{ 'edit_plan.spaces' }}

    +

    {{ 'app.admin.plans.edit.spaces' }}

    - - + + @@ -97,7 +97,7 @@
    {{ 'edit_plan.space' }}{{ 'edit_plan.hourly_rate' }}{{ 'app.admin.plans.edit.space' }}{{ 'app.admin.plans.edit.hourly_rate' }}
    diff --git a/app/assets/templates/admin/plans/new.html.erb b/app/assets/templates/admin/plans/new.html.erb index d1acc9cc6..12ad2d12f 100644 --- a/app/assets/templates/admin/plans/new.html.erb +++ b/app/assets/templates/admin/plans/new.html.erb @@ -7,7 +7,7 @@
    -

    {{ 'new_plan.add_a_subscription_plan' }}

    +

    {{ 'app.admin.plans.new.add_a_subscription_plan' }}

    @@ -20,10 +20,10 @@
    - + '">
    diff --git a/app/assets/templates/admin/pricing/coupons.html.erb b/app/assets/templates/admin/pricing/coupons.html.erb index 556a00cbb..b1118e4cb 100644 --- a/app/assets/templates/admin/pricing/coupons.html.erb +++ b/app/assets/templates/admin/pricing/coupons.html.erb @@ -1,15 +1,15 @@ -

    {{ 'pricing.list_of_the_coupons' }}

    +

    {{ 'app.admin.pricing.list_of_the_coupons' }}

    @@ -18,10 +18,10 @@ - - - - + + + + @@ -33,7 +33,7 @@ {{coupon.amount_off}} {{currencySymbol}} - +
    {{ 'pricing.name' }}{{ 'pricing.discount' }}{{ 'pricing.nb_of_usages' }}{{ 'pricing.status' }}{{ 'app.admin.pricing.name' }}{{ 'app.admin.pricing.discount' }}{{ 'app.admin.pricing.nb_of_usages' }}{{ 'app.admin.pricing.status' }}
    {{coupon.usages}}{{'pricing.'+coupon.status}}{{'app.admin.pricing.'+coupon.status}} @@ -44,5 +44,5 @@
    - +
    diff --git a/app/assets/templates/admin/pricing/credits.html.erb b/app/assets/templates/admin/pricing/credits.html.erb index 0dfd69f79..0832e9a7a 100644 --- a/app/assets/templates/admin/pricing/credits.html.erb +++ b/app/assets/templates/admin/pricing/credits.html.erb @@ -1,10 +1,10 @@ -

    {{ 'pricing.trainings' }}

    +

    {{ 'app.admin.pricing.trainings' }}

    - - - + + + @@ -35,7 +35,7 @@
    @@ -43,17 +43,17 @@
    {{ 'pricing.subscription' }}{{ 'pricing.credits' }}{{ 'pricing.related_trainings' }}{{ 'app.admin.pricing.subscription' }}{{ 'app.admin.pricing.credits' }}{{ 'app.admin.pricing.related_trainings' }}
    -

    {{ 'pricing.machines' }}

    +

    {{ 'app.admin.pricing.machines' }}

    - +
    - - - + + + @@ -85,10 +85,10 @@
    @@ -96,16 +96,16 @@
    {{ 'pricing.machine' }}{{ 'pricing.hours' | translate:{DURATION:slotDuration} }}{{ 'pricing.related_subscriptions' }}{{ 'app.admin.pricing.machine' }}{{ 'app.admin.pricing.hours' | translate:{DURATION:slotDuration} }}{{ 'app.admin.pricing.related_subscriptions' }}
    -

    {{ 'pricing.spaces' }}

    +

    {{ 'app.admin.pricing.spaces' }}

    - +
    - - - + + + @@ -137,10 +137,10 @@
    diff --git a/app/assets/templates/admin/pricing/index.html.erb b/app/assets/templates/admin/pricing/index.html.erb index c1aff1cb7..2a6b1b10c 100644 --- a/app/assets/templates/admin/pricing/index.html.erb +++ b/app/assets/templates/admin/pricing/index.html.erb @@ -7,7 +7,7 @@
    -

    {{ 'pricing.pricing_management' }}

    +

    {{ 'app.admin.pricing.pricing_management' }}

    @@ -21,28 +21,28 @@
    - - + + '"> - - + + '"> - - + + '"> - - + + '"> - - + + '"> - - + + '">
    diff --git a/app/assets/templates/admin/pricing/machine_hours.html.erb b/app/assets/templates/admin/pricing/machine_hours.html.erb index 3823e42cc..d8ef5780c 100644 --- a/app/assets/templates/admin/pricing/machine_hours.html.erb +++ b/app/assets/templates/admin/pricing/machine_hours.html.erb @@ -1,10 +1,10 @@
    - {{ 'pricing.these_prices_match_machine_hours_rates_' | translate:{DURATION:slotDuration} }} {{ 'pricing._without_subscriptions' }}. + {{ 'app.admin.pricing.these_prices_match_machine_hours_rates_' | translate:{DURATION:slotDuration} }} {{ 'app.admin.pricing._without_subscriptions' }}.
    {{ 'pricing.space' }}{{ 'pricing.hours' | translate:{DURATION:slotDuration} }}{{ 'pricing.related_subscriptions' }}{{ 'app.admin.pricing.space' }}{{ 'app.admin.pricing.hours' | translate:{DURATION:slotDuration} }}{{ 'app.admin.pricing.related_subscriptions' }}
    - + diff --git a/app/assets/templates/admin/pricing/sendCoupon.html.erb b/app/assets/templates/admin/pricing/sendCoupon.html.erb index fad186cd7..83e63e536 100644 --- a/app/assets/templates/admin/pricing/sendCoupon.html.erb +++ b/app/assets/templates/admin/pricing/sendCoupon.html.erb @@ -1,11 +1,11 @@
    {{ 'pricing.machines' }}{{ 'app.admin.pricing.machines' }} {{group.name}}
    @@ -13,18 +13,18 @@ - - - - - - + + + + + +
    {{'pricing.code'}} {{coupon.code}}
    {{'pricing.discount'}} {{coupon.percent_off}} %{{coupon.amount_off}} {{currencySymbol}}
    {{'pricing.validity_per_user'}} {{'pricing.'+coupon.validity_per_user}}
    {{'pricing.valid_until'}} {{coupon.valid_until | amDateFormat:'L'}}
    {{'pricing.usages'}} {{coupon.usages}} / {{coupon.max_usages | maxCount}}
    {{'pricing.enabled'}} {{coupon.active | booleanFormat}}
    {{'app.admin.pricing.code'}} {{coupon.code}}
    {{'app.admin.pricing.discount'}} {{coupon.percent_off}} %{{coupon.amount_off}} {{currencySymbol}}
    {{'app.admin.pricing.validity_per_user'}} {{'app.admin.pricing.'+coupon.validity_per_user}}
    {{'app.admin.pricing.valid_until'}} {{coupon.valid_until | amDateFormat:'L'}}
    {{'app.admin.pricing.usages'}} {{coupon.usages}} / {{coupon.max_usages | maxCount}}
    {{'app.admin.pricing.enabled'}} {{coupon.active | booleanFormat}}
    \ No newline at end of file + + +
    diff --git a/app/assets/templates/admin/pricing/spaces.html.erb b/app/assets/templates/admin/pricing/spaces.html.erb index 1395f3f83..03675b2e3 100644 --- a/app/assets/templates/admin/pricing/spaces.html.erb +++ b/app/assets/templates/admin/pricing/spaces.html.erb @@ -1,10 +1,10 @@
    - {{ 'pricing.these_prices_match_space_hours_rates_' | translate:{DURATION:slotDuration} }} {{ 'pricing._without_subscriptions' }}. + {{ 'app.admin.pricing.these_prices_match_space_hours_rates_' | translate:{DURATION:slotDuration} }} {{ 'app.admin.pricing._without_subscriptions' }}.
    - + diff --git a/app/assets/templates/admin/pricing/subscriptions.html.erb b/app/assets/templates/admin/pricing/subscriptions.html.erb index bcb98f4bc..8cef00a25 100644 --- a/app/assets/templates/admin/pricing/subscriptions.html.erb +++ b/app/assets/templates/admin/pricing/subscriptions.html.erb @@ -1,21 +1,21 @@ -

    {{ 'pricing.list_of_the_subscription_plans' }}

    +

    {{ 'app.admin.pricing.list_of_the_subscription_plans' }}

    - {{ 'pricing.beware_the_subscriptions_are_disabled_on_this_application' | translate }} - {{ 'pricing.you_can_create_some_but_they_wont_be_available_until_the_project_is_redeployed_by_the_server_manager' | translate }} -
    {{ 'pricing.for_safety_reasons_please_dont_create_subscriptions_if_you_dont_want_intend_to_use_them_later' | translate }} + {{ 'app.admin.pricing.beware_the_subscriptions_are_disabled_on_this_application' | translate }} + {{ 'app.admin.pricing.you_can_create_some_but_they_wont_be_available_until_the_project_is_redeployed_by_the_server_manager' | translate }} +
    {{ 'app.admin.pricing.for_safety_reasons_please_dont_create_subscriptions_if_you_dont_want_intend_to_use_them_later' | translate }}
    @@ -24,12 +24,12 @@
    {{ 'pricing.spaces' }}{{ 'app.admin.pricing.spaces' }} {{group.name}}
    - - - - - - + + + + + + diff --git a/app/assets/templates/admin/pricing/trainings.html.erb b/app/assets/templates/admin/pricing/trainings.html.erb index 2dc0544b6..6c0bde16f 100644 --- a/app/assets/templates/admin/pricing/trainings.html.erb +++ b/app/assets/templates/admin/pricing/trainings.html.erb @@ -1,7 +1,7 @@
    {{ 'pricing.type' | translate }} {{ 'pricing.name' | translate }} {{ 'pricing.duration' | translate }} {{ 'pricing.group' | translate }} {{ 'pricing.price' | translate }} {{ 'app.admin.pricing.type' | translate }} {{ 'app.admin.pricing.name' | translate }} {{ 'app.admin.pricing.duration' | translate }} {{ 'app.admin.pricing.group' | translate }} {{ 'app.admin.pricing.price' | translate }}
    - + @@ -21,4 +21,4 @@ -
    {{ 'pricing.trainings' }}{{ 'app.admin.pricing.trainings' }} {{group.name}}
    \ No newline at end of file + diff --git a/app/assets/templates/admin/subscriptions/create_modal.html.erb b/app/assets/templates/admin/subscriptions/create_modal.html.erb index 7ff44ecdc..3554747eb 100644 --- a/app/assets/templates/admin/subscriptions/create_modal.html.erb +++ b/app/assets/templates/admin/subscriptions/create_modal.html.erb @@ -1,10 +1,10 @@ diff --git a/app/assets/templates/admin/tags/index.html.erb b/app/assets/templates/admin/tags/index.html.erb index dd734e01a..ece11564c 100644 --- a/app/assets/templates/admin/tags/index.html.erb +++ b/app/assets/templates/admin/tags/index.html.erb @@ -1,8 +1,8 @@ - + - + @@ -25,7 +25,7 @@
    - + +
    diff --git a/app/assets/templates/admin/events/prices.html.erb b/app/assets/templates/admin/events/prices.html.erb index fcca1229b..bcf00aca2 100644 --- a/app/assets/templates/admin/events/prices.html.erb +++ b/app/assets/templates/admin/events/prices.html.erb @@ -1,12 +1,12 @@
    -

    {{ 'prices_categories' }}

    +

    {{ 'app.admin.events.prices_categories' }}

    - +
    {{ 'tag_name' }}{{ 'app.admin.members.tag_form.tag_name' }}
    - - + + @@ -17,7 +17,7 @@
    {{ 'name' }}{{ 'usages_count' }}{{ 'app.admin.events.name' }}{{ 'app.admin.events.usages_count' }}
    -
    \ No newline at end of file + diff --git a/app/assets/templates/admin/events/reservations.html.erb b/app/assets/templates/admin/events/reservations.html.erb index 954e23d11..105cd7c72 100644 --- a/app/assets/templates/admin/events/reservations.html.erb +++ b/app/assets/templates/admin/events/reservations.html.erb @@ -7,7 +7,7 @@
    -

    {{ 'the_reservations' | translate }} {{event.title}}

    +

    {{ 'app.admin.event_reservations.the_reservations' | translate }} {{event.title}}

    @@ -20,9 +20,9 @@ - - - + + + @@ -33,23 +33,23 @@
    {{ 'user' }}{{ 'payment_date' }}{{ 'reserved_tickets' }}{{ 'app.admin.event_reservations.user' }}{{ 'app.admin.event_reservations.payment_date' }}{{ 'app.admin.event_reservations.reserved_tickets' }}
    {{ reservation.created_at | amDateFormat:'LL LTS' }} - {{ 'full_price_' | translate }} {{reservation.nb_reserve_places}}
    + {{ 'app.admin.event_reservations.full_price_' | translate }} {{reservation.nb_reserve_places}}
    {{ticket.event_price_category.price_category.name}} : {{ticket.booked}} -
    {{ 'canceled' }}
    +
    {{ 'app.admin.event_reservations.canceled' }}
    -

    {{ 'no_reservations_for_now' }}

    +

    {{ 'app.admin.event_reservations.no_reservations_for_now' }}

    - + diff --git a/app/assets/templates/admin/open_api_clients/index.html.erb b/app/assets/templates/admin/open_api_clients/index.html.erb index 8f4e3bf70..4866d812d 100644 --- a/app/assets/templates/admin/open_api_clients/index.html.erb +++ b/app/assets/templates/admin/open_api_clients/index.html.erb @@ -7,7 +7,7 @@
    -

    {{ 'open_api_clients' }}

    +

    {{ 'app.admin.open_api_clients.open_api_clients' }}

    @@ -15,7 +15,7 @@
      - {{ 'api_documentation' }}  + {{ 'app.admin.open_api_clients.api_documentation' }} 
    @@ -28,27 +28,27 @@
    - +
    - +
    - - + +
    - + - + - + - + @@ -62,11 +62,11 @@
    {{ 'name' | translate }} {{ 'app.admin.open_api_clients.name' | translate }} {{ 'calls_count' | translate }} {{ 'app.admin.open_api_clients.calls_count' | translate }} {{ 'token' | translate }}{{ 'app.admin.open_api_clients.token' | translate }}{{ 'created_at' | translate }} {{ 'app.admin.open_api_clients.created_at' | translate }}
    {{ 'app.shared.plan.attach_an_information_sheet' }} - {{ 'change' }}{{ 'app.shared.buttons.change' }} diff --git a/app/assets/templates/admin/project_elements/index.html.erb b/app/assets/templates/admin/project_elements/index.html.erb index 4b9ce21e5..a18d94310 100644 --- a/app/assets/templates/admin/project_elements/index.html.erb +++ b/app/assets/templates/admin/project_elements/index.html.erb @@ -7,12 +7,12 @@
    -

    {{ 'project_elements.projects_elements_management' }}

    +

    {{ 'app.admin.project_elements.projects_elements_management' }}

    @@ -24,17 +24,17 @@
    - - + + '"> - - + + '"> - - + + '">
    - \ No newline at end of file + diff --git a/app/assets/templates/admin/project_elements/licences.html.erb b/app/assets/templates/admin/project_elements/licences.html.erb index ac0352d92..afa0f94cf 100644 --- a/app/assets/templates/admin/project_elements/licences.html.erb +++ b/app/assets/templates/admin/project_elements/licences.html.erb @@ -1,10 +1,10 @@ - + - - + + @@ -32,7 +32,7 @@
    {{ 'name' }}{{ 'app.admin.project_elements.name' }}
    \ No newline at end of file +
    diff --git a/app/assets/templates/admin/project_elements/materials.html.erb b/app/assets/templates/admin/project_elements/materials.html.erb index 6ea552964..fb96dc881 100644 --- a/app/assets/templates/admin/project_elements/materials.html.erb +++ b/app/assets/templates/admin/project_elements/materials.html.erb @@ -1,9 +1,9 @@ - + - + @@ -26,7 +26,7 @@
    {{ 'name' }}{{ 'app.admin.project_elements.name' }}
    \ No newline at end of file + diff --git a/app/assets/templates/admin/project_elements/themes.html.erb b/app/assets/templates/admin/project_elements/themes.html.erb index 0650bb470..0583accb1 100644 --- a/app/assets/templates/admin/project_elements/themes.html.erb +++ b/app/assets/templates/admin/project_elements/themes.html.erb @@ -1,9 +1,9 @@ - + - + @@ -26,7 +26,7 @@
    {{ 'name' }}{{ 'app.admin.project_elements.name' }}
    \ No newline at end of file + diff --git a/app/assets/templates/admin/settings/about.html b/app/assets/templates/admin/settings/about.html index c58a5c440..b190fa9ff 100644 --- a/app/assets/templates/admin/settings/about.html +++ b/app/assets/templates/admin/settings/about.html @@ -3,34 +3,34 @@
    -

    - {{ 'settings.shift_enter_to_force_carriage_return' | translate }} - +

    + {{ 'app.admin.settings.shift_enter_to_force_carriage_return' | translate }} +
    -
    - {{ 'settings.drag_and_drop_to_insert_images' | translate }} - + {{ 'app.admin.settings.drag_and_drop_to_insert_images' | translate }} +
    -
    - {{ 'settings.shift_enter_to_force_carriage_return' | translate }} - + {{ 'app.admin.settings.shift_enter_to_force_carriage_return' | translate }} +
    -
    \ No newline at end of file + diff --git a/app/assets/templates/admin/settings/general.html b/app/assets/templates/admin/settings/general.html index f4f9c0514..7fdc8b997 100644 --- a/app/assets/templates/admin/settings/general.html +++ b/app/assets/templates/admin/settings/general.html @@ -1,42 +1,42 @@
    - {{ 'settings.title' }} + {{ 'app.admin.settings.title' }}
    - +
    - +
    - +
    -

    {{ 'settings.title_concordance' }}

    +

    {{ 'app.admin.settings.title_concordance' }}



    - +
    @@ -45,62 +45,62 @@
    - {{ 'settings.customize_information_messages' }} + {{ 'app.admin.settings.customize_information_messages' }}
    -

    {{ 'settings.message_of_the_machine_booking_page' }}

    +

    {{ 'app.admin.settings.message_of_the_machine_booking_page' }}

    - +
    -

    {{ 'settings.warning_message_of_the_training_booking_page'}}

    +

    {{ 'app.admin.settings.warning_message_of_the_training_booking_page'}}

    - +
    -

    {{ 'settings.information_message_of_the_training_reservation_page'}}

    +

    {{ 'app.admin.settings.information_message_of_the_training_reservation_page'}}

    - +
    -

    {{ 'settings.message_of_the_subscriptions_page' }}

    +

    {{ 'app.admin.settings.message_of_the_subscriptions_page' }}

    - +
    -

    {{ 'settings.message_of_the_events_page' }}

    +

    {{ 'app.admin.settings.message_of_the_events_page' }}

    - +
    -

    {{ 'settings.message_of_the_spaces_page' }}

    +

    {{ 'app.admin.settings.message_of_the_spaces_page' }}

    - +
    @@ -108,17 +108,17 @@
    - {{ 'settings.legal_documents'}} + {{ 'app.admin.settings.legal_documents'}}
    - {{ 'settings.if_these_documents_are_not_filled_no_consent_about_them_will_be_asked_to_the_user' }} + {{ 'app.admin.settings.if_these_documents_are_not_filled_no_consent_about_them_will_be_asked_to_the_user' }}
    - +
    @@ -135,14 +135,14 @@
    - +
    - +
    @@ -159,7 +159,7 @@
    - +
    @@ -167,21 +167,21 @@
    - {{ 'settings.customize_the_graphics' }} + {{ 'app.admin.settings.customize_the_graphics' }}
    - {{ 'settings.for_an_optimal_rendering_the_logo_image_must_be_at_the_PNG_format_with_a_transparent_background_and_with_an_aspect_ratio_3.5_times_wider_than_the_height' }}
    - {{ 'settings.concerning_the_favicon_it_must_be_at_ICO_format_with_a_size_of_16x16_pixels' }}
    + {{ 'app.admin.settings.for_an_optimal_rendering_the_logo_image_must_be_at_the_PNG_format_with_a_transparent_background_and_with_an_aspect_ratio_3.5_times_wider_than_the_height' }}
    + {{ 'app.admin.settings.concerning_the_favicon_it_must_be_at_ICO_format_with_a_size_of_16x16_pixels' }}

    - {{ 'settings.remember_to_refresh_the_page_for_the_changes_to_take_effect' }} + {{ 'app.admin.settings.remember_to_refresh_the_page_for_the_changes_to_take_effect' }}
    -

    {{ 'settings.logo_white_background' }}

    +

    {{ 'app.admin.settings.logo_white_background' }}

    @@ -34,10 +34,10 @@ name="first_name" ng-model="user.profile_attributes.first_name" class="form-control" - placeholder="{{ 'your_first_name' | translate }}" + placeholder="{{ 'app.public.common.your_first_name' | translate }}" required> - {{ 'first_name_is_required' }} + {{ 'app.public.common.first_name_is_required' }}
    @@ -45,10 +45,10 @@ name="last_name" ng-model="user.profile_attributes.last_name" class="form-control" - placeholder="{{ 'your_surname' | translate }}" + placeholder="{{ 'app.public.common.your_surname' | translate }}" required> - {{ 'surname_is_required' }} + {{ 'app.public.common.surname_is_required' }}
    @@ -60,11 +60,11 @@ name="username" ng-model="user.username" class="form-control" - placeholder="{{ 'your_pseudonym' | translate }}" + placeholder="{{ 'app.public.common.your_pseudonym' | translate }}" required>
    - {{ 'pseudonym_is_required' }} + {{ 'app.public.common.pseudonym_is_required' }}
    @@ -76,11 +76,11 @@ name="email" ng-model="user.email" class="form-control" - placeholder="{{ 'your_email_address' | translate }}" + placeholder="{{ 'app.public.common.your_email_address' | translate }}" required>
    - - {{ 'email_is_required' }} + + {{ 'app.public.common.email_is_required' }}
    @@ -92,13 +92,13 @@ name="password" ng-model="user.password" class="form-control" - placeholder="{{ 'your_password' | translate }}" + placeholder="{{ 'app.public.common.your_password' | translate }}" required ng-minlength="8">
    - {{ 'password_is_required' }} - {{ 'password_is_too_short' }} + {{ 'app.public.common.password_is_required' }} + {{ 'app.public.common.password_is_too_short' }}
    @@ -110,13 +110,13 @@ name="password_confirmation" ng-model="user.password_confirmation" class="form-control" - placeholder="{{ 'type_your_password_again' | translate }}" + placeholder="{{ 'app.public.common.type_your_password_again' | translate }}" required ng-minlength="8" match="user.password"> - {{ 'password_confirmation_is_required' }} - {{ 'password_does_not_match_with_confirmation' }} + {{ 'app.public.common.password_confirmation_is_required' }} + {{ 'app.public.common.password_does_not_match_with_confirmation' }} @@ -127,7 +127,7 @@ id="organization" ng-model="user.organization" value="false"/> - + @@ -139,11 +139,11 @@ name="organization_name" ng-model="user.profile_attributes.organization_attributes.name" class="form-control" - placeholder="{{ 'name_of_your_organization' | translate }}" + placeholder="{{ 'app.public.common.name_of_your_organization' | translate }}" ng-required="user.organization"> - - {{ 'organization_name_is_required' }} + + {{ 'app.public.common.organization_name_is_required' }} @@ -155,11 +155,11 @@ name="organization_address" ng-model="user.profile_attributes.organization_attributes.address_attributes.address" class="form-control" - placeholder="{{ 'address_of_your_organization' | translate }}" + placeholder="{{ 'app.public.common.address_of_your_organization' | translate }}" ng-required="user.organization"> - - {{ 'organization_address_is_required' }} + + {{ 'app.public.common.organization_address_is_required' }} @@ -167,11 +167,11 @@
    - +
    - {{ 'user_s_profile_is_required' }} + {{ 'app.public.common.user_s_profile_is_required' }}
    @@ -186,12 +186,12 @@ uib-datepicker-popup="{{datePicker.format}}" datepicker-options="datePicker.options" is-open="datePicker.opened" - placeholder="{{ 'birth_date' | translate }}" + placeholder="{{ 'app.public.common.birth_date' | translate }}" ng-click="openDatePicker($event)" required/> - - {{ 'birth_date_is_required' }} + + {{ 'app.public.common.birth_date_is_required' }} @@ -203,13 +203,13 @@ type="text" name="phone" class="form-control" - placeholder="{{ 'phone_number' | translate }}" + placeholder="{{ 'app.public.common.phone_number' | translate }}" ng-required="phoneRequired"> - + - {{ 'phone_number_is_required' }} + {{ 'app.public.common.phone_number_is_required' }} @@ -220,7 +220,7 @@ id="is_allow_contact" ng-model="user.is_allow_contact" value="true"/> - + @@ -231,7 +231,7 @@ id="is_allow_newsletter" ng-model="user.is_allow_newsletter" value="true"/> - + @@ -244,8 +244,8 @@ value="true" ng-required="cgu != null"/> @@ -257,7 +257,7 @@ - {{ 'field_required' }} + {{ 'app.public.common.field_required' }}
    @@ -266,5 +266,5 @@
    diff --git a/app/assets/templates/shared/valid_reservation_modal.html.erb b/app/assets/templates/shared/valid_reservation_modal.html.erb index 510b44029..b74fb1ce1 100644 --- a/app/assets/templates/shared/valid_reservation_modal.html.erb +++ b/app/assets/templates/shared/valid_reservation_modal.html.erb @@ -1,17 +1,17 @@ diff --git a/app/assets/templates/stripe/payment_modal.html.erb b/app/assets/templates/stripe/payment_modal.html.erb index b6f76e509..7f6f70be8 100644 --- a/app/assets/templates/stripe/payment_modal.html.erb +++ b/app/assets/templates/stripe/payment_modal.html.erb @@ -1,7 +1,7 @@