From 36d7c02776073306ab077d76968b2013154fab5a Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Wed, 12 Apr 2023 15:51:20 +0200
Subject: [PATCH 01/45] Version 6.0.3
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 070f89f6d..3b042d3a8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "fab-manager",
- "version": "6.0.2",
+ "version": "6.0.3",
"description": "Fab-manager 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 b4250b2ce61bcc04294d15d939f0aa4c2073981c Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Mon, 17 Apr 2023 11:29:36 +0200
Subject: [PATCH 02/45] (bug) abus notification error
---
CHANGELOG.md | 2 ++
app/models/project.rb | 2 ++
2 files changed, 4 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 652ceb0f4..5458cd199 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,7 @@
# Changelog Fab-manager
+- Fix a bug: notification is broken when delete a project
+
## v6.0.3 2023 April 12
- Fix a bug: unable to install Fab-manager by setup.sh
diff --git a/app/models/project.rb b/app/models/project.rb
index 860e2f5ad..238353e96 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -40,6 +40,8 @@ class Project < ApplicationRecord
has_many :project_steps, dependent: :destroy
accepts_nested_attributes_for :project_steps, allow_destroy: true
+ has_many :abuses, as: :signaled, dependent: :destroy, class_name: 'Abuse'
+
# validations
validates :author, :name, presence: true
From acc081a41396cceed4c3778653a9848cb03c4189 Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Mon, 17 Apr 2023 12:20:36 +0200
Subject: [PATCH 03/45] (bug) logout error in payment_schedules page
---
.../payment-schedule/payment-schedule-item-actions.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/frontend/src/javascript/components/payment-schedule/payment-schedule-item-actions.tsx b/app/frontend/src/javascript/components/payment-schedule/payment-schedule-item-actions.tsx
index caf059353..7a7e8f307 100644
--- a/app/frontend/src/javascript/components/payment-schedule/payment-schedule-item-actions.tsx
+++ b/app/frontend/src/javascript/components/payment-schedule/payment-schedule-item-actions.tsx
@@ -54,7 +54,7 @@ export const PaymentScheduleItemActions: React.FC {
- return (operator.role === 'admin' || operator.role === 'manager');
+ return (operator?.role === 'admin' || operator?.role === 'manager');
};
/**
From 4c126a63d521a48a3dabc63c3a9e93757bae8010 Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Mon, 17 Apr 2023 12:32:01 +0200
Subject: [PATCH 04/45] (bug) add a task for clean abuse notifications
---
CHANGELOG.md | 1 +
lib/tasks/fablab/maintenance.rake | 7 +++++++
2 files changed, 8 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5458cd199..4b0ef55ef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
# Changelog Fab-manager
- Fix a bug: notification is broken when delete a project
+- [TODO DEPLOY] `rails fablab:maintenance:clean_abuse_notifications`
## v6.0.3 2023 April 12
diff --git a/lib/tasks/fablab/maintenance.rake b/lib/tasks/fablab/maintenance.rake
index 23a6dc4f7..3402555fa 100644
--- a/lib/tasks/fablab/maintenance.rake
+++ b/lib/tasks/fablab/maintenance.rake
@@ -155,5 +155,12 @@ namespace :fablab do
end_date = args.end == 'today' ? Time.current.end_of_day : start_date.next_month
[start_date, end_date]
end
+
+ desc 'Clean the abuse notifications if signaled object is null'
+ task clean_abuse_notifications: :environment do
+ Abuse.all.each do |abuse|
+ abuse.destroy if abuse.signaled.nil?
+ end
+ end
end
end
From e39c5638b80bf9b3efb7ec5c8316ec07e698a822 Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Tue, 18 Apr 2023 10:07:30 +0200
Subject: [PATCH 05/45] (quality) payzen order status
---
app/controllers/api/payzen_controller.rb | 5 +++--
.../src/javascript/components/payment/payzen/payzen-form.tsx | 2 +-
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/app/controllers/api/payzen_controller.rb b/app/controllers/api/payzen_controller.rb
index 14eaf310b..3583ac12a 100644
--- a/app/controllers/api/payzen_controller.rb
+++ b/app/controllers/api/payzen_controller.rb
@@ -72,7 +72,7 @@ class API::PayzenController < API::PaymentsController
cart = shopping_cart
- if order['answer']['transactions'].any? { |transaction| transaction['status'] == 'PAID' }
+ if order['answer']['transactions'].all? { |transaction| transaction['status'] == 'PAID' }
render on_payment_success(params[:order_id], cart)
else
render json: order['answer'], status: :unprocessable_entity
@@ -86,10 +86,11 @@ class API::PayzenController < API::PaymentsController
client = PayZen::Transaction.new
transaction = client.get(params[:transaction_uuid])
+ order = PayZen::Order.new.get(params[:order_id])
cart = shopping_cart
- if transaction['answer']['status'] == 'PAID'
+ if transaction['answer']['status'] == 'PAID' && order['answer']['transactions'].all? { |t| t['status'] == 'PAID' }
render on_payment_success(params[:order_id], cart)
else
render json: transaction['answer'], status: :unprocessable_entity
diff --git a/app/frontend/src/javascript/components/payment/payzen/payzen-form.tsx b/app/frontend/src/javascript/components/payment/payzen/payzen-form.tsx
index 541880808..7f7a6223b 100644
--- a/app/frontend/src/javascript/components/payment/payzen/payzen-form.tsx
+++ b/app/frontend/src/javascript/components/payment/payzen/payzen-form.tsx
@@ -75,7 +75,7 @@ export const PayzenForm: React.FC = ({ onSubmit, onSuccess, onE
if (updateCard) return onSuccess(null);
const transaction = event.clientAnswer.transactions[0];
- if (event.clientAnswer.orderStatus === 'PAID') {
+ if (event.clientAnswer.orderStatus === 'PAID' && transaction?.status === 'PAID') {
confirmPayment(event, transaction).then((confirmation) => {
PayZenKR.current.removeForms().then(() => {
onSuccess(confirmation);
From 112df59c96254d55116359ef43c034854b065978 Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Thu, 20 Apr 2023 18:57:37 +0200
Subject: [PATCH 06/45] (bug) date shift in event creation/update
---
app/services/event_service.rb | 4 ++--
test/integration/events/timezone_test.rb | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/app/services/event_service.rb b/app/services/event_service.rb
index 639353313..7a6adcaf6 100644
--- a/app/services/event_service.rb
+++ b/app/services/event_service.rb
@@ -33,8 +33,8 @@ class EventService
end
def date_range(starting, ending, all_day)
- start_date = Time.zone.parse(starting[:date])
- end_date = Time.zone.parse(ending[:date])
+ start_date = Date.parse(starting[:date])
+ end_date = Date.parse(ending[:date])
start_time = starting[:time] ? Time.zone.parse(starting[:time]) : nil
end_time = ending[:time] ? Time.zone.parse(ending[:time]) : nil
if all_day || start_time.nil? || end_time.nil?
diff --git a/test/integration/events/timezone_test.rb b/test/integration/events/timezone_test.rb
index 2c2489e41..4f8db9e4a 100644
--- a/test/integration/events/timezone_test.rb
+++ b/test/integration/events/timezone_test.rb
@@ -46,8 +46,8 @@ class Events::TimezoneTest < ActionDispatch::IntegrationTest
e = Event.find_by(id: event[:id])
assert_not_nil e, 'Event was not created in database'
- assert_equal '2023-06-15', e.availability.start_at.to_date.iso8601
- assert_equal '2023-06-15', e.availability.end_at.to_date.iso8601
+ assert_equal '2023-06-14', e.availability.start_at.to_date.iso8601
+ assert_equal '2023-06-14', e.availability.end_at.to_date.iso8601
assert_equal '09:48', e.availability.start_at.strftime('%R')
assert_equal '11:48', e.availability.end_at.strftime('%R')
end
From 57683125a89693f4fc1ccb4689881c100a3424be Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Fri, 21 Apr 2023 19:28:33 +0200
Subject: [PATCH 07/45] (bug) window end time < window start time
---
app/frontend/src/javascript/controllers/admin/settings.js | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/app/frontend/src/javascript/controllers/admin/settings.js b/app/frontend/src/javascript/controllers/admin/settings.js
index 4e07dcc30..e43bca1dc 100644
--- a/app/frontend/src/javascript/controllers/admin/settings.js
+++ b/app/frontend/src/javascript/controllers/admin/settings.js
@@ -487,8 +487,12 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
// we prevent the admin from setting the closing time before the opening time
$scope.$watch('windowEnd.value', function (newValue, oldValue, scope) {
- if ($scope.windowStart && moment($scope.windowStart.value).isAfter(newValue)) {
- return $scope.windowEnd.value = oldValue;
+ if (scope.windowStart) {
+ const startTime = moment($scope.windowStart.value).format('HH:mm:ss');
+ const endTime = moment(newValue).format('HH:mm:ss');
+ if (startTime >= endTime) {
+ scope.windowEnd.value = oldValue;
+ }
}
});
From b588f1c78077a07b2de607cf33696f0bc5dc5b41 Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Mon, 24 Apr 2023 19:17:07 +0200
Subject: [PATCH 08/45] (bug) broken notifications email
---
CHANGELOG.md | 1 +
.../notify_admin_low_stock_threshold.html.erb | 4 ++--
.../notify_admin_training_auto_cancelled.html.erb | 2 +-
.../notify_admin_when_user_is_created.html.erb | 2 +-
.../notify_admin_when_user_is_imported.html.erb | 2 +-
.../notify_member_training_authorization_expired.html.erb | 2 +-
.../notify_member_training_auto_cancelled.html.erb | 2 +-
.../notify_member_training_invalidated.html.erb | 2 +-
8 files changed, 9 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4b0ef55ef..dd5626219 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
# Changelog Fab-manager
- Fix a bug: notification is broken when delete a project
+- Fix a bug: broken notifications email
- [TODO DEPLOY] `rails fablab:maintenance:clean_abuse_notifications`
## v6.0.3 2023 April 12
diff --git a/app/views/notifications_mailer/notify_admin_low_stock_threshold.html.erb b/app/views/notifications_mailer/notify_admin_low_stock_threshold.html.erb
index 0c1bae992..a5dff9aa3 100644
--- a/app/views/notifications_mailer/notify_admin_low_stock_threshold.html.erb
+++ b/app/views/notifications_mailer/notify_admin_low_stock_threshold.html.erb
@@ -1,10 +1,10 @@
<%= render 'notifications_mailer/shared/hello', recipient: @recipient %>
- <%= t('.body.low_stock', { PRODUCT: @attached_object.name }) %>
+ <%= t('.body.low_stock', **{ PRODUCT: @attached_object.name }) %>
- <%= t('.body.stocks_state_html', { INTERNAL: @attached_object.stock['internal'], EXTERNAL: @attached_object.stock['external'] }) %>
+<%= t('.body.stocks_state_html', **{ INTERNAL: @attached_object.stock['internal'], EXTERNAL: @attached_object.stock['external'] }) %>
<%=link_to( t('.body.manage_stock'), "#{root_url}#!/admin/store/products/#{@attached_object.id}/edit", target: "_blank" )%>
diff --git a/app/views/notifications_mailer/notify_admin_training_auto_cancelled.html.erb b/app/views/notifications_mailer/notify_admin_training_auto_cancelled.html.erb
index 8a62126d3..41b94d0ce 100644
--- a/app/views/notifications_mailer/notify_admin_training_auto_cancelled.html.erb
+++ b/app/views/notifications_mailer/notify_admin_training_auto_cancelled.html.erb
@@ -1,7 +1,7 @@
<%= render 'notifications_mailer/shared/hello', recipient: @recipient %>
- <%= t('.body.cancelled_training', {
+ <%= t('.body.cancelled_training', **{
TRAINING: @attached_object.trainings.first.name,
DATE: I18n.l(@attached_object.start_at.to_date),
START: I18n.l(@attached_object.start_at, format: :hour_minute),
diff --git a/app/views/notifications_mailer/notify_admin_when_user_is_created.html.erb b/app/views/notifications_mailer/notify_admin_when_user_is_created.html.erb
index f994e2be5..6412854e5 100644
--- a/app/views/notifications_mailer/notify_admin_when_user_is_created.html.erb
+++ b/app/views/notifications_mailer/notify_admin_when_user_is_created.html.erb
@@ -6,7 +6,7 @@
- <%= t('.body.user_of_group_html', { GROUP: @attached_object&.group&.name }) %>
+ <%= t('.body.user_of_group_html', GROUP: @attached_object&.group&.name) %>
<% if @attached_object.invoicing_profile.organization %>
diff --git a/app/views/notifications_mailer/notify_admin_when_user_is_imported.html.erb b/app/views/notifications_mailer/notify_admin_when_user_is_imported.html.erb
index eac0434bb..8fe92d867 100644
--- a/app/views/notifications_mailer/notify_admin_when_user_is_imported.html.erb
+++ b/app/views/notifications_mailer/notify_admin_when_user_is_imported.html.erb
@@ -2,7 +2,7 @@
<%= render 'notifications_mailer/shared/hello', recipient: @recipient %>
<%= t('.body.new_account_imported', ID: @attached_object.id, PROVIDER: provider.name) %>
- <%= t('.body.provider_uid', UID:@attached_object.uid) %>
+ <%= t('.body.provider_uid', UID: @attached_object.uid) %>
<% if provider.sso_fields.size > 1 %>
diff --git a/app/views/notifications_mailer/notify_member_training_authorization_expired.html.erb b/app/views/notifications_mailer/notify_member_training_authorization_expired.html.erb
index f4fc020ed..9522275a6 100644
--- a/app/views/notifications_mailer/notify_member_training_authorization_expired.html.erb
+++ b/app/views/notifications_mailer/notify_member_training_authorization_expired.html.erb
@@ -1,6 +1,6 @@
<%= render 'notifications_mailer/shared/hello', recipient: @recipient %>
-<%= t('.body.training_expired_html', {
+<%= t('.body.training_expired_html', **{
TRAINING: @attached_object.name,
MACHINES: @attached_object.machines.map(&:name).join(', '),
DATE: I18n.l((DateTime.current - @attached_object.authorization_period.months).to_date),
diff --git a/app/views/notifications_mailer/notify_member_training_auto_cancelled.html.erb b/app/views/notifications_mailer/notify_member_training_auto_cancelled.html.erb
index ca8144c00..e50d0a0c3 100644
--- a/app/views/notifications_mailer/notify_member_training_auto_cancelled.html.erb
+++ b/app/views/notifications_mailer/notify_member_training_auto_cancelled.html.erb
@@ -1,7 +1,7 @@
<%= render 'notifications_mailer/shared/hello', recipient: @recipient %>
- <%= t('.body.cancelled_training', {
+ <%= t('.body.cancelled_training', **{
TRAINING: @attached_object.reservation.reservable.name,
DATE: I18n.l(@attached_object.start_at.to_date),
START: I18n.l(@attached_object.start_at, format: :hour_minute),
diff --git a/app/views/notifications_mailer/notify_member_training_invalidated.html.erb b/app/views/notifications_mailer/notify_member_training_invalidated.html.erb
index fd6ae0e5f..0390c504a 100644
--- a/app/views/notifications_mailer/notify_member_training_invalidated.html.erb
+++ b/app/views/notifications_mailer/notify_member_training_invalidated.html.erb
@@ -1,6 +1,6 @@
<%= render 'notifications_mailer/shared/hello', recipient: @recipient %>
-<%= t('.body.training_invalidated_html', {
+<%= t('.body.training_invalidated_html', **{
TRAINING: @attached_object.name,
MACHINES: @attached_object.machines.map(&:name).join(', '),
DATE: I18n.l((DateTime.current - @attached_object.authorization_period.months).to_date),
From 5bc34971d124b8de953bc4cf0ba56bc5e647876a Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Tue, 25 Apr 2023 15:11:46 +0200
Subject: [PATCH 09/45] (bug) unable to show calendar
---
CHANGELOG.md | 1 +
app/frontend/src/javascript/controllers/admin/calendar.js | 4 ++--
app/frontend/src/javascript/controllers/admin/settings.js | 4 ++--
app/frontend/src/javascript/controllers/calendar.js | 4 ++--
app/frontend/src/javascript/controllers/machines.js.erb | 4 ++--
app/frontend/src/javascript/controllers/spaces.js.erb | 4 ++--
app/frontend/src/javascript/controllers/trainings.js.erb | 4 ++--
7 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dd5626219..c0075074b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
- Fix a bug: notification is broken when delete a project
- Fix a bug: broken notifications email
+- Fix a bug: unable to show calendar
- [TODO DEPLOY] `rails fablab:maintenance:clean_abuse_notifications`
## v6.0.3 2023 April 12
diff --git a/app/frontend/src/javascript/controllers/admin/calendar.js b/app/frontend/src/javascript/controllers/admin/calendar.js
index cb290a790..bf3363d68 100644
--- a/app/frontend/src/javascript/controllers/admin/calendar.js
+++ b/app/frontend/src/javascript/controllers/admin/calendar.js
@@ -69,8 +69,8 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
snapDuration: BOOKING_SNAP,
selectable: true,
selectHelper: true,
- minTime: moment.duration(moment(bookingWindowStart.setting.value).format('HH:mm:ss')),
- maxTime: moment.duration(moment(bookingWindowEnd.setting.value).format('HH:mm:ss')),
+ minTime: moment.duration(moment.utc(bookingWindowStart.setting.value).format('HH:mm:ss')),
+ maxTime: moment.duration(moment.utc(bookingWindowEnd.setting.value).format('HH:mm:ss')),
select (start, end, jsEvent, view) {
return calendarSelectCb(start, end, jsEvent, view);
},
diff --git a/app/frontend/src/javascript/controllers/admin/settings.js b/app/frontend/src/javascript/controllers/admin/settings.js
index e43bca1dc..91ffa6818 100644
--- a/app/frontend/src/javascript/controllers/admin/settings.js
+++ b/app/frontend/src/javascript/controllers/admin/settings.js
@@ -71,8 +71,8 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
$scope.subscriptionExplicationsAlert = { name: 'subscription_explications_alert', value: settingsPromise.subscription_explications_alert };
$scope.eventExplicationsAlert = { name: 'event_explications_alert', value: settingsPromise.event_explications_alert };
$scope.spaceExplicationsAlert = { name: 'space_explications_alert', value: settingsPromise.space_explications_alert };
- $scope.windowStart = { name: 'booking_window_start', value: settingsPromise.booking_window_start };
- $scope.windowEnd = { name: 'booking_window_end', value: settingsPromise.booking_window_end };
+ $scope.windowStart = { name: 'booking_window_start', value: moment.utc(settingsPromise.booking_window_start).format('YYYY-MM-DD HH:mm:ss') };
+ $scope.windowEnd = { name: 'booking_window_end', value: moment.utc(settingsPromise.booking_window_end).format('YYYY-MM-DD HH:mm:ss') };
$scope.mainColorSetting = { name: 'main_color', value: settingsPromise.main_color };
$scope.secondColorSetting = { name: 'secondary_color', value: settingsPromise.secondary_color };
$scope.nameGenre = { name: 'name_genre', value: settingsPromise.name_genre };
diff --git a/app/frontend/src/javascript/controllers/calendar.js b/app/frontend/src/javascript/controllers/calendar.js
index a4f4ac162..e862a670d 100644
--- a/app/frontend/src/javascript/controllers/calendar.js
+++ b/app/frontend/src/javascript/controllers/calendar.js
@@ -204,8 +204,8 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$
center: 'title',
right: ''
},
- minTime: moment.duration(moment(bookingWindowStart.setting.value).format('HH:mm:ss')),
- maxTime: moment.duration(moment(bookingWindowEnd.setting.value).format('HH:mm:ss')),
+ minTime: moment.duration(moment.utc(bookingWindowStart.setting.value).format('HH:mm:ss')),
+ maxTime: moment.duration(moment.utc(bookingWindowEnd.setting.value).format('HH:mm:ss')),
defaultView: window.innerWidth <= 480 ? 'agendaDay' : 'agendaWeek',
eventClick (event, jsEvent, view) {
return calendarEventClickCb(event, jsEvent, view);
diff --git a/app/frontend/src/javascript/controllers/machines.js.erb b/app/frontend/src/javascript/controllers/machines.js.erb
index c6fa655c9..b62e273f3 100644
--- a/app/frontend/src/javascript/controllers/machines.js.erb
+++ b/app/frontend/src/javascript/controllers/machines.js.erb
@@ -447,8 +447,8 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$tran
// fullCalendar (v2) configuration
$scope.calendarConfig = CalendarConfig({
- minTime: moment.duration(moment(settingsPromise.booking_window_start).format('HH:mm:ss')),
- maxTime: moment.duration(moment(settingsPromise.booking_window_end).format('HH:mm:ss')),
+ minTime: moment.duration(moment.utc(settingsPromise.booking_window_start).format('HH:mm:ss')),
+ maxTime: moment.duration(moment.utc(settingsPromise.booking_window_end).format('HH:mm:ss')),
eventClick (event, jsEvent, view) {
return calendarEventClickCb(event, jsEvent, view);
},
diff --git a/app/frontend/src/javascript/controllers/spaces.js.erb b/app/frontend/src/javascript/controllers/spaces.js.erb
index 13899c7c2..fc9586d69 100644
--- a/app/frontend/src/javascript/controllers/spaces.js.erb
+++ b/app/frontend/src/javascript/controllers/spaces.js.erb
@@ -385,8 +385,8 @@ Application.Controllers.controller('ReserveSpaceController', ['$scope', '$transi
// fullCalendar (v2) configuration
$scope.calendarConfig = CalendarConfig({
- minTime: moment.duration(moment(settingsPromise.booking_window_start).format('HH:mm:ss')),
- maxTime: moment.duration(moment(settingsPromise.booking_window_end).format('HH:mm:ss')),
+ minTime: moment.duration(moment.utc(settingsPromise.booking_window_start).format('HH:mm:ss')),
+ maxTime: moment.duration(moment.utc(settingsPromise.booking_window_end).format('HH:mm:ss')),
eventClick (event, jsEvent, view) {
return calendarEventClickCb(event, jsEvent, view);
},
diff --git a/app/frontend/src/javascript/controllers/trainings.js.erb b/app/frontend/src/javascript/controllers/trainings.js.erb
index 929e56148..f051845f5 100644
--- a/app/frontend/src/javascript/controllers/trainings.js.erb
+++ b/app/frontend/src/javascript/controllers/trainings.js.erb
@@ -155,8 +155,8 @@ Application.Controllers.controller('ReserveTrainingController', ['$scope', '$tra
// fullCalendar (v2) configuration
$scope.calendarConfig = CalendarConfig({
- minTime: moment.duration(moment(settingsPromise.booking_window_start).format('HH:mm:ss')),
- maxTime: moment.duration(moment(settingsPromise.booking_window_end).format('HH:mm:ss')),
+ minTime: moment.duration(moment.utc(settingsPromise.booking_window_start).format('HH:mm:ss')),
+ maxTime: moment.duration(moment.utc(settingsPromise.booking_window_end).format('HH:mm:ss')),
eventClick (event, jsEvent, view) {
return calendarEventClickCb(event, jsEvent, view);
},
From b5f4d7a541d2f1b0914666f407511e294b2e5ac4 Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Tue, 25 Apr 2023 15:29:22 +0200
Subject: [PATCH 10/45] (i18n) update locales
---
CHANGELOG.md | 1 +
config/locales/app.admin.it.yml | 14 +++++++-------
config/locales/app.public.it.yml | 8 ++++----
config/locales/app.shared.it.yml | 2 +-
4 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c0075074b..eb44da00b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
- Fix a bug: notification is broken when delete a project
- Fix a bug: broken notifications email
- Fix a bug: unable to show calendar
+- Update translations from Crowdin
- [TODO DEPLOY] `rails fablab:maintenance:clean_abuse_notifications`
## v6.0.3 2023 April 12
diff --git a/config/locales/app.admin.it.yml b/config/locales/app.admin.it.yml
index 6398a72a2..4a62c96b2 100644
--- a/config/locales/app.admin.it.yml
+++ b/config/locales/app.admin.it.yml
@@ -112,7 +112,7 @@ it:
create_success: "Lo spazio è stato creato correttamente"
update_success: "Lo spazio è stato aggiornato correttamente"
event_form:
- ACTION_title: "{ACTION, select, create{Nuovo} other{Aggiorna l'}} evento"
+ ACTION_title: "{ACTION, select, create{Nuovo} other{Aggiorna}} evento"
title: "Titolo"
matching_visual: "Corrispondenza illustazione"
description: "Descrizione"
@@ -152,7 +152,7 @@ it:
every_month: "Ogni mese"
every_year: "Ogni anno"
plan_form:
- ACTION_title: "{ACTION, select, create{Nuovo} other{Aggiorna l'}}evento"
+ ACTION_title: "{ACTION, select, create{Nuovo} other{Aggiorna}}evento"
tab_settings: "Impostazioni"
tab_usage_limits: "Limiti di utilizzo"
description: "Descrizione"
@@ -285,7 +285,7 @@ it:
#manage the trainings & machines slots
calendar:
calendar_management: "Gestione calendario"
- trainings: "Certificazioni"
+ trainings: "Abilitazioni"
machines: "Macchine"
spaces: "Spazi"
events: "Eventi"
@@ -416,7 +416,7 @@ it:
themes: "Temi"
add_a_new_theme: "Aggiungi un nuovo tema"
licences: "Licenze"
- statuses: "Statuto"
+ statuses: "Status"
description: "Descrizione"
add_a_new_licence: "Aggiungere una nuova licenza"
manage_abuses: "Gestisci i report"
@@ -1783,7 +1783,7 @@ it:
title: "Titolo"
fablab_title: "Titolo del FabLab"
title_concordance: "Corrispondenza del titolo"
- male: "Machio."
+ male: "Maschio."
female: "Femmina."
neutral: "Neutrale."
eg: "es:"
@@ -1804,7 +1804,7 @@ it:
public_registrations_allowed: "Registrazioni pubbliche consentite"
help: "Aiuto"
feature_tour: "Tour delle funzionalità"
- feature_tour_info_html: "Quando un amministratore o un manager è connesso, un tour di funzionalità verrà attivato la prima volta che visita ogni sezione dell'applicazione. È possibile modificare questo comportamento in uno dei seguenti valori:
« Una volta » per mantenere il comportamento predefinito. « Per sessione » per visualizzare i tour ogni volta che riapri l'applicazione. « trigger manuale» per impedire la visualizzazione automatica dei tour. Sarà comunque possibile attivarli premendo il tasto F1 o cliccando su « Aiuto » nel menu dell'utente. "
+ feature_tour_info_html: "Quando un amministratore o un manager è connesso, un tour di funzionalità verrà attivato la prima volta che visita ogni sezione dell'applicazione. È possibile modificare questo comportamento in uno dei seguenti valori:
« Una volta » per mantenere il comportamento predefinito. « Per sessione » per visualizzare i tour ogni volta che riapri l'applicazione. « Trigger manuale» per impedire la visualizzazione automatica dei tour. Sarà comunque possibile attivarli premendo il tasto F1 o cliccando su « Aiuto » nel menu dell'utente. "
feature_tour_display_mode: "Modalità visualizzazione tour funzionalità"
display_mode:
once: "Una volta"
@@ -2373,7 +2373,7 @@ it:
filter_clear: "Cancella tutto"
filter_apply: "Applica"
filter_ref: "Per riferimento"
- filter_status: "Per stato"
+ filter_status: "Per status"
filter_client: "Per cliente"
filter_period: "Per periodo"
filter_period_from: "Da"
diff --git a/config/locales/app.public.it.yml b/config/locales/app.public.it.yml
index 5c6a0c76f..963c6a577 100644
--- a/config/locales/app.public.it.yml
+++ b/config/locales/app.public.it.yml
@@ -185,12 +185,12 @@ it:
rough_draft: "Bozza preliminare"
status_filter:
all_statuses: "Tutti gli stati"
- select_status: "Seleziona uno stato"
+ select_status: "Seleziona uno status"
#details of a projet
projects_show:
rough_draft: "Bozza"
project_description: "Descrizione del progetto"
- by_name: "Per {NAME}"
+ by_name: "di {NAME}"
step_N: "Fase {INDEX}"
share_on_facebook: "Condividi su Facebook"
share_on_twitter: "Condividi su Twitter"
@@ -237,7 +237,7 @@ it:
all_machines: "Tutte le macchine"
machine_card:
book: "Prenota"
- consult: "Guarda"
+ consult: "Vedi"
#details of a machine
machines_show:
book_this_machine: "Modifica questa macchina"
@@ -549,7 +549,7 @@ it:
content: "Le macchine sono gli utensili a disposizione degli utenti da prenotare."
view:
title: "Visualizza"
- content: "Per modificare o eliminare una macchina, prima clicca qui. Non sarai in grado di eliminare una macchina che è già stata associata a uno slot, ma potrai disattivarla."
+ content: "Per modificare o eliminare una macchina, clicca qui. Non sarai in grado di eliminare una macchina che è già stata associata a uno slot di disponibilità, ma potrai disattivarla."
reserve:
title: "Prenota"
content: "Clicca qui per accedere a un calendario che mostra slot liberi. Questo ti permetterà di prenotare questa macchina per un utente e di gestire le prenotazioni esistenti."
diff --git a/config/locales/app.shared.it.yml b/config/locales/app.shared.it.yml
index 9adae1b81..f136a1376 100644
--- a/config/locales/app.shared.it.yml
+++ b/config/locales/app.shared.it.yml
@@ -4,7 +4,7 @@ it:
#translations of common buttons
buttons:
confirm_changes: "Conferma le modifiche"
- consult: "Guarda"
+ consult: "Vedi"
edit: "Modifica"
change: "Modifica"
delete: "Elimina"
From fe7394d9a84119e705f94638e81586ab59f7e0af Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Tue, 25 Apr 2023 16:00:18 +0200
Subject: [PATCH 11/45] Version 6.0.4
---
CHANGELOG.md | 2 ++
package.json | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index eb44da00b..cccb06abf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,7 @@
# Changelog Fab-manager
+## v6.0.4 2023 April 25
+
- Fix a bug: notification is broken when delete a project
- Fix a bug: broken notifications email
- Fix a bug: unable to show calendar
diff --git a/package.json b/package.json
index 3b042d3a8..82bfc73d6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "fab-manager",
- "version": "6.0.3",
+ "version": "6.0.4",
"description": "Fab-manager 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 47370723a872f4c3dfd5cf8ca8a17475ac373366 Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Tue, 2 May 2023 05:49:45 -0400
Subject: [PATCH 12/45] (bug) unable to show calendar for Firefox and Safari
---
CHANGELOG.md | 2 ++
app/frontend/src/javascript/controllers/admin/calendar.js | 4 ++--
app/frontend/src/javascript/controllers/calendar.js | 4 ++--
app/frontend/src/javascript/controllers/machines.js.erb | 4 ++--
app/frontend/src/javascript/controllers/spaces.js.erb | 4 ++--
app/frontend/src/javascript/controllers/trainings.js.erb | 4 ++--
6 files changed, 12 insertions(+), 10 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cccb06abf..9cc9e57ec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,7 @@
# Changelog Fab-manager
+- Fix a bug: unable to show calendar for Firefox and Safari
+
## v6.0.4 2023 April 25
- Fix a bug: notification is broken when delete a project
diff --git a/app/frontend/src/javascript/controllers/admin/calendar.js b/app/frontend/src/javascript/controllers/admin/calendar.js
index bf3363d68..67af6ec61 100644
--- a/app/frontend/src/javascript/controllers/admin/calendar.js
+++ b/app/frontend/src/javascript/controllers/admin/calendar.js
@@ -69,8 +69,8 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
snapDuration: BOOKING_SNAP,
selectable: true,
selectHelper: true,
- minTime: moment.duration(moment.utc(bookingWindowStart.setting.value).format('HH:mm:ss')),
- maxTime: moment.duration(moment.utc(bookingWindowEnd.setting.value).format('HH:mm:ss')),
+ minTime: moment.duration(moment.utc(bookingWindowStart.setting.value.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
+ maxTime: moment.duration(moment.utc(bookingWindowEnd.setting.value.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
select (start, end, jsEvent, view) {
return calendarSelectCb(start, end, jsEvent, view);
},
diff --git a/app/frontend/src/javascript/controllers/calendar.js b/app/frontend/src/javascript/controllers/calendar.js
index e862a670d..2581496a4 100644
--- a/app/frontend/src/javascript/controllers/calendar.js
+++ b/app/frontend/src/javascript/controllers/calendar.js
@@ -204,8 +204,8 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$
center: 'title',
right: ''
},
- minTime: moment.duration(moment.utc(bookingWindowStart.setting.value).format('HH:mm:ss')),
- maxTime: moment.duration(moment.utc(bookingWindowEnd.setting.value).format('HH:mm:ss')),
+ minTime: moment.duration(moment.utc(bookingWindowStart.setting.value.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
+ maxTime: moment.duration(moment.utc(bookingWindowEnd.setting.value.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
defaultView: window.innerWidth <= 480 ? 'agendaDay' : 'agendaWeek',
eventClick (event, jsEvent, view) {
return calendarEventClickCb(event, jsEvent, view);
diff --git a/app/frontend/src/javascript/controllers/machines.js.erb b/app/frontend/src/javascript/controllers/machines.js.erb
index b62e273f3..3c5851e6b 100644
--- a/app/frontend/src/javascript/controllers/machines.js.erb
+++ b/app/frontend/src/javascript/controllers/machines.js.erb
@@ -447,8 +447,8 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$tran
// fullCalendar (v2) configuration
$scope.calendarConfig = CalendarConfig({
- minTime: moment.duration(moment.utc(settingsPromise.booking_window_start).format('HH:mm:ss')),
- maxTime: moment.duration(moment.utc(settingsPromise.booking_window_end).format('HH:mm:ss')),
+ minTime: moment.duration(moment.utc(settingsPromise.booking_window_start.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
+ maxTime: moment.duration(moment.utc(settingsPromise.booking_window_end.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
eventClick (event, jsEvent, view) {
return calendarEventClickCb(event, jsEvent, view);
},
diff --git a/app/frontend/src/javascript/controllers/spaces.js.erb b/app/frontend/src/javascript/controllers/spaces.js.erb
index fc9586d69..a6e4c3b85 100644
--- a/app/frontend/src/javascript/controllers/spaces.js.erb
+++ b/app/frontend/src/javascript/controllers/spaces.js.erb
@@ -385,8 +385,8 @@ Application.Controllers.controller('ReserveSpaceController', ['$scope', '$transi
// fullCalendar (v2) configuration
$scope.calendarConfig = CalendarConfig({
- minTime: moment.duration(moment.utc(settingsPromise.booking_window_start).format('HH:mm:ss')),
- maxTime: moment.duration(moment.utc(settingsPromise.booking_window_end).format('HH:mm:ss')),
+ minTime: moment.duration(moment.utc(settingsPromise.booking_window_start.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
+ maxTime: moment.duration(moment.utc(settingsPromise.booking_window_end.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
eventClick (event, jsEvent, view) {
return calendarEventClickCb(event, jsEvent, view);
},
diff --git a/app/frontend/src/javascript/controllers/trainings.js.erb b/app/frontend/src/javascript/controllers/trainings.js.erb
index f051845f5..e08b69ff8 100644
--- a/app/frontend/src/javascript/controllers/trainings.js.erb
+++ b/app/frontend/src/javascript/controllers/trainings.js.erb
@@ -155,8 +155,8 @@ Application.Controllers.controller('ReserveTrainingController', ['$scope', '$tra
// fullCalendar (v2) configuration
$scope.calendarConfig = CalendarConfig({
- minTime: moment.duration(moment.utc(settingsPromise.booking_window_start).format('HH:mm:ss')),
- maxTime: moment.duration(moment.utc(settingsPromise.booking_window_end).format('HH:mm:ss')),
+ minTime: moment.duration(moment.utc(settingsPromise.booking_window_start.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
+ maxTime: moment.duration(moment.utc(settingsPromise.booking_window_end.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
eventClick (event, jsEvent, view) {
return calendarEventClickCb(event, jsEvent, view);
},
From acbe74815751114b7e4f375d47c20099cb036a19 Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Tue, 2 May 2023 18:05:42 +0200
Subject: [PATCH 13/45] (bug) unable to show calendar
---
app/frontend/src/javascript/controllers/admin/calendar.js | 4 ++--
app/frontend/src/javascript/controllers/calendar.js | 4 ++--
app/frontend/src/javascript/controllers/machines.js.erb | 4 ++--
app/frontend/src/javascript/controllers/spaces.js.erb | 4 ++--
app/frontend/src/javascript/controllers/trainings.js.erb | 4 ++--
5 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/app/frontend/src/javascript/controllers/admin/calendar.js b/app/frontend/src/javascript/controllers/admin/calendar.js
index 67af6ec61..3e59d6b76 100644
--- a/app/frontend/src/javascript/controllers/admin/calendar.js
+++ b/app/frontend/src/javascript/controllers/admin/calendar.js
@@ -69,8 +69,8 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
snapDuration: BOOKING_SNAP,
selectable: true,
selectHelper: true,
- minTime: moment.duration(moment.utc(bookingWindowStart.setting.value.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
- maxTime: moment.duration(moment.utc(bookingWindowEnd.setting.value.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
+ minTime: moment.duration(moment.utc(bookingWindowStart.setting.value.match(/\d{4}-\d{2}-\d{2}(?: |T)\d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
+ maxTime: moment.duration(moment.utc(bookingWindowEnd.setting.value.match(/\d{4}-\d{2}-\d{2}(?: |T)\d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
select (start, end, jsEvent, view) {
return calendarSelectCb(start, end, jsEvent, view);
},
diff --git a/app/frontend/src/javascript/controllers/calendar.js b/app/frontend/src/javascript/controllers/calendar.js
index 2581496a4..e0977c9dd 100644
--- a/app/frontend/src/javascript/controllers/calendar.js
+++ b/app/frontend/src/javascript/controllers/calendar.js
@@ -204,8 +204,8 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$
center: 'title',
right: ''
},
- minTime: moment.duration(moment.utc(bookingWindowStart.setting.value.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
- maxTime: moment.duration(moment.utc(bookingWindowEnd.setting.value.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
+ minTime: moment.duration(moment.utc(bookingWindowStart.setting.value.match(/\d{4}-\d{2}-\d{2}(?: |T)\d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
+ maxTime: moment.duration(moment.utc(bookingWindowEnd.setting.value.match(/\d{4}-\d{2}-\d{2}(?: |T)\d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
defaultView: window.innerWidth <= 480 ? 'agendaDay' : 'agendaWeek',
eventClick (event, jsEvent, view) {
return calendarEventClickCb(event, jsEvent, view);
diff --git a/app/frontend/src/javascript/controllers/machines.js.erb b/app/frontend/src/javascript/controllers/machines.js.erb
index 3c5851e6b..9d8556865 100644
--- a/app/frontend/src/javascript/controllers/machines.js.erb
+++ b/app/frontend/src/javascript/controllers/machines.js.erb
@@ -447,8 +447,8 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$tran
// fullCalendar (v2) configuration
$scope.calendarConfig = CalendarConfig({
- minTime: moment.duration(moment.utc(settingsPromise.booking_window_start.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
- maxTime: moment.duration(moment.utc(settingsPromise.booking_window_end.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
+ minTime: moment.duration(moment.utc(settingsPromise.booking_window_start.match(/\d{4}-\d{2}-\d{2}(?: |T)\d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
+ maxTime: moment.duration(moment.utc(settingsPromise.booking_window_end.match(/\d{4}-\d{2}-\d{2}(?: |T)\d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
eventClick (event, jsEvent, view) {
return calendarEventClickCb(event, jsEvent, view);
},
diff --git a/app/frontend/src/javascript/controllers/spaces.js.erb b/app/frontend/src/javascript/controllers/spaces.js.erb
index a6e4c3b85..d4bded6da 100644
--- a/app/frontend/src/javascript/controllers/spaces.js.erb
+++ b/app/frontend/src/javascript/controllers/spaces.js.erb
@@ -385,8 +385,8 @@ Application.Controllers.controller('ReserveSpaceController', ['$scope', '$transi
// fullCalendar (v2) configuration
$scope.calendarConfig = CalendarConfig({
- minTime: moment.duration(moment.utc(settingsPromise.booking_window_start.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
- maxTime: moment.duration(moment.utc(settingsPromise.booking_window_end.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
+ minTime: moment.duration(moment.utc(settingsPromise.booking_window_start.match(/\d{4}-\d{2}-\d{2}(?: |T)\d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
+ maxTime: moment.duration(moment.utc(settingsPromise.booking_window_end.match(/\d{4}-\d{2}-\d{2}(?: |T)\d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
eventClick (event, jsEvent, view) {
return calendarEventClickCb(event, jsEvent, view);
},
diff --git a/app/frontend/src/javascript/controllers/trainings.js.erb b/app/frontend/src/javascript/controllers/trainings.js.erb
index e08b69ff8..0fa0a18a8 100644
--- a/app/frontend/src/javascript/controllers/trainings.js.erb
+++ b/app/frontend/src/javascript/controllers/trainings.js.erb
@@ -155,8 +155,8 @@ Application.Controllers.controller('ReserveTrainingController', ['$scope', '$tra
// fullCalendar (v2) configuration
$scope.calendarConfig = CalendarConfig({
- minTime: moment.duration(moment.utc(settingsPromise.booking_window_start.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
- maxTime: moment.duration(moment.utc(settingsPromise.booking_window_end.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
+ minTime: moment.duration(moment.utc(settingsPromise.booking_window_start.match(/\d{4}-\d{2}-\d{2}(?: |T)\d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
+ maxTime: moment.duration(moment.utc(settingsPromise.booking_window_end.match(/\d{4}-\d{2}-\d{2}(?: |T)\d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
eventClick (event, jsEvent, view) {
return calendarEventClickCb(event, jsEvent, view);
},
From 6bb74bbc6a061fd77ea7d02c82881a9255ac164a Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Tue, 2 May 2023 18:07:21 +0200
Subject: [PATCH 14/45] (quality) error message for event reservation
---
CHANGELOG.md | 1 +
app/frontend/src/javascript/controllers/events.js.erb | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9cc9e57ec..01c07c64d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
# Changelog Fab-manager
- Fix a bug: unable to show calendar for Firefox and Safari
+- Improved error message for event reservation
## v6.0.4 2023 April 25
diff --git a/app/frontend/src/javascript/controllers/events.js.erb b/app/frontend/src/javascript/controllers/events.js.erb
index 8fe4e618a..09b5ab08c 100644
--- a/app/frontend/src/javascript/controllers/events.js.erb
+++ b/app/frontend/src/javascript/controllers/events.js.erb
@@ -372,7 +372,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
}
, function (response) {
// reservation failed
- growl.error(response && response.data && response.data.card && response.data.card[0] || 'server error');
+ growl.error(response && response.data && _.keys(response.data)[0] && response.data[_.keys(response.data)[0]][0] || 'server error');
// unset the attempting marker
$scope.attempting = false;
})
From 6e9cfe11c3a8997324761338dbd9cb113ea5027b Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Tue, 2 May 2023 18:15:49 +0200
Subject: [PATCH 15/45] Version 6.0.5
---
CHANGELOG.md | 2 ++
package.json | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 01c07c64d..f780dcd75 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,7 @@
# Changelog Fab-manager
+## v6.0.5 2023 May 2
+
- Fix a bug: unable to show calendar for Firefox and Safari
- Improved error message for event reservation
diff --git a/package.json b/package.json
index 82bfc73d6..1d657277e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "fab-manager",
- "version": "6.0.4",
+ "version": "6.0.5",
"description": "Fab-manager 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 2756502131b343fcc564764e17cf6d1bc9a25dc8 Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Thu, 4 May 2023 14:15:14 +0200
Subject: [PATCH 16/45] (bug) invalid duration for machine/spaces reservations
in statistics, when using slots of not 1 hour
---
CHANGELOG.md | 3 +++
app/helpers/excel_helper.rb | 2 +-
app/models/concerns/stat_concern.rb | 2 +-
app/services/statistics/concerns/helpers_concern.rb | 6 +++---
app/services/statistics/fetcher_service.rb | 4 ++--
5 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f780dcd75..85da5d366 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
# Changelog Fab-manager
+- Fix a bug: invalid duration for machine/spaces reservations in statistics, when using slots of not 1 hour
+- [TODO DEPLOY] `rails fablab:es:build_stats` THEN `rails fablab:maintenance:regenerate_statistics[2014,1]`
+
## v6.0.5 2023 May 2
- Fix a bug: unable to show calendar for Firefox and Safari
diff --git a/app/helpers/excel_helper.rb b/app/helpers/excel_helper.rb
index 81653b6d2..de4622826 100644
--- a/app/helpers/excel_helper.rb
+++ b/app/helpers/excel_helper.rb
@@ -41,7 +41,7 @@ module ExcelHelper
unless type.simple
data.push hit['_source']['stat']
styles.push nil
- types.push :string
+ types.push :float
end
[data, styles, types]
diff --git a/app/models/concerns/stat_concern.rb b/app/models/concerns/stat_concern.rb
index 1743af6e4..2b0549ba4 100644
--- a/app/models/concerns/stat_concern.rb
+++ b/app/models/concerns/stat_concern.rb
@@ -8,7 +8,7 @@ module StatConcern
attribute :type, String
attribute :subType, String
attribute :date, String
- attribute :stat, Integer
+ attribute :stat, Float
attribute :userId, Integer
attribute :gender, String
attribute :age, Integer
diff --git a/app/services/statistics/concerns/helpers_concern.rb b/app/services/statistics/concerns/helpers_concern.rb
index d851a4eb2..6508fe4b1 100644
--- a/app/services/statistics/concerns/helpers_concern.rb
+++ b/app/services/statistics/concerns/helpers_concern.rb
@@ -39,11 +39,11 @@ module Statistics::Concerns::HelpersConcern
def difference_in_hours(start_at, end_at)
if start_at.to_date == end_at.to_date
- ((end_at - start_at) / 3600.0).to_i
+ ((end_at - start_at) / 3600.0).to_f
else
end_at_to_start_date = end_at.change(year: start_at.year, month: start_at.month, day: start_at.day)
- hours = ((end_at_to_start_date - start_at) / 60 / 60).to_i
- hours = ((end_at.to_date - start_at.to_date).to_i + 1) * hours if end_at.to_date > start_at.to_date
+ hours = ((end_at_to_start_date - start_at) / 60 / 60).to_f
+ hours = ((end_at.to_date - start_at.to_date).to_f + 1) * hours if end_at.to_date > start_at.to_date
hours
end
end
diff --git a/app/services/statistics/fetcher_service.rb b/app/services/statistics/fetcher_service.rb
index 42eb79817..ee130f4dd 100644
--- a/app/services/statistics/fetcher_service.rb
+++ b/app/services/statistics/fetcher_service.rb
@@ -57,7 +57,7 @@ class Statistics::FetcherService
machine_type: r.reservable.friendly_id,
machine_name: r.reservable.name,
slot_dates: r.slots.map(&:start_at).map(&:to_date),
- nb_hours: (r.slots.map(&:duration).map(&:to_i).reduce(:+) / 3600.0).to_i,
+ nb_hours: (r.slots.map(&:duration).map(&:to_i).reduce(:+) / 3600.0).to_f,
ca: calcul_ca(r.original_invoice) }.merge(user_info(profile))
yield result
end
@@ -81,7 +81,7 @@ class Statistics::FetcherService
space_name: r.reservable.name,
space_type: r.reservable.slug,
slot_dates: r.slots.map(&:start_at).map(&:to_date),
- nb_hours: (r.slots.map(&:duration).map(&:to_i).reduce(:+) / 3600.0).to_i,
+ nb_hours: (r.slots.map(&:duration).map(&:to_i).reduce(:+) / 3600.0).to_f,
ca: calcul_ca(r.original_invoice) }.merge(user_info(profile))
yield result
end
From 7cff0d6c398f98689f8f7067a473abc749836c48 Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Thu, 4 May 2023 14:26:15 +0200
Subject: [PATCH 17/45] Version 6.0.6
---
CHANGELOG.md | 2 ++
package.json | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 85da5d366..98e10f82e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,7 @@
# Changelog Fab-manager
+## v6.0.6 2023 May 4
+
- Fix a bug: invalid duration for machine/spaces reservations in statistics, when using slots of not 1 hour
- [TODO DEPLOY] `rails fablab:es:build_stats` THEN `rails fablab:maintenance:regenerate_statistics[2014,1]`
diff --git a/package.json b/package.json
index 1d657277e..38cad9cdb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "fab-manager",
- "version": "6.0.5",
+ "version": "6.0.6",
"description": "Fab-manager 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 be539b1f768f8299ea643b10b452edbb09b267ec Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Fri, 31 Mar 2023 14:44:37 +0200
Subject: [PATCH 18/45] (feat) add Family account setting
---
app/frontend/src/javascript/models/setting.ts | 3 ++-
app/frontend/templates/admin/settings/compte.html | 11 +++++++++++
app/helpers/settings_helper.rb | 1 +
app/policies/setting_policy.rb | 2 +-
config/locales/app.admin.en.yml | 3 +++
config/locales/app.admin.fr.yml | 3 +++
config/locales/en.yml | 1 +
db/seeds/settings.rb | 2 ++
test/fixtures/history_values.yml | 8 ++++++++
test/fixtures/settings.yml | 6 ++++++
test/frontend/__fixtures__/settings.ts | 6 ++++++
11 files changed, 44 insertions(+), 2 deletions(-)
diff --git a/app/frontend/src/javascript/models/setting.ts b/app/frontend/src/javascript/models/setting.ts
index 27f84db14..5090487cb 100644
--- a/app/frontend/src/javascript/models/setting.ts
+++ b/app/frontend/src/javascript/models/setting.ts
@@ -178,7 +178,8 @@ export const accountSettings = [
'external_id',
'user_change_group',
'user_validation_required',
- 'user_validation_required_list'
+ 'user_validation_required_list',
+ 'family_account'
] as const;
export const analyticsSettings = [
diff --git a/app/frontend/templates/admin/settings/compte.html b/app/frontend/templates/admin/settings/compte.html
index 0ed115706..3935b9c98 100644
--- a/app/frontend/templates/admin/settings/compte.html
+++ b/app/frontend/templates/admin/settings/compte.html
@@ -51,6 +51,17 @@
+
+
{{ 'app.admin.settings.family_account' }}
+
+
+
+
+
{{ 'app.admin.settings.captcha' }}
diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb
index 1520bd44d..4029db1fa 100644
--- a/app/helpers/settings_helper.rb
+++ b/app/helpers/settings_helper.rb
@@ -167,6 +167,7 @@ module SettingsHelper
user_validation_required
user_validation_required_list
show_username_in_admin_list
+ family_account
store_module
store_withdrawal_instructions
store_hidden
diff --git a/app/policies/setting_policy.rb b/app/policies/setting_policy.rb
index 1593beb55..8c83827fa 100644
--- a/app/policies/setting_policy.rb
+++ b/app/policies/setting_policy.rb
@@ -46,7 +46,7 @@ class SettingPolicy < ApplicationPolicy
external_id machines_banner_active machines_banner_text machines_banner_cta_active machines_banner_cta_label
machines_banner_cta_url trainings_banner_active trainings_banner_text trainings_banner_cta_active trainings_banner_cta_label
trainings_banner_cta_url events_banner_active events_banner_text events_banner_cta_active events_banner_cta_label
- events_banner_cta_url]
+ events_banner_cta_url family_account]
end
##
diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml
index 8714ac56d..b4ac6a9d9 100644
--- a/config/locales/app.admin.en.yml
+++ b/config/locales/app.admin.en.yml
@@ -1773,6 +1773,9 @@ en:
extended_prices_in_same_day: "Extended prices in the same day"
public_registrations: "Public registrations"
show_username_in_admin_list: "Show the username in the list"
+ family_account: "family account"
+ family_account_info_html: "By activating this option, you offer your members the possibility to add their child(ren) to their own account. You can also request proof if you wish to validate them."
+ enable_family_account: "Enable the Family Account option"
overlapping_options:
training_reservations: "Trainings"
machine_reservations: "Machines"
diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml
index e7082ef58..f89ade8b7 100644
--- a/config/locales/app.admin.fr.yml
+++ b/config/locales/app.admin.fr.yml
@@ -1773,6 +1773,9 @@ fr:
extended_prices_in_same_day: "Prix étendus le même jour"
public_registrations: "Inscriptions publiques"
show_username_in_admin_list: "Afficher le nom d'utilisateur dans la liste"
+ family_account: "Compte famille"
+ family_account_info_html: "En activant cette option, vous offrez à vos membres la possibilité d'ajouter sur leur propre compte leur(s) enfants. Vous pouvez aussi demander un justificatif si vous souhaitez les valider."
+ enable_family_account: "Activer l'option Compte Famille"
overlapping_options:
training_reservations: "Formations"
machine_reservations: "Machines"
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 3bd7aae37..ef51539d0 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -697,6 +697,7 @@ en:
trainings_authorization_validity_duration: "Trainings validity period duration"
trainings_invalidation_rule: "Trainings automatic invalidation"
trainings_invalidation_rule_period: "Grace period before invalidating a training"
+ family_account: "Family account"
#statuses of projects
statuses:
new: "New"
diff --git a/db/seeds/settings.rb b/db/seeds/settings.rb
index 4df47c23c..bd701eba0 100644
--- a/db/seeds/settings.rb
+++ b/db/seeds/settings.rb
@@ -728,3 +728,5 @@ Setting.set('accounting_Error_code', 'ERROR') unless Setting.find_by(name: 'acco
Setting.set('accounting_Error_label', 'Erroneous invoices to refund') unless Setting.find_by(name: 'accounting_Error_label').try(:value)
Setting.set('external_id', false) unless Setting.find_by(name: 'external_id').try(:value)
+
+Setting.set('family_account', false) unless Setting.find_by(name: 'family_account').try(:value)
diff --git a/test/fixtures/history_values.yml b/test/fixtures/history_values.yml
index 4ce437af6..42f684753 100644
--- a/test/fixtures/history_values.yml
+++ b/test/fixtures/history_values.yml
@@ -852,3 +852,11 @@ history_value_100:
updated_at: 2023-04-05 09:16:08.000511500 Z
invoicing_profile_id: 1
+history_value_101:
+ id: 101
+ setting_id: 100
+ value: 'false'
+ created_at: '2023-03-31 14:38:40.000421'
+ updated_at: '2023-03-31 14:38:40.000421'
+ footprint:
+ invoicing_profile_id: 1
diff --git a/test/fixtures/settings.yml b/test/fixtures/settings.yml
index eb21fa7f8..c6a05e176 100644
--- a/test/fixtures/settings.yml
+++ b/test/fixtures/settings.yml
@@ -586,3 +586,9 @@ setting_99:
name: home_css
created_at: 2023-04-05 09:16:08.000511500 Z
updated_at: 2023-04-05 09:16:08.000511500 Z
+
+setting_100:
+ id: 100
+ name: family_account
+ created_at: 2023-03-31 14:38:40.000421500 Z
+ updated_at: 2023-03-31 14:38:40.000421500 Z
diff --git a/test/frontend/__fixtures__/settings.ts b/test/frontend/__fixtures__/settings.ts
index 03dd134be..578fc6dbb 100644
--- a/test/frontend/__fixtures__/settings.ts
+++ b/test/frontend/__fixtures__/settings.ts
@@ -825,6 +825,12 @@ export const settings: Array
= [
value: 'https://www.sleede.com/',
last_update: '2022-12-23T14:39:12+0100',
localized: 'Url'
+ },
+ {
+ name: 'family_account',
+ value: 'false',
+ last_update: '2023-03-31T14:39:12+0100',
+ localized: 'Family account'
}
];
From 10ef342edffb801251a5c8642be6f417b97d9bba Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Mon, 3 Apr 2023 18:23:49 +0200
Subject: [PATCH 19/45] (wip) add/edit user children
---
app/controllers/api/children_controller.rb | 52 +++++++++++
app/frontend/src/javascript/api/child.ts | 31 +++++++
.../components/family-account/child-form.tsx | 53 ++++++++++++
.../components/family-account/child-item.tsx | 38 ++++++++
.../components/family-account/child-modal.tsx | 69 +++++++++++++++
.../family-account/children-list.tsx | 86 +++++++++++++++++++
.../src/javascript/controllers/children.js | 23 +++++
app/frontend/src/javascript/models/child.ts | 16 ++++
app/frontend/src/javascript/router.js | 16 +++-
.../templates/dashboard/children.html | 11 +++
app/frontend/templates/dashboard/nav.html | 1 +
app/models/child.rb | 14 +++
app/models/user.rb | 3 +
app/policies/child_policy.rb | 31 +++++++
app/views/api/children/_child.json.jbuilder | 3 +
app/views/api/children/create.json.jbuilder | 3 +
app/views/api/children/index.json.jbuilder | 5 ++
app/views/api/children/update.json.jbuilder | 3 +
config/locales/app.public.en.yml | 3 +
config/locales/app.public.fr.yml | 16 ++++
config/routes.rb | 2 +
db/migrate/20230331132506_create_children.rb | 17 ++++
db/schema.rb | 17 +++-
23 files changed, 509 insertions(+), 4 deletions(-)
create mode 100644 app/controllers/api/children_controller.rb
create mode 100644 app/frontend/src/javascript/api/child.ts
create mode 100644 app/frontend/src/javascript/components/family-account/child-form.tsx
create mode 100644 app/frontend/src/javascript/components/family-account/child-item.tsx
create mode 100644 app/frontend/src/javascript/components/family-account/child-modal.tsx
create mode 100644 app/frontend/src/javascript/components/family-account/children-list.tsx
create mode 100644 app/frontend/src/javascript/controllers/children.js
create mode 100644 app/frontend/src/javascript/models/child.ts
create mode 100644 app/frontend/templates/dashboard/children.html
create mode 100644 app/models/child.rb
create mode 100644 app/policies/child_policy.rb
create mode 100644 app/views/api/children/_child.json.jbuilder
create mode 100644 app/views/api/children/create.json.jbuilder
create mode 100644 app/views/api/children/index.json.jbuilder
create mode 100644 app/views/api/children/update.json.jbuilder
create mode 100644 db/migrate/20230331132506_create_children.rb
diff --git a/app/controllers/api/children_controller.rb b/app/controllers/api/children_controller.rb
new file mode 100644
index 000000000..7c5a1003a
--- /dev/null
+++ b/app/controllers/api/children_controller.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+# API Controller for resources of type Child
+# Children are used to provide a way to manage multiple users in the family account
+class API::ChildrenController < API::ApiController
+ before_action :authenticate_user!
+ before_action :set_child, only: %i[show update destroy]
+
+ def index
+ @children = policy_scope(Child)
+ end
+
+ def show
+ authorize @child
+ end
+
+ def create
+ @child = Child.new(child_params)
+ authorize @child
+ if @child.save
+ render status: :created
+ else
+ render json: @child.errors.full_messages, status: :unprocessable_entity
+ end
+ end
+
+ def update
+ authorize @child
+
+ if @child.update(child_params)
+ render status: :ok
+ else
+ render json: @child.errors.full_messages, status: :unprocessable_entity
+ end
+ end
+
+ def destroy
+ authorize @child
+ @child.destroy
+ head :no_content
+ end
+
+ private
+
+ def set_child
+ @child = Child.find(params[:id])
+ end
+
+ def child_params
+ params.require(:child).permit(:first_name, :last_name, :email, :phone, :birthday, :user_id)
+ end
+end
diff --git a/app/frontend/src/javascript/api/child.ts b/app/frontend/src/javascript/api/child.ts
new file mode 100644
index 000000000..1ff948219
--- /dev/null
+++ b/app/frontend/src/javascript/api/child.ts
@@ -0,0 +1,31 @@
+import apiClient from './clients/api-client';
+import { AxiosResponse } from 'axios';
+import { Child, ChildIndexFilter } from '../models/child';
+import ApiLib from '../lib/api';
+
+export default class ChildAPI {
+ static async index (filters: ChildIndexFilter): Promise> {
+ const res: AxiosResponse> = await apiClient.get(`/api/children${ApiLib.filtersToQuery(filters)}`);
+ return res?.data;
+ }
+
+ static async get (id: number): Promise {
+ const res: AxiosResponse = await apiClient.get(`/api/children/${id}`);
+ return res?.data;
+ }
+
+ static async create (child: Child): Promise {
+ const res: AxiosResponse = await apiClient.post('/api/children', { child });
+ return res?.data;
+ }
+
+ static async update (child: Child): Promise {
+ const res: AxiosResponse = await apiClient.patch(`/api/children/${child.id}`, { child });
+ return res?.data;
+ }
+
+ static async destroy (childId: number): Promise {
+ const res: AxiosResponse = await apiClient.delete(`/api/children/${childId}`);
+ return res?.data;
+ }
+}
diff --git a/app/frontend/src/javascript/components/family-account/child-form.tsx b/app/frontend/src/javascript/components/family-account/child-form.tsx
new file mode 100644
index 000000000..fee281548
--- /dev/null
+++ b/app/frontend/src/javascript/components/family-account/child-form.tsx
@@ -0,0 +1,53 @@
+import React from 'react';
+import { useForm } from 'react-hook-form';
+import { useTranslation } from 'react-i18next';
+import { Child } from '../../models/child';
+import { TDateISODate } from '../../typings/date-iso';
+import { FormInput } from '../form/form-input';
+
+interface ChildFormProps {
+ child: Child;
+ onChange: (field: string, value: string | TDateISODate) => void;
+}
+
+/**
+ * A form for creating or editing a child.
+ */
+export const ChildForm: React.FC = ({ child, onChange }) => {
+ const { t } = useTranslation('public');
+
+ const { register, formState } = useForm({
+ defaultValues: child
+ });
+
+ /**
+ * Handle the change of a child form field
+ */
+ const handleChange = (event: React.ChangeEvent): void => {
+ onChange(event.target.id, event.target.value);
+ };
+
+ return (
+
+
+ {t('app.public.child_form.child_form_info')}
+
+
+
+ );
+};
diff --git a/app/frontend/src/javascript/components/family-account/child-item.tsx b/app/frontend/src/javascript/components/family-account/child-item.tsx
new file mode 100644
index 000000000..747d8212c
--- /dev/null
+++ b/app/frontend/src/javascript/components/family-account/child-item.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import { Child } from '../../models/child';
+import { useTranslation } from 'react-i18next';
+import { FabButton } from '../base/fab-button';
+
+interface ChildItemProps {
+ child: Child;
+ onEdit: (child: Child) => void;
+ onDelete: (child: Child) => void;
+}
+
+/**
+ * A child item.
+ */
+export const ChildItem: React.FC = ({ child, onEdit, onDelete }) => {
+ const { t } = useTranslation('public');
+
+ return (
+
+
+
{t('app.public.child_item.last_name')}
+
{child.last_name}
+
+
+
{t('app.public.child_item.first_name')}
+
{child.first_name}
+
+
+
{t('app.public.child_item.birthday')}
+
{child.birthday}
+
+
+ } onClick={() => onEdit(child)} className="edit-button" />
+ } onClick={() => onDelete(child)} className="delete-button" />
+
+
+ );
+};
diff --git a/app/frontend/src/javascript/components/family-account/child-modal.tsx b/app/frontend/src/javascript/components/family-account/child-modal.tsx
new file mode 100644
index 000000000..03f1486c4
--- /dev/null
+++ b/app/frontend/src/javascript/components/family-account/child-modal.tsx
@@ -0,0 +1,69 @@
+import * as React from 'react';
+import { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { FabModal, ModalSize } from '../base/fab-modal';
+import { Child } from '../../models/child';
+import { TDateISODate } from '../../typings/date-iso';
+import ChildAPI from '../../api/child';
+import { ChildForm } from './child-form';
+
+interface ChildModalProps {
+ child?: Child;
+ isOpen: boolean;
+ toggleModal: () => void;
+}
+
+/**
+ * A modal for creating or editing a child.
+ */
+export const ChildModal: React.FC = ({ child, isOpen, toggleModal }) => {
+ const { t } = useTranslation('public');
+ const [data, setData] = useState(child);
+ console.log(child, data);
+
+ /**
+ * Save the child to the API
+ */
+ const handleSaveChild = async (): Promise => {
+ try {
+ if (child?.id) {
+ await ChildAPI.update(data);
+ } else {
+ await ChildAPI.create(data);
+ }
+ toggleModal();
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ /**
+ * Check if the form is valid to save the child
+ */
+ const isPreventedSaveChild = (): boolean => {
+ return !data?.first_name || !data?.last_name;
+ };
+
+ /**
+ * Handle the change of a child form field
+ */
+ const handleChildChanged = (field: string, value: string | TDateISODate): void => {
+ setData({
+ ...data,
+ [field]: value
+ });
+ };
+
+ return (
+
+
+
+ );
+};
diff --git a/app/frontend/src/javascript/components/family-account/children-list.tsx b/app/frontend/src/javascript/components/family-account/children-list.tsx
new file mode 100644
index 000000000..c737e2d98
--- /dev/null
+++ b/app/frontend/src/javascript/components/family-account/children-list.tsx
@@ -0,0 +1,86 @@
+import React, { useState, useEffect } from 'react';
+import { react2angular } from 'react2angular';
+import { Child } from '../../models/child';
+// import { ChildListItem } from './child-list-item';
+import ChildAPI from '../../api/child';
+import { User } from '../../models/user';
+import { useTranslation } from 'react-i18next';
+import { Loader } from '../base/loader';
+import { IApplication } from '../../models/application';
+import { ChildModal } from './child-modal';
+import { ChildItem } from './child-item';
+import { FabButton } from '../base/fab-button';
+
+declare const Application: IApplication;
+
+interface ChildrenListProps {
+ currentUser: User;
+}
+
+/**
+ * A list of children belonging to the current user.
+ */
+export const ChildrenList: React.FC = ({ currentUser }) => {
+ const { t } = useTranslation('public');
+
+ const [children, setChildren] = useState>([]);
+ const [isOpenChildModal, setIsOpenChildModal] = useState(false);
+ const [child, setChild] = useState();
+
+ useEffect(() => {
+ ChildAPI.index({ user_id: currentUser.id }).then(setChildren);
+ }, [currentUser]);
+
+ /**
+ * Open the add child modal
+ */
+ const addChild = () => {
+ setIsOpenChildModal(true);
+ setChild({ user_id: currentUser.id } as Child);
+ };
+
+ /**
+ * Open the edit child modal
+ */
+ const editChild = (child: Child) => {
+ setIsOpenChildModal(true);
+ setChild(child);
+ };
+
+ /**
+ * Delete a child
+ */
+ const deleteChild = (child: Child) => {
+ ChildAPI.destroy(child.id).then(() => {
+ setChildren(children.filter(c => c.id !== child.id));
+ });
+ };
+
+ return (
+
+
+ {t('app.public.children_list.heading')}
+
+ {t('app.public.children_list.add_child')}
+
+
+
+
+ {children.map(child => (
+
+ ))}
+
+ setIsOpenChildModal(false)} />
+
+ );
+};
+
+const ChildrenListWrapper: React.FC = (props) => {
+ return (
+
+
+
+ );
+};
+
+Application.Components.component('childrenList', react2angular(ChildrenListWrapper, ['currentUser']));
diff --git a/app/frontend/src/javascript/controllers/children.js b/app/frontend/src/javascript/controllers/children.js
new file mode 100644
index 000000000..1fc180294
--- /dev/null
+++ b/app/frontend/src/javascript/controllers/children.js
@@ -0,0 +1,23 @@
+'use strict';
+
+Application.Controllers.controller('ChildrenController', ['$scope', 'memberPromise', 'growl',
+ function ($scope, memberPromise, growl) {
+ // Current user's profile
+ $scope.user = memberPromise;
+
+ /**
+ * Callback used to display a error message
+ */
+ $scope.onError = function (message) {
+ console.error(message);
+ growl.error(message);
+ };
+
+ /**
+ * Callback used to display a success message
+ */
+ $scope.onSuccess = function (message) {
+ growl.success(message);
+ };
+ }
+]);
diff --git a/app/frontend/src/javascript/models/child.ts b/app/frontend/src/javascript/models/child.ts
new file mode 100644
index 000000000..cea24bf1d
--- /dev/null
+++ b/app/frontend/src/javascript/models/child.ts
@@ -0,0 +1,16 @@
+import { TDateISODate } from '../typings/date-iso';
+import { ApiFilter } from './api';
+
+export interface ChildIndexFilter extends ApiFilter {
+ user_id: number,
+}
+
+export interface Child {
+ id?: number,
+ last_name: string,
+ first_name: string,
+ email?: string,
+ phone?: string,
+ birthday: TDateISODate,
+ user_id: number
+}
diff --git a/app/frontend/src/javascript/router.js b/app/frontend/src/javascript/router.js
index 26ce00a93..1339e7eb4 100644
--- a/app/frontend/src/javascript/router.js
+++ b/app/frontend/src/javascript/router.js
@@ -28,9 +28,9 @@ angular.module('application.router', ['ui.router'])
logoBlackFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'logo-black-file' }).$promise; }],
sharedTranslations: ['Translations', function (Translations) { return Translations.query(['app.shared', 'app.public.common']).$promise; }],
modulesPromise: ['Setting', function (Setting) { return Setting.query({ names: "['machines_module', 'spaces_module', 'plans_module', 'invoicing_module', 'wallet_module', 'statistics_module', 'trainings_module', 'public_agenda_module', 'store_module']" }).$promise; }],
- settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['public_registrations', 'store_hidden']" }).$promise; }]
+ settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['public_registrations', 'store_hidden', 'family_account']" }).$promise; }]
},
- onEnter: ['$rootScope', 'logoFile', 'logoBlackFile', 'modulesPromise', 'CSRF', function ($rootScope, logoFile, logoBlackFile, modulesPromise, CSRF) {
+ onEnter: ['$rootScope', 'logoFile', 'logoBlackFile', 'modulesPromise', 'settingsPromise', 'CSRF', function ($rootScope, logoFile, logoBlackFile, modulesPromise, settingsPromise, CSRF) {
// Retrieve Anti-CSRF tokens from cookies
CSRF.setMetaTags();
// Application logo
@@ -47,6 +47,9 @@ angular.module('application.router', ['ui.router'])
publicAgenda: (modulesPromise.public_agenda_module === 'true'),
statistics: (modulesPromise.statistics_module === 'true')
};
+ $rootScope.settings = {
+ familyAccount: (settingsPromise.family_account === 'true')
+ };
}]
})
.state('app.public', {
@@ -151,6 +154,15 @@ angular.module('application.router', ['ui.router'])
}
}
})
+ .state('app.logged.dashboard.children', {
+ url: '/children',
+ views: {
+ 'main@': {
+ templateUrl: '/dashboard/children.html',
+ controller: 'ChildrenController'
+ }
+ }
+ })
.state('app.logged.dashboard.settings', {
url: '/settings',
views: {
diff --git a/app/frontend/templates/dashboard/children.html b/app/frontend/templates/dashboard/children.html
new file mode 100644
index 000000000..8f5c2c17d
--- /dev/null
+++ b/app/frontend/templates/dashboard/children.html
@@ -0,0 +1,11 @@
+
diff --git a/app/frontend/templates/dashboard/nav.html b/app/frontend/templates/dashboard/nav.html
index 8da97d01a..f0dc4bd10 100644
--- a/app/frontend/templates/dashboard/nav.html
+++ b/app/frontend/templates/dashboard/nav.html
@@ -11,6 +11,7 @@
{{ 'app.public.common.dashboard' }}
{{ 'app.public.common.my_profile' }}
+ {{ 'app.public.common.my_children' }}
{{ 'app.public.common.my_settings' }}
{{ 'app.public.common.my_supporting_documents_files' }}
{{ 'app.public.common.my_projects' }}
diff --git a/app/models/child.rb b/app/models/child.rb
new file mode 100644
index 000000000..49ebdaf9e
--- /dev/null
+++ b/app/models/child.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+# Child is a modal for a child of a user
+class Child < ApplicationRecord
+ belongs_to :user
+
+ validates :first_name, presence: true
+ validates :last_name, presence: true
+ validate :validate_age
+
+ def validate_age
+ errors.add(:birthday, 'You should be over 18 years old.') if birthday.blank? && birthday < 18.years.ago
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 8568a444a..e8a31f6d0 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -53,6 +53,9 @@ class User < ApplicationRecord
has_many :notifications, as: :receiver, dependent: :destroy
has_many :notification_preferences, dependent: :destroy
+ has_many :children, dependent: :destroy
+ accepts_nested_attributes_for :children, allow_destroy: true
+
# fix for create admin user
before_save do
email&.downcase!
diff --git a/app/policies/child_policy.rb b/app/policies/child_policy.rb
new file mode 100644
index 000000000..3bfe45032
--- /dev/null
+++ b/app/policies/child_policy.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+# Check the access policies for API::ChildrenController
+class ChildPolicy < ApplicationPolicy
+ # Defines the scope of the children index, depending on the current user
+ class Scope < Scope
+ def resolve
+ scope.where(user_id: user.id)
+ end
+ end
+
+ def index?
+ !user.organization?
+ end
+
+ def create?
+ !user.organization? && user.id == record.user_id
+ end
+
+ def show?
+ user.id == record.user_id
+ end
+
+ def update?
+ user.id == record.user_id
+ end
+
+ def destroy?
+ user.id == record.user_id
+ end
+end
diff --git a/app/views/api/children/_child.json.jbuilder b/app/views/api/children/_child.json.jbuilder
new file mode 100644
index 000000000..d80836a4e
--- /dev/null
+++ b/app/views/api/children/_child.json.jbuilder
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+json.extract! child, :id, :first_name, :last_name, :email, :birthday, :phone, :user_id
diff --git a/app/views/api/children/create.json.jbuilder b/app/views/api/children/create.json.jbuilder
new file mode 100644
index 000000000..c14c4cc71
--- /dev/null
+++ b/app/views/api/children/create.json.jbuilder
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+json.partial! 'child', child: @child
diff --git a/app/views/api/children/index.json.jbuilder b/app/views/api/children/index.json.jbuilder
new file mode 100644
index 000000000..b876f1116
--- /dev/null
+++ b/app/views/api/children/index.json.jbuilder
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+json.array! @children do |child|
+ json.partial! 'child', child: child
+end
diff --git a/app/views/api/children/update.json.jbuilder b/app/views/api/children/update.json.jbuilder
new file mode 100644
index 000000000..c97cd198b
--- /dev/null
+++ b/app/views/api/children/update.json.jbuilder
@@ -0,0 +1,3 @@
+# forzen_string_literal: true
+
+json.partial! 'child', child: @child
diff --git a/config/locales/app.public.en.yml b/config/locales/app.public.en.yml
index 7dfc61f69..4428d8470 100644
--- a/config/locales/app.public.en.yml
+++ b/config/locales/app.public.en.yml
@@ -14,6 +14,7 @@ en:
#dashboard sections
dashboard: "Dashboard"
my_profile: "My Profile"
+ my_children: "My Children"
my_settings: "My Settings"
my_supporting_documents_files: "My supporting documents"
my_projects: "My Projects"
@@ -475,6 +476,8 @@ en:
member_select:
select_a_member: "Select a member"
start_typing: "Start typing..."
+ children_list:
+ heading: "My children"
tour:
conclusion:
title: "Thank you for your attention"
diff --git a/config/locales/app.public.fr.yml b/config/locales/app.public.fr.yml
index 09a07a116..aa33053e7 100644
--- a/config/locales/app.public.fr.yml
+++ b/config/locales/app.public.fr.yml
@@ -14,6 +14,7 @@ fr:
#dashboard sections
dashboard: "Tableau de bord"
my_profile: "Mon profil"
+ my_children: "Mes enfants"
my_settings: "Mes paramètres"
my_supporting_documents_files: "Mes justificatifs"
my_projects: "Mes projets"
@@ -475,6 +476,21 @@ fr:
member_select:
select_a_member: "Sélectionnez un membre"
start_typing: "Commencez à écrire..."
+ children_list:
+ heading: "Mes enfants"
+ add_child: "Ajouter un enfant"
+ child_modal:
+ edit_child: "Modifier un enfant"
+ new_child: "Ajouter un enfant"
+ save: "Enregistrer"
+ child_form:
+ child_form_info: "Notez que vous ne pouvez ajouter que vos enfants de moins de 18 ans. Des pièces justificatives sont demandés par votre administrateur, elles lui seront utiles pour valider le compte de votre enfant et ainsi autoriser la réservation d'événements."
+ first_name: "Prénom"
+ last_name: "Nom"
+ child_item:
+ first_name: "Prénom de l'enfant"
+ last_name: "Nom de l'enfant"
+ birthday: "Date de naissance"
tour:
conclusion:
title: "Merci de votre attention"
diff --git a/config/routes.rb b/config/routes.rb
index 4f9872af2..b7fb651af 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -183,6 +183,8 @@ Rails.application.routes.draw do
get 'withdrawal_instructions', on: :member
end
+ resources :children, only: %i[index show create update destroy]
+
# for admin
resources :trainings do
get :availabilities, on: :member
diff --git a/db/migrate/20230331132506_create_children.rb b/db/migrate/20230331132506_create_children.rb
new file mode 100644
index 000000000..45f536fea
--- /dev/null
+++ b/db/migrate/20230331132506_create_children.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+# Child is a modal for a child of a user
+class CreateChildren < ActiveRecord::Migration[5.2]
+ def change
+ create_table :children do |t|
+ t.belongs_to :user, foreign_key: true
+ t.string :first_name
+ t.string :last_name
+ t.date :birthday
+ t.string :phone
+ t.string :email
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index f167f4316..0c08d852f 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[6.1].define(version: 2023_03_15_095054) do
+ActiveRecord::Schema[6.1].define(version: 2023_03_31_132506) do
# These are extensions that must be enabled in order to support this database
enable_extension "fuzzystrmatch"
@@ -263,6 +263,18 @@ ActiveRecord::Schema[6.1].define(version: 2023_03_15_095054) do
t.index ["slug"], name: "index_categories_on_slug", unique: true
end
+ create_table "children", force: :cascade do |t|
+ t.bigint "user_id"
+ t.string "first_name"
+ t.string "last_name"
+ t.date "birthday"
+ t.string "phone"
+ t.string "email"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["user_id"], name: "index_children_on_user_id"
+ end
+
create_table "components", id: :serial, force: :cascade do |t|
t.string "name", null: false
end
@@ -1332,8 +1344,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_03_15_095054) do
t.boolean "is_allow_newsletter"
t.inet "current_sign_in_ip"
t.inet "last_sign_in_ip"
- t.string "mapped_from_sso"
t.datetime "validated_at"
+ t.string "mapped_from_sso"
t.index ["auth_token"], name: "index_users_on_auth_token"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email", unique: true
@@ -1409,6 +1421,7 @@ ActiveRecord::Schema[6.1].define(version: 2023_03_15_095054) do
add_foreign_key "cart_item_reservations", "plans"
add_foreign_key "cart_item_subscriptions", "invoicing_profiles", column: "customer_profile_id"
add_foreign_key "cart_item_subscriptions", "plans"
+ add_foreign_key "children", "users"
add_foreign_key "event_price_categories", "events"
add_foreign_key "event_price_categories", "price_categories"
add_foreign_key "events", "categories"
From dc4151cfb07a95bf5aa84c78ff7a8698f5a7b6f7 Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Tue, 4 Apr 2023 18:09:17 +0200
Subject: [PATCH 20/45] (wip) create/update user children
---
app/controllers/api/children_controller.rb | 2 +-
.../components/family-account/child-form.tsx | 38 +++++++++++++++++--
.../components/family-account/child-item.tsx | 3 +-
.../components/family-account/child-modal.tsx | 28 +++++++-------
.../family-account/children-list.tsx | 18 ++++++---
.../javascript/components/form/form-input.tsx | 8 ++--
app/models/child.rb | 4 +-
config/locales/app.public.en.yml | 16 ++++++++
config/locales/app.public.fr.yml | 5 ++-
config/locales/en.yml | 1 +
config/locales/fr.yml | 1 +
11 files changed, 94 insertions(+), 30 deletions(-)
diff --git a/app/controllers/api/children_controller.rb b/app/controllers/api/children_controller.rb
index 7c5a1003a..f923f2bb3 100644
--- a/app/controllers/api/children_controller.rb
+++ b/app/controllers/api/children_controller.rb
@@ -2,7 +2,7 @@
# API Controller for resources of type Child
# Children are used to provide a way to manage multiple users in the family account
-class API::ChildrenController < API::ApiController
+class API::ChildrenController < API::APIController
before_action :authenticate_user!
before_action :set_child, only: %i[show update destroy]
diff --git a/app/frontend/src/javascript/components/family-account/child-form.tsx b/app/frontend/src/javascript/components/family-account/child-form.tsx
index fee281548..2a70e4b9b 100644
--- a/app/frontend/src/javascript/components/family-account/child-form.tsx
+++ b/app/frontend/src/javascript/components/family-account/child-form.tsx
@@ -1,22 +1,25 @@
import React from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
+import moment from 'moment';
import { Child } from '../../models/child';
import { TDateISODate } from '../../typings/date-iso';
import { FormInput } from '../form/form-input';
+import { FabButton } from '../base/fab-button';
interface ChildFormProps {
child: Child;
onChange: (field: string, value: string | TDateISODate) => void;
+ onSubmit: (data: Child) => void;
}
/**
* A form for creating or editing a child.
*/
-export const ChildForm: React.FC = ({ child, onChange }) => {
+export const ChildForm: React.FC = ({ child, onChange, onSubmit }) => {
const { t } = useTranslation('public');
- const { register, formState } = useForm({
+ const { register, formState, handleSubmit } = useForm({
defaultValues: child
});
@@ -32,7 +35,7 @@ export const ChildForm: React.FC = ({ child, onChange }) => {
{t('app.public.child_form.child_form_info')}
-
);
diff --git a/app/frontend/src/javascript/components/family-account/child-item.tsx b/app/frontend/src/javascript/components/family-account/child-item.tsx
index 747d8212c..44ca25aea 100644
--- a/app/frontend/src/javascript/components/family-account/child-item.tsx
+++ b/app/frontend/src/javascript/components/family-account/child-item.tsx
@@ -2,6 +2,7 @@ import React from 'react';
import { Child } from '../../models/child';
import { useTranslation } from 'react-i18next';
import { FabButton } from '../base/fab-button';
+import FormatLib from '../../lib/format';
interface ChildItemProps {
child: Child;
@@ -27,7 +28,7 @@ export const ChildItem: React.FC = ({ child, onEdit, onDelete })
{t('app.public.child_item.birthday')}
-
{child.birthday}
+
{FormatLib.date(child.birthday)}
} onClick={() => onEdit(child)} className="edit-button" />
diff --git a/app/frontend/src/javascript/components/family-account/child-modal.tsx b/app/frontend/src/javascript/components/family-account/child-modal.tsx
index 03f1486c4..baa9af408 100644
--- a/app/frontend/src/javascript/components/family-account/child-modal.tsx
+++ b/app/frontend/src/javascript/components/family-account/child-modal.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FabModal, ModalSize } from '../base/fab-modal';
import { Child } from '../../models/child';
@@ -11,15 +11,20 @@ interface ChildModalProps {
child?: Child;
isOpen: boolean;
toggleModal: () => void;
+ onSuccess: (child: Child) => void;
+ onError: (error: string) => void;
}
/**
* A modal for creating or editing a child.
*/
-export const ChildModal: React.FC
= ({ child, isOpen, toggleModal }) => {
+export const ChildModal: React.FC = ({ child, isOpen, toggleModal, onSuccess, onError }) => {
const { t } = useTranslation('public');
const [data, setData] = useState(child);
- console.log(child, data);
+
+ useEffect(() => {
+ setData(child);
+ }, [child]);
/**
* Save the child to the API
@@ -32,18 +37,12 @@ export const ChildModal: React.FC = ({ child, isOpen, toggleMod
await ChildAPI.create(data);
}
toggleModal();
+ onSuccess(data);
} catch (error) {
- console.error(error);
+ onError(error);
}
};
- /**
- * Check if the form is valid to save the child
- */
- const isPreventedSaveChild = (): boolean => {
- return !data?.first_name || !data?.last_name;
- };
-
/**
* Handle the change of a child form field
*/
@@ -60,10 +59,9 @@ export const ChildModal: React.FC = ({ child, isOpen, toggleMod
isOpen={isOpen}
toggleModal={toggleModal}
closeButton={true}
- confirmButton={t('app.public.child_modal.save')}
- onConfirm={handleSaveChild}
- preventConfirm={isPreventedSaveChild()}>
-
+ confirmButton={false}
+ onConfirm={handleSaveChild} >
+
);
};
diff --git a/app/frontend/src/javascript/components/family-account/children-list.tsx b/app/frontend/src/javascript/components/family-account/children-list.tsx
index c737e2d98..287f69802 100644
--- a/app/frontend/src/javascript/components/family-account/children-list.tsx
+++ b/app/frontend/src/javascript/components/family-account/children-list.tsx
@@ -1,7 +1,6 @@
import React, { useState, useEffect } from 'react';
import { react2angular } from 'react2angular';
import { Child } from '../../models/child';
-// import { ChildListItem } from './child-list-item';
import ChildAPI from '../../api/child';
import { User } from '../../models/user';
import { useTranslation } from 'react-i18next';
@@ -15,12 +14,14 @@ declare const Application: IApplication;
interface ChildrenListProps {
currentUser: User;
+ onSuccess: (error: string) => void;
+ onError: (error: string) => void;
}
/**
* A list of children belonging to the current user.
*/
-export const ChildrenList: React.FC = ({ currentUser }) => {
+export const ChildrenList: React.FC = ({ currentUser, onError }) => {
const { t } = useTranslation('public');
const [children, setChildren] = useState>([]);
@@ -52,10 +53,17 @@ export const ChildrenList: React.FC = ({ currentUser }) => {
*/
const deleteChild = (child: Child) => {
ChildAPI.destroy(child.id).then(() => {
- setChildren(children.filter(c => c.id !== child.id));
+ ChildAPI.index({ user_id: currentUser.id }).then(setChildren);
});
};
+ /**
+ * Handle save child success from the API
+ */
+ const handleSaveChildSuccess = () => {
+ ChildAPI.index({ user_id: currentUser.id }).then(setChildren);
+ };
+
return (
@@ -70,7 +78,7 @@ export const ChildrenList: React.FC = ({ currentUser }) => {
))}
- setIsOpenChildModal(false)} />
+ setIsOpenChildModal(false)} onSuccess={handleSaveChildSuccess} onError={onError} />
);
};
@@ -83,4 +91,4 @@ const ChildrenListWrapper: React.FC = (props) => {
);
};
-Application.Components.component('childrenList', react2angular(ChildrenListWrapper, ['currentUser']));
+Application.Components.component('childrenList', react2angular(ChildrenListWrapper, ['currentUser', 'onSuccess', 'onError']));
diff --git a/app/frontend/src/javascript/components/form/form-input.tsx b/app/frontend/src/javascript/components/form/form-input.tsx
index d4a4331e9..b19d42ccc 100644
--- a/app/frontend/src/javascript/components/form/form-input.tsx
+++ b/app/frontend/src/javascript/components/form/form-input.tsx
@@ -22,13 +22,14 @@ type FormInputProps = FormComponent & Ab
onChange?: (event: React.ChangeEvent) => void,
nullable?: boolean,
ariaLabel?: string,
- maxLength?: number
+ maxLength?: number,
+ max?: number | string,
}
/**
* This component is a template for an input component to use within React Hook Form
*/
-export const FormInput = ({ id, register, label, tooltip, defaultValue, icon, className, rules, disabled, type, addOn, addOnAction, addOnClassName, addOnAriaLabel, placeholder, error, warning, formState, step, onChange, debounce, accept, nullable = false, ariaLabel, maxLength }: FormInputProps) => {
+export const FormInput = ({ id, register, label, tooltip, defaultValue, icon, className, rules, disabled, type, addOn, addOnAction, addOnClassName, addOnAriaLabel, placeholder, error, warning, formState, step, onChange, debounce, accept, nullable = false, ariaLabel, maxLength, max }: FormInputProps) => {
const [characterCount, setCharacterCount] = useState(0);
/**
@@ -100,7 +101,8 @@ export const FormInput = ({ id, re
disabled={typeof disabled === 'function' ? disabled(id) : disabled}
placeholder={placeholder}
accept={accept}
- maxLength={maxLength} />
+ maxLength={maxLength}
+ max={max} />
{(type === 'file' && placeholder) && {placeholder} }
{maxLength && {characterCount} / {maxLength} }
{addOn && addOnAction && {addOn} }
diff --git a/app/models/child.rb b/app/models/child.rb
index 49ebdaf9e..93332bde8 100644
--- a/app/models/child.rb
+++ b/app/models/child.rb
@@ -6,9 +6,11 @@ class Child < ApplicationRecord
validates :first_name, presence: true
validates :last_name, presence: true
+ validates :email, presence: true, format: { with: Devise.email_regexp }
validate :validate_age
+ # birthday should less than 18 years ago
def validate_age
- errors.add(:birthday, 'You should be over 18 years old.') if birthday.blank? && birthday < 18.years.ago
+ errors.add(:birthday, I18n.t('.errors.messages.birthday_less_than_18_years_ago')) if birthday.blank? || birthday > 18.years.ago
end
end
diff --git a/config/locales/app.public.en.yml b/config/locales/app.public.en.yml
index 4428d8470..12e8c9785 100644
--- a/config/locales/app.public.en.yml
+++ b/config/locales/app.public.en.yml
@@ -478,6 +478,22 @@ en:
start_typing: "Start typing..."
children_list:
heading: "My children"
+ add_child: "Add a child"
+ child_modal:
+ edit_child: "Edit child"
+ new_child: "New child"
+ child_form:
+ child_form_info: "Note that you can only add your children under 18 years old. Supporting documents are requested by your administrator, they will be useful to validate your child's account and authorize the reservation of events."
+ first_name: "First name"
+ last_name: "Last name"
+ birthday: "Birthday"
+ email: "Email"
+ phone: "Phone"
+ save: "Save"
+ child_item:
+ first_name: "First name of the child"
+ last_name: "Last name of the child"
+ birthday: "Birthday"
tour:
conclusion:
title: "Thank you for your attention"
diff --git a/config/locales/app.public.fr.yml b/config/locales/app.public.fr.yml
index aa33053e7..3ef09b458 100644
--- a/config/locales/app.public.fr.yml
+++ b/config/locales/app.public.fr.yml
@@ -482,11 +482,14 @@ fr:
child_modal:
edit_child: "Modifier un enfant"
new_child: "Ajouter un enfant"
- save: "Enregistrer"
child_form:
child_form_info: "Notez que vous ne pouvez ajouter que vos enfants de moins de 18 ans. Des pièces justificatives sont demandés par votre administrateur, elles lui seront utiles pour valider le compte de votre enfant et ainsi autoriser la réservation d'événements."
first_name: "Prénom"
last_name: "Nom"
+ birthday: "Date de naissance"
+ email: "Courriel"
+ phone: "Téléphone"
+ save: "Enregistrer"
child_item:
first_name: "Prénom de l'enfant"
last_name: "Nom de l'enfant"
diff --git a/config/locales/en.yml b/config/locales/en.yml
index ef51539d0..f3b148b89 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -49,6 +49,7 @@ en:
gateway_amount_too_large: "Payments above %{AMOUNT} are not supported. Please order directly at the reception."
product_in_use: "This product have already been ordered"
slug_already_used: "is already used"
+ birthday_less_than_18_years_ago: "Birthday must be under 18 years ago"
coupon:
code_format_error: "only caps letters, numbers, and dashes are allowed"
apipie:
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 5331975c3..092b677cd 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -49,6 +49,7 @@ fr:
gateway_amount_too_large: "Les paiements supérieurs à %{AMOUNT} ne sont pas pris en charge. Merci de passer commande directement à l'accueil."
product_in_use: "Ce produit a déjà été commandé"
slug_already_used: "est déjà utilisée"
+ birthday_less_than_18_years_ago: "l'age devez avoir au moins 18 ans."
coupon:
code_format_error: "seules les majuscules, chiffres et tirets sont autorisés"
apipie:
From 8c4602e535d866404b360b9b154ec94ef34208fe Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Tue, 9 May 2023 18:54:16 +0200
Subject: [PATCH 21/45] (wip) event reservation naminative
---
Gemfile.lock | 3 +
app/controllers/api/events_controller.rb | 2 +-
.../components/events/event-form.tsx | 5 +
.../src/javascript/controllers/events.js.erb | 51 ++++-
app/frontend/src/javascript/models/event.ts | 3 +-
app/frontend/templates/events/show.html | 16 +-
app/models/booking_user.rb | 9 +
app/models/reservation.rb | 3 +
app/views/api/events/_event.json.jbuilder | 2 +-
config/locales/app.admin.en.yml | 2 +
config/locales/app.admin.fr.yml | 2 +
...9121907_add_booking_nominative_to_event.rb | 8 +
.../20230509161557_create_booking_users.rb | 15 ++
db/structure.sql | 210 +++++++++++++++---
.../components/events/event-form.test.tsx | 1 +
15 files changed, 291 insertions(+), 41 deletions(-)
create mode 100644 app/models/booking_user.rb
create mode 100644 db/migrate/20230509121907_add_booking_nominative_to_event.rb
create mode 100644 db/migrate/20230509161557_create_booking_users.rb
diff --git a/Gemfile.lock b/Gemfile.lock
index 4606b128b..5cb4eaf82 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -269,6 +269,8 @@ GEM
net-smtp (0.3.3)
net-protocol
nio4r (2.5.8)
+ nokogiri (1.14.3-x86_64-darwin)
+ racc (~> 1.4)
nokogiri (1.14.3-x86_64-linux)
racc (~> 1.4)
oauth2 (1.4.4)
@@ -524,6 +526,7 @@ GEM
zeitwerk (2.6.7)
PLATFORMS
+ x86_64-darwin-20
x86_64-linux
DEPENDENCIES
diff --git a/app/controllers/api/events_controller.rb b/app/controllers/api/events_controller.rb
index ce4b42bd9..1b8abdc77 100644
--- a/app/controllers/api/events_controller.rb
+++ b/app/controllers/api/events_controller.rb
@@ -96,7 +96,7 @@ class API::EventsController < API::APIController
# handle general properties
event_preparams = params.required(:event).permit(:title, :description, :start_date, :start_time, :end_date, :end_time,
:amount, :nb_total_places, :availability_id, :all_day, :recurrence,
- :recurrence_end_at, :category_id, :event_theme_ids, :age_range_id,
+ :recurrence_end_at, :category_id, :event_theme_ids, :age_range_id, :booking_nominative,
event_theme_ids: [],
event_image_attributes: %i[id attachment],
event_files_attributes: %i[id attachment _destroy],
diff --git a/app/frontend/src/javascript/components/events/event-form.tsx b/app/frontend/src/javascript/components/events/event-form.tsx
index e8a89c29f..033dff894 100644
--- a/app/frontend/src/javascript/components/events/event-form.tsx
+++ b/app/frontend/src/javascript/components/events/event-form.tsx
@@ -290,6 +290,11 @@ export const EventForm: React.FC = ({ action, event, onError, on
label={t('app.admin.event_form.seats_available')}
type="number"
tooltip={t('app.admin.event_form.seats_help')} />
+
u.booked_id === $scope.ctrl.member.id && u.booked_type === 'User')) {
+ return true;
+ }
+ }
+ return false;
+ };
+
/**
* Callback to call when the number of tickets to book changes in the current booking
*/
- $scope.changeNbPlaces = function () {
+ $scope.changeNbPlaces = function (priceType) {
// compute the total remaining places
let remain = $scope.event.nb_free_places - $scope.reserve.nbReservePlaces;
for (let ticket in $scope.reserve.tickets) {
@@ -247,6 +260,22 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
}
}
+ const nbBookingUsers = $scope.reserve.bookingUsers[priceType].length;
+ const nbReservePlaces = priceType === 'normal' ? $scope.reserve.nbReservePlaces : $scope.reserve.tickets[priceType];
+ if (nbReservePlaces > nbBookingUsers) {
+ _.times(nbReservePlaces - nbBookingUsers, () => {
+ if (!hasMemberInBookingUsers()) {
+ $scope.reserve.bookingUsers[priceType].push({ event_price_category_id: priceType === 'normal' ? null : priceType, booked_id: $scope.ctrl.member.id, booked_type: 'User', name: $scope.ctrl.member.name });
+ } else {
+ $scope.reserve.bookingUsers[priceType].push({ event_price_category_id: priceType === 'normal' ? null : priceType });
+ }
+ });
+ } else {
+ _.times(nbBookingUsers - nbReservePlaces, () => {
+ $scope.reserve.bookingUsers[priceType].pop();
+ });
+ }
+
// recompute the total price
return $scope.computeEventAmount();
};
@@ -638,7 +667,8 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
reservable_type: 'Event',
slots_reservations_attributes: [],
nb_reserve_places: reserve.nbReservePlaces,
- tickets_attributes: []
+ tickets_attributes: [],
+ booking_users_attributes: []
};
reservation.slots_reservations_attributes.push({
@@ -656,6 +686,15 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
}
}
+ if (event.booking_nominative) {
+ for (const key of Object.keys($scope.reserve.bookingUsers)) {
+ for (const user of $scope.reserve.bookingUsers[key]) {
+ reservation.booking_users_attributes.push(user);
+ }
+ }
+ console.log(reservation);
+ }
+
return { reservation };
};
@@ -688,11 +727,15 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
tickets: {},
toReserve: false,
amountTotal: 0,
- totalSeats: 0
+ totalSeats: 0,
+ bookingUsers: {
+ normal: [],
+ },
};
for (let evt_px_cat of Array.from($scope.event.event_price_categories_attributes)) {
$scope.reserve.nbPlaces[evt_px_cat.id] = __range__(0, $scope.event.nb_free_places, true);
+ $scope.reserve.bookingUsers[evt_px_cat.id] = [];
$scope.reserve.tickets[evt_px_cat.id] = 0;
}
diff --git a/app/frontend/src/javascript/models/event.ts b/app/frontend/src/javascript/models/event.ts
index 5cca1d9e8..52bdc1496 100644
--- a/app/frontend/src/javascript/models/event.ts
+++ b/app/frontend/src/javascript/models/event.ts
@@ -63,7 +63,8 @@ export interface Event {
}>,
recurrence: RecurrenceOption,
recurrence_end_at: Date,
- advanced_accounting_attributes?: AdvancedAccounting
+ advanced_accounting_attributes?: AdvancedAccounting,
+ booking_nominative: boolean,
}
export interface EventDecoration {
diff --git a/app/frontend/templates/events/show.html b/app/frontend/templates/events/show.html
index ec362a3d5..c8d86cd02 100644
--- a/app/frontend/templates/events/show.html
+++ b/app/frontend/templates/events/show.html
@@ -116,16 +116,28 @@
{{ 'app.public.events_show.full_price_' | translate }} {{event.amount | currency}}
-
+
{{ 'app.public.events_show.ticket' | translate:{NUMBER:reserve.nbReservePlaces} }}
+
{{price.category.name}} : {{price.amount | currency}}
-
+
{{ 'app.public.events_show.ticket' | translate:{NUMBER:reserve.tickets[price.id]} }}
+
diff --git a/app/models/booking_user.rb b/app/models/booking_user.rb
new file mode 100644
index 000000000..78c6fe6b5
--- /dev/null
+++ b/app/models/booking_user.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+# BookingUser is a class for save the booking info of reservation
+# booked can be a User or a Child (polymorphic)
+class BookingUser < ApplicationRecord
+ belongs_to :reservation
+ belongs_to :booked, polymorphic: true
+ belongs_to :event_price_category
+end
diff --git a/app/models/reservation.rb b/app/models/reservation.rb
index d0472354b..9b9d0ff4f 100644
--- a/app/models/reservation.rb
+++ b/app/models/reservation.rb
@@ -23,6 +23,9 @@ class Reservation < ApplicationRecord
has_many :prepaid_pack_reservations, dependent: :destroy
+ has_many :booking_users, dependent: :destroy
+ accepts_nested_attributes_for :booking_users, allow_destroy: true
+
validates :reservable_id, :reservable_type, presence: true
validate :machine_not_already_reserved, if: -> { reservable.is_a?(Machine) }
validate :training_not_fully_reserved, if: -> { reservable.is_a?(Training) }
diff --git a/app/views/api/events/_event.json.jbuilder b/app/views/api/events/_event.json.jbuilder
index 09a19e0c0..11be0748e 100644
--- a/app/views/api/events/_event.json.jbuilder
+++ b/app/views/api/events/_event.json.jbuilder
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-json.extract! event, :id, :title, :description
+json.extract! event, :id, :title, :description, :booking_nominative
if event.event_image
json.event_image_attributes do
json.id event.event_image.id
diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml
index b4ac6a9d9..a8ccdbafc 100644
--- a/config/locales/app.admin.en.yml
+++ b/config/locales/app.admin.en.yml
@@ -139,6 +139,8 @@ en:
event_themes: "Event themes"
age_range: "Age range"
add_price: "Add a price"
+ booking_nominative: "Nominative booking"
+ booking_nominative_help: "If you check this option, the members will have to enter the names of the participants when booking."
save: "Save"
create_success: "The event was created successfully"
events_updated: "{COUNT, plural, =1{One event was} other{{COUNT} Events were}} successfully updated"
diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml
index f89ade8b7..67970617b 100644
--- a/config/locales/app.admin.fr.yml
+++ b/config/locales/app.admin.fr.yml
@@ -139,6 +139,8 @@ fr:
event_themes: "Thèmes de l'événement"
age_range: "Tranche d'âge"
add_price: "Ajouter un tarif"
+ booking_nominative: "Réservation nominative"
+ booking_nominative_help: "Si cette option est activée, les réservations seront nominatives. Les participants devront s'identifier pour réserver."
save: "Enregistrer"
create_success: "L'événement a bien été créé"
events_updated: "{COUNT, plural, one {}=1{Un événement à été} other{{COUNT} événements ont été}} mis à jour avec succès"
diff --git a/db/migrate/20230509121907_add_booking_nominative_to_event.rb b/db/migrate/20230509121907_add_booking_nominative_to_event.rb
new file mode 100644
index 000000000..1a709170d
--- /dev/null
+++ b/db/migrate/20230509121907_add_booking_nominative_to_event.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+# add booking_nominative to event
+class AddBookingNominativeToEvent < ActiveRecord::Migration[7.0]
+ def change
+ add_column :events, :booking_nominative, :boolean, default: false
+ end
+end
diff --git a/db/migrate/20230509161557_create_booking_users.rb b/db/migrate/20230509161557_create_booking_users.rb
new file mode 100644
index 000000000..f1ba65728
--- /dev/null
+++ b/db/migrate/20230509161557_create_booking_users.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+# create booking_users table
+class CreateBookingUsers < ActiveRecord::Migration[7.0]
+ def change
+ create_table :booking_users do |t|
+ t.string :name
+ t.belongs_to :reservation, foreign_key: true
+ t.references :booked, polymorphic: true
+ t.references :event_price_category, foreign_key: true
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/structure.sql b/db/structure.sql
index 0f648a913..cc238df4c 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -115,8 +115,8 @@ SET default_tablespace = '';
CREATE TABLE public.abuses (
id integer NOT NULL,
- signaled_id integer,
signaled_type character varying,
+ signaled_id integer,
first_name character varying,
last_name character varying,
email character varying,
@@ -236,8 +236,8 @@ CREATE TABLE public.addresses (
locality character varying,
country character varying,
postal_code character varying,
- placeable_id integer,
placeable_type character varying,
+ placeable_id integer,
created_at timestamp without time zone,
updated_at timestamp without time zone
);
@@ -346,8 +346,8 @@ CREATE TABLE public.ar_internal_metadata (
CREATE TABLE public.assets (
id integer NOT NULL,
- viewable_id integer,
viewable_type character varying,
+ viewable_id integer,
attachment character varying,
type character varying,
created_at timestamp without time zone,
@@ -520,6 +520,41 @@ CREATE SEQUENCE public.availability_tags_id_seq
ALTER SEQUENCE public.availability_tags_id_seq OWNED BY public.availability_tags.id;
+--
+-- Name: booking_users; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.booking_users (
+ id bigint NOT NULL,
+ name character varying,
+ reservation_id bigint,
+ booked_type character varying,
+ booked_id bigint,
+ event_price_category_id bigint,
+ created_at timestamp(6) without time zone NOT NULL,
+ updated_at timestamp(6) without time zone NOT NULL
+);
+
+
+--
+-- Name: booking_users_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public.booking_users_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+
+--
+-- Name: booking_users_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE public.booking_users_id_seq OWNED BY public.booking_users.id;
+
+
--
-- Name: cart_item_coupons; Type: TABLE; Schema: public; Owner: -
--
@@ -892,6 +927,42 @@ CREATE SEQUENCE public.chained_elements_id_seq
ALTER SEQUENCE public.chained_elements_id_seq OWNED BY public.chained_elements.id;
+--
+-- Name: children; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.children (
+ id bigint NOT NULL,
+ user_id bigint,
+ first_name character varying,
+ last_name character varying,
+ birthday date,
+ phone character varying,
+ email character varying,
+ created_at timestamp without time zone NOT NULL,
+ updated_at timestamp without time zone NOT NULL
+);
+
+
+--
+-- Name: children_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public.children_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+
+--
+-- Name: children_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE public.children_id_seq OWNED BY public.children.id;
+
+
--
-- Name: components; Type: TABLE; Schema: public; Owner: -
--
@@ -965,8 +1036,8 @@ ALTER SEQUENCE public.coupons_id_seq OWNED BY public.coupons.id;
CREATE TABLE public.credits (
id integer NOT NULL,
- creditable_id integer,
creditable_type character varying,
+ creditable_id integer,
plan_id integer,
hours integer,
created_at timestamp without time zone,
@@ -1136,7 +1207,8 @@ CREATE TABLE public.events (
recurrence_id integer,
age_range_id integer,
category_id integer,
- deleted_at timestamp without time zone
+ deleted_at timestamp without time zone,
+ booking_nominative boolean DEFAULT false
);
@@ -1762,15 +1834,15 @@ ALTER SEQUENCE public.notification_types_id_seq OWNED BY public.notification_typ
CREATE TABLE public.notifications (
id integer NOT NULL,
receiver_id integer,
- attached_object_id integer,
attached_object_type character varying,
+ attached_object_id integer,
notification_type_id integer,
is_read boolean DEFAULT false,
created_at timestamp without time zone,
updated_at timestamp without time zone,
receiver_type character varying,
is_send boolean DEFAULT false,
- meta_data jsonb DEFAULT '{}'::jsonb
+ meta_data jsonb DEFAULT '"{}"'::jsonb
);
@@ -2498,8 +2570,8 @@ CREATE TABLE public.prices (
id integer NOT NULL,
group_id integer,
plan_id integer,
- priceable_id integer,
priceable_type character varying,
+ priceable_id integer,
amount integer,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL,
@@ -2962,8 +3034,8 @@ CREATE TABLE public.reservations (
message text,
created_at timestamp without time zone,
updated_at timestamp without time zone,
- reservable_id integer,
reservable_type character varying,
+ reservable_id integer,
nb_reserve_places integer,
statistic_profile_id integer
);
@@ -2995,8 +3067,8 @@ ALTER SEQUENCE public.reservations_id_seq OWNED BY public.reservations.id;
CREATE TABLE public.roles (
id integer NOT NULL,
name character varying,
- resource_id integer,
resource_type character varying,
+ resource_id integer,
created_at timestamp without time zone,
updated_at timestamp without time zone
);
@@ -4102,8 +4174,8 @@ CREATE TABLE public.users (
is_allow_newsletter boolean,
current_sign_in_ip inet,
last_sign_in_ip inet,
- mapped_from_sso character varying,
- validated_at timestamp without time zone
+ validated_at timestamp without time zone,
+ mapped_from_sso character varying
);
@@ -4312,6 +4384,13 @@ ALTER TABLE ONLY public.availabilities ALTER COLUMN id SET DEFAULT nextval('publ
ALTER TABLE ONLY public.availability_tags ALTER COLUMN id SET DEFAULT nextval('public.availability_tags_id_seq'::regclass);
+--
+-- Name: booking_users id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.booking_users ALTER COLUMN id SET DEFAULT nextval('public.booking_users_id_seq'::regclass);
+
+
--
-- Name: cart_item_coupons id; Type: DEFAULT; Schema: public; Owner: -
--
@@ -4389,6 +4468,13 @@ ALTER TABLE ONLY public.categories ALTER COLUMN id SET DEFAULT nextval('public.c
ALTER TABLE ONLY public.chained_elements ALTER COLUMN id SET DEFAULT nextval('public.chained_elements_id_seq'::regclass);
+--
+-- Name: children id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.children ALTER COLUMN id SET DEFAULT nextval('public.children_id_seq'::regclass);
+
+
--
-- Name: components id; Type: DEFAULT; Schema: public; Owner: -
--
@@ -5150,6 +5236,14 @@ ALTER TABLE ONLY public.availability_tags
ADD CONSTRAINT availability_tags_pkey PRIMARY KEY (id);
+--
+-- Name: booking_users booking_users_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.booking_users
+ ADD CONSTRAINT booking_users_pkey PRIMARY KEY (id);
+
+
--
-- Name: cart_item_coupons cart_item_coupons_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@@ -5238,6 +5332,14 @@ ALTER TABLE ONLY public.chained_elements
ADD CONSTRAINT chained_elements_pkey PRIMARY KEY (id);
+--
+-- Name: children children_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.children
+ ADD CONSTRAINT children_pkey PRIMARY KEY (id);
+
+
--
-- Name: components components_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@@ -5718,6 +5820,14 @@ ALTER TABLE ONLY public.roles
ADD CONSTRAINT roles_pkey PRIMARY KEY (id);
+--
+-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.schema_migrations
+ ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);
+
+
--
-- Name: settings settings_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@@ -6061,6 +6171,27 @@ CREATE INDEX index_availability_tags_on_availability_id ON public.availability_t
CREATE INDEX index_availability_tags_on_tag_id ON public.availability_tags USING btree (tag_id);
+--
+-- Name: index_booking_users_on_booked; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX index_booking_users_on_booked ON public.booking_users USING btree (booked_type, booked_id);
+
+
+--
+-- Name: index_booking_users_on_event_price_category_id; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX index_booking_users_on_event_price_category_id ON public.booking_users USING btree (event_price_category_id);
+
+
+--
+-- Name: index_booking_users_on_reservation_id; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX index_booking_users_on_reservation_id ON public.booking_users USING btree (reservation_id);
+
+
--
-- Name: index_cart_item_coupons_on_coupon_id; Type: INDEX; Schema: public; Owner: -
--
@@ -6243,6 +6374,13 @@ CREATE UNIQUE INDEX index_categories_on_slug ON public.categories USING btree (s
CREATE INDEX index_chained_elements_on_element ON public.chained_elements USING btree (element_type, element_id);
+--
+-- Name: index_children_on_user_id; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX index_children_on_user_id ON public.children USING btree (user_id);
+
+
--
-- Name: index_coupons_on_code; Type: INDEX; Schema: public; Owner: -
--
@@ -7377,21 +7515,6 @@ CREATE INDEX proof_of_identity_type_id_and_proof_of_identity_refusal_id ON publi
CREATE UNIQUE INDEX unique_not_null_external_id ON public.invoicing_profiles USING btree (external_id) WHERE (external_id IS NOT NULL);
---
--- Name: unique_schema_migrations; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE UNIQUE INDEX unique_schema_migrations ON public.schema_migrations USING btree (version);
-
-
---
--- Name: accounting_periods accounting_periods_del_protect; Type: RULE; Schema: public; Owner: -
---
-
-CREATE RULE accounting_periods_del_protect AS
- ON DELETE TO public.accounting_periods DO INSTEAD NOTHING;
-
-
--
-- Name: accounting_periods accounting_periods_upd_protect; Type: RULE; Schema: public; Owner: -
--
@@ -7625,6 +7748,14 @@ ALTER TABLE ONLY public.subscriptions
ADD CONSTRAINT fk_rails_358a71f738 FOREIGN KEY (statistic_profile_id) REFERENCES public.statistic_profiles(id);
+--
+-- Name: booking_users fk_rails_38ad1ae7e8; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.booking_users
+ ADD CONSTRAINT fk_rails_38ad1ae7e8 FOREIGN KEY (reservation_id) REFERENCES public.reservations(id);
+
+
--
-- Name: invoices fk_rails_40d78f8cf6; Type: FK CONSTRAINT; Schema: public; Owner: -
--
@@ -8025,6 +8156,14 @@ ALTER TABLE ONLY public.cart_item_coupons
ADD CONSTRAINT fk_rails_a44bac1e45 FOREIGN KEY (operator_profile_id) REFERENCES public.invoicing_profiles(id);
+--
+-- Name: children fk_rails_a51d7cfb22; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.children
+ ADD CONSTRAINT fk_rails_a51d7cfb22 FOREIGN KEY (user_id) REFERENCES public.users(id);
+
+
--
-- Name: projects_themes fk_rails_b021a22658; Type: FK CONSTRAINT; Schema: public; Owner: -
--
@@ -8257,6 +8396,14 @@ ALTER TABLE ONLY public.projects
ADD CONSTRAINT fk_rails_e812590204 FOREIGN KEY (author_statistic_profile_id) REFERENCES public.statistic_profiles(id);
+--
+-- Name: booking_users fk_rails_e88263229e; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.booking_users
+ ADD CONSTRAINT fk_rails_e88263229e FOREIGN KEY (event_price_category_id) REFERENCES public.event_price_categories(id);
+
+
--
-- Name: user_tags fk_rails_ea0382482a; Type: FK CONSTRAINT; Schema: public; Owner: -
--
@@ -8368,7 +8515,6 @@ INSERT INTO "schema_migrations" (version) VALUES
('20140605125131'),
('20140605142133'),
('20140605151442'),
-('20140606133116'),
('20140609092700'),
('20140609092827'),
('20140610153123'),
@@ -8437,14 +8583,12 @@ INSERT INTO "schema_migrations" (version) VALUES
('20150507075620'),
('20150512123546'),
('20150520132030'),
-('20150520133409'),
('20150526130729'),
('20150527153312'),
('20150529113555'),
('20150601125944'),
('20150603104502'),
('20150603104658'),
-('20150603133050'),
('20150604081757'),
('20150604131525'),
('20150608142234'),
@@ -8526,7 +8670,6 @@ INSERT INTO "schema_migrations" (version) VALUES
('20160905142700'),
('20160906094739'),
('20160906094847'),
-('20160906145713'),
('20160915105234'),
('20161123104604'),
('20170109085345'),
@@ -8693,6 +8836,9 @@ INSERT INTO "schema_migrations" (version) VALUES
('20230324095639'),
('20230328094807'),
('20230328094808'),
-('20230328094809');
+('20230328094809'),
+('20230331132506'),
+('20230509121907'),
+('20230509161557');
diff --git a/test/frontend/components/events/event-form.test.tsx b/test/frontend/components/events/event-form.test.tsx
index 7b3d53b6c..49f50ce79 100644
--- a/test/frontend/components/events/event-form.test.tsx
+++ b/test/frontend/components/events/event-form.test.tsx
@@ -27,6 +27,7 @@ describe('EventForm', () => {
expect(screen.getByLabelText(/app.admin.event_form._and_ends_on/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.event_form.seats_available/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.event_form.standard_rate/)).toBeInTheDocument();
+ expect(screen.getByLabelText(/app.admin.event_form.booking_nominative/)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /app.admin.event_form.add_price/ })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /app.admin.event_form.add_a_new_file/ })).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.advanced_accounting_form.code/)).toBeInTheDocument();
From 6888f00036f82a018271fde8daa1a5f27bf8b95d Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Wed, 10 May 2023 18:47:13 +0200
Subject: [PATCH 22/45] (wip) save booking user for event nominatif
---
.../src/javascript/controllers/events.js.erb | 11 ++-
app/frontend/templates/events/show.html | 10 +--
app/models/cart_item.rb | 5 ++
app/models/cart_item/event_reservation.rb | 13 +++
.../event_reservation_booking_user.rb | 10 +++
app/services/cart_service.rb | 3 +-
config/locales/app.public.en.yml | 1 +
config/locales/app.public.fr.yml | 1 +
...rt_item_event_reservation_booking_users.rb | 15 ++++
db/structure.sql | 90 ++++++++++++++++++-
10 files changed, 150 insertions(+), 9 deletions(-)
create mode 100644 app/models/cart_item.rb
create mode 100644 app/models/cart_item/event_reservation_booking_user.rb
create mode 100644 db/migrate/20230510141305_create_cart_item_event_reservation_booking_users.rb
diff --git a/app/frontend/src/javascript/controllers/events.js.erb b/app/frontend/src/javascript/controllers/events.js.erb
index 63e2bd25d..335f97580 100644
--- a/app/frontend/src/javascript/controllers/events.js.erb
+++ b/app/frontend/src/javascript/controllers/events.js.erb
@@ -264,11 +264,14 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
const nbReservePlaces = priceType === 'normal' ? $scope.reserve.nbReservePlaces : $scope.reserve.tickets[priceType];
if (nbReservePlaces > nbBookingUsers) {
_.times(nbReservePlaces - nbBookingUsers, () => {
+ /*
if (!hasMemberInBookingUsers()) {
$scope.reserve.bookingUsers[priceType].push({ event_price_category_id: priceType === 'normal' ? null : priceType, booked_id: $scope.ctrl.member.id, booked_type: 'User', name: $scope.ctrl.member.name });
} else {
$scope.reserve.bookingUsers[priceType].push({ event_price_category_id: priceType === 'normal' ? null : priceType });
}
+ */
+ $scope.reserve.bookingUsers[priceType].push({ event_price_category_id: priceType === 'normal' ? null : priceType });
});
} else {
_.times(nbBookingUsers - nbReservePlaces, () => {
@@ -689,10 +692,14 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
if (event.booking_nominative) {
for (const key of Object.keys($scope.reserve.bookingUsers)) {
for (const user of $scope.reserve.bookingUsers[key]) {
- reservation.booking_users_attributes.push(user);
+ reservation.booking_users_attributes.push({
+ event_price_category_id: user.event_price_category_id,
+ name: user.name,
+ booked_id: user.booked_id,
+ booked_type: user.booked_type
+ });
}
}
- console.log(reservation);
}
return { reservation };
diff --git a/app/frontend/templates/events/show.html b/app/frontend/templates/events/show.html
index c8d86cd02..a75de228b 100644
--- a/app/frontend/templates/events/show.html
+++ b/app/frontend/templates/events/show.html
@@ -119,9 +119,9 @@
{{ 'app.public.events_show.ticket' | translate:{NUMBER:reserve.nbReservePlaces} }}
-
+
@@ -132,9 +132,9 @@
{{ 'app.public.events_show.ticket' | translate:{NUMBER:reserve.tickets[price.id]} }}
-
+
@@ -169,7 +169,7 @@
{{ 'app.public.events_show.you_booked_DATE' | translate:{DATE:(reservation.created_at | amDateFormat:'L LT')} }}
{{ 'app.public.events_show.full_price_' | translate }} {{reservation.nb_reserve_places}} {{ 'app.public.events_show.ticket' | translate:{NUMBER:reservation.nb_reserve_places} }}
-
+
{{ticket.event_price_category.price_category.name}} : {{ticket.booked}} {{ 'app.public.events_show.ticket' | translate:{NUMBER:ticket.booked} }}
diff --git a/app/models/cart_item.rb b/app/models/cart_item.rb
new file mode 100644
index 000000000..4397e866e
--- /dev/null
+++ b/app/models/cart_item.rb
@@ -0,0 +1,5 @@
+module CartItem
+ def self.table_name_prefix
+ "cart_item_"
+ end
+end
diff --git a/app/models/cart_item/event_reservation.rb b/app/models/cart_item/event_reservation.rb
index 9ae7be11a..cbb90a934 100644
--- a/app/models/cart_item/event_reservation.rb
+++ b/app/models/cart_item/event_reservation.rb
@@ -13,6 +13,11 @@ class CartItem::EventReservation < CartItem::Reservation
foreign_type: 'cart_item_type', as: :cart_item
accepts_nested_attributes_for :cart_item_reservation_slots
+ has_many :cart_item_event_reservation_booking_users, class_name: 'CartItem::EventReservationBookingUser', dependent: :destroy,
+ inverse_of: :cart_item_event_reservation,
+ foreign_key: 'cart_item_event_reservation_id'
+ accepts_nested_attributes_for :cart_item_event_reservation_booking_users
+
belongs_to :operator_profile, class_name: 'InvoicingProfile'
belongs_to :customer_profile, class_name: 'InvoicingProfile'
@@ -63,6 +68,14 @@ class CartItem::EventReservation < CartItem::Reservation
booked: t.booked
}
end,
+ booking_users_attributes: cart_item_event_reservation_booking_users.map do |b|
+ {
+ event_price_category_id: b.event_price_category_id,
+ booked_type: b.booked_type,
+ booked_id: b.booked_id,
+ name: b.name
+ }
+ end,
nb_reserve_places: normal_tickets,
statistic_profile_id: StatisticProfile.find_by(user: customer).id
)
diff --git a/app/models/cart_item/event_reservation_booking_user.rb b/app/models/cart_item/event_reservation_booking_user.rb
new file mode 100644
index 000000000..82df9ac9c
--- /dev/null
+++ b/app/models/cart_item/event_reservation_booking_user.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+# A relation table between a pending event reservation and reservation users for this event
+class CartItem::EventReservationBookingUser < ApplicationRecord
+ self.table_name = 'cart_item_event_reservation_booking_users'
+
+ belongs_to :cart_item_event_reservation, class_name: 'CartItem::EventReservation', inverse_of: :cart_item_event_reservation_booking_users
+ belongs_to :event_price_category, inverse_of: :cart_item_event_reservation_tickets
+ belongs_to :booked, polymorphic: true
+end
diff --git a/app/services/cart_service.rb b/app/services/cart_service.rb
index 6c6588e4b..52210c40d 100644
--- a/app/services/cart_service.rb
+++ b/app/services/cart_service.rb
@@ -171,7 +171,8 @@ class CartService
event: reservable,
cart_item_reservation_slots_attributes: cart_item[:slots_reservations_attributes],
normal_tickets: cart_item[:nb_reserve_places],
- cart_item_event_reservation_tickets_attributes: cart_item[:tickets_attributes] || {})
+ cart_item_event_reservation_tickets_attributes: cart_item[:tickets_attributes] || {},
+ cart_item_event_reservation_booking_users_attributes: cart_item[:booking_users_attributes] || {})
when Space
CartItem::SpaceReservation.new(customer_profile: @customer.invoicing_profile,
operator_profile: @operator.invoicing_profile,
diff --git a/config/locales/app.public.en.yml b/config/locales/app.public.en.yml
index 12e8c9785..b8607b367 100644
--- a/config/locales/app.public.en.yml
+++ b/config/locales/app.public.en.yml
@@ -357,6 +357,7 @@ en:
view_event_list: "View events to come"
share_on_facebook: "Share on Facebook"
share_on_twitter: "Share on Twitter"
+ last_name_and_first_name: "Last name and first name"
#public calendar
calendar:
calendar: "Calendar"
diff --git a/config/locales/app.public.fr.yml b/config/locales/app.public.fr.yml
index 3ef09b458..0f0ca2103 100644
--- a/config/locales/app.public.fr.yml
+++ b/config/locales/app.public.fr.yml
@@ -357,6 +357,7 @@ fr:
view_event_list: "Voir les événements à venir"
share_on_facebook: "Partager sur Facebook"
share_on_twitter: "Partager sur Twitter"
+ last_name_and_first_name: "Nom et prénom"
#public calendar
calendar:
calendar: "Calendrier"
diff --git a/db/migrate/20230510141305_create_cart_item_event_reservation_booking_users.rb b/db/migrate/20230510141305_create_cart_item_event_reservation_booking_users.rb
new file mode 100644
index 000000000..bbaae5626
--- /dev/null
+++ b/db/migrate/20230510141305_create_cart_item_event_reservation_booking_users.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+# A relation table between a pending event reservation and reservation users for this event
+class CreateCartItemEventReservationBookingUsers < ActiveRecord::Migration[7.0]
+ def change
+ create_table :cart_item_event_reservation_booking_users do |t|
+ t.string :name
+ t.belongs_to :cart_item_event_reservation, foreign_key: true, index: { name: 'index_cart_item_booking_users_on_cart_item_event_reservation' }
+ t.references :event_price_category, foreign_key: true, index: { name: 'index_cart_item_booking_users_on_event_price_category' }
+ t.references :booked, polymorphic: true
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/structure.sql b/db/structure.sql
index cc238df4c..c19642c69 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -588,6 +588,41 @@ CREATE SEQUENCE public.cart_item_coupons_id_seq
ALTER SEQUENCE public.cart_item_coupons_id_seq OWNED BY public.cart_item_coupons.id;
+--
+-- Name: cart_item_event_reservation_booking_users; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.cart_item_event_reservation_booking_users (
+ id bigint NOT NULL,
+ name character varying,
+ cart_item_event_reservation_id bigint,
+ event_price_category_id bigint,
+ booked_type character varying,
+ booked_id bigint,
+ created_at timestamp(6) without time zone NOT NULL,
+ updated_at timestamp(6) without time zone NOT NULL
+);
+
+
+--
+-- Name: cart_item_event_reservation_booking_users_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public.cart_item_event_reservation_booking_users_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+
+--
+-- Name: cart_item_event_reservation_booking_users_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE public.cart_item_event_reservation_booking_users_id_seq OWNED BY public.cart_item_event_reservation_booking_users.id;
+
+
--
-- Name: cart_item_event_reservation_tickets; Type: TABLE; Schema: public; Owner: -
--
@@ -4398,6 +4433,13 @@ ALTER TABLE ONLY public.booking_users ALTER COLUMN id SET DEFAULT nextval('publi
ALTER TABLE ONLY public.cart_item_coupons ALTER COLUMN id SET DEFAULT nextval('public.cart_item_coupons_id_seq'::regclass);
+--
+-- Name: cart_item_event_reservation_booking_users id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.cart_item_event_reservation_booking_users ALTER COLUMN id SET DEFAULT nextval('public.cart_item_event_reservation_booking_users_id_seq'::regclass);
+
+
--
-- Name: cart_item_event_reservation_tickets id; Type: DEFAULT; Schema: public; Owner: -
--
@@ -5252,6 +5294,14 @@ ALTER TABLE ONLY public.cart_item_coupons
ADD CONSTRAINT cart_item_coupons_pkey PRIMARY KEY (id);
+--
+-- Name: cart_item_event_reservation_booking_users cart_item_event_reservation_booking_users_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.cart_item_event_reservation_booking_users
+ ADD CONSTRAINT cart_item_event_reservation_booking_users_pkey PRIMARY KEY (id);
+
+
--
-- Name: cart_item_event_reservation_tickets cart_item_event_reservation_tickets_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@@ -6192,6 +6242,20 @@ CREATE INDEX index_booking_users_on_event_price_category_id ON public.booking_us
CREATE INDEX index_booking_users_on_reservation_id ON public.booking_users USING btree (reservation_id);
+--
+-- Name: index_cart_item_booking_users_on_cart_item_event_reservation; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX index_cart_item_booking_users_on_cart_item_event_reservation ON public.cart_item_event_reservation_booking_users USING btree (cart_item_event_reservation_id);
+
+
+--
+-- Name: index_cart_item_booking_users_on_event_price_category; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX index_cart_item_booking_users_on_event_price_category ON public.cart_item_event_reservation_booking_users USING btree (event_price_category_id);
+
+
--
-- Name: index_cart_item_coupons_on_coupon_id; Type: INDEX; Schema: public; Owner: -
--
@@ -6213,6 +6277,13 @@ CREATE INDEX index_cart_item_coupons_on_customer_profile_id ON public.cart_item_
CREATE INDEX index_cart_item_coupons_on_operator_profile_id ON public.cart_item_coupons USING btree (operator_profile_id);
+--
+-- Name: index_cart_item_event_reservation_booking_users_on_booked; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX index_cart_item_event_reservation_booking_users_on_booked ON public.cart_item_event_reservation_booking_users USING btree (booked_type, booked_id);
+
+
--
-- Name: index_cart_item_event_reservations_on_customer_profile_id; Type: INDEX; Schema: public; Owner: -
--
@@ -7548,6 +7619,14 @@ ALTER TABLE ONLY public.payment_schedules
ADD CONSTRAINT fk_rails_00308dc223 FOREIGN KEY (wallet_transaction_id) REFERENCES public.wallet_transactions(id);
+--
+-- Name: cart_item_event_reservation_booking_users fk_rails_0964335a37; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.cart_item_event_reservation_booking_users
+ ADD CONSTRAINT fk_rails_0964335a37 FOREIGN KEY (event_price_category_id) REFERENCES public.event_price_categories(id);
+
+
--
-- Name: cart_item_free_extensions fk_rails_0d11862969; Type: FK CONSTRAINT; Schema: public; Owner: -
--
@@ -7820,6 +7899,14 @@ ALTER TABLE ONLY public.chained_elements
ADD CONSTRAINT fk_rails_4fad806cca FOREIGN KEY (previous_id) REFERENCES public.chained_elements(id);
+--
+-- Name: cart_item_event_reservation_booking_users fk_rails_5206c6ca4a; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.cart_item_event_reservation_booking_users
+ ADD CONSTRAINT fk_rails_5206c6ca4a FOREIGN KEY (cart_item_event_reservation_id) REFERENCES public.cart_item_event_reservations(id);
+
+
--
-- Name: cart_item_event_reservation_tickets fk_rails_5307e8aab8; Type: FK CONSTRAINT; Schema: public; Owner: -
--
@@ -8839,6 +8926,7 @@ INSERT INTO "schema_migrations" (version) VALUES
('20230328094809'),
('20230331132506'),
('20230509121907'),
-('20230509161557');
+('20230509161557'),
+('20230510141305');
From aed5d1fc1b2a08a6ce4c69d4a6598e6f8d120b87 Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Thu, 11 May 2023 11:22:23 +0200
Subject: [PATCH 23/45] (wip) add event type
---
app/controllers/api/events_controller.rb | 2 +-
.../components/events/event-form.tsx | 26 ++++++++++++++-----
.../src/javascript/controllers/events.js.erb | 2 +-
app/frontend/src/javascript/models/event.ts | 3 ++-
app/frontend/templates/events/show.html | 4 +--
app/models/event.rb | 2 ++
app/views/api/events/_event.json.jbuilder | 2 +-
config/locales/app.admin.en.yml | 7 +++--
config/locales/app.admin.fr.yml | 7 +++--
...9121907_add_booking_nominative_to_event.rb | 8 ------
...rt_item_event_reservation_booking_users.rb | 3 ++-
.../20230511081018_add_event_type_to_event.rb | 10 +++++++
db/structure.sql | 6 +++--
test/fixtures/history_values.yml | 1 -
.../components/events/event-form.test.tsx | 2 +-
test/integration/events/as_admin_test.rb | 1 +
16 files changed, 56 insertions(+), 30 deletions(-)
delete mode 100644 db/migrate/20230509121907_add_booking_nominative_to_event.rb
create mode 100644 db/migrate/20230511081018_add_event_type_to_event.rb
diff --git a/app/controllers/api/events_controller.rb b/app/controllers/api/events_controller.rb
index 1b8abdc77..4d05f6b56 100644
--- a/app/controllers/api/events_controller.rb
+++ b/app/controllers/api/events_controller.rb
@@ -96,7 +96,7 @@ class API::EventsController < API::APIController
# handle general properties
event_preparams = params.required(:event).permit(:title, :description, :start_date, :start_time, :end_date, :end_time,
:amount, :nb_total_places, :availability_id, :all_day, :recurrence,
- :recurrence_end_at, :category_id, :event_theme_ids, :age_range_id, :booking_nominative,
+ :recurrence_end_at, :category_id, :event_theme_ids, :age_range_id, :event_type,
event_theme_ids: [],
event_image_attributes: %i[id attachment],
event_files_attributes: %i[id attachment _destroy],
diff --git a/app/frontend/src/javascript/components/events/event-form.tsx b/app/frontend/src/javascript/components/events/event-form.tsx
index 033dff894..0a2ce1d8c 100644
--- a/app/frontend/src/javascript/components/events/event-form.tsx
+++ b/app/frontend/src/javascript/components/events/event-form.tsx
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import * as React from 'react';
import { SubmitHandler, useFieldArray, useForm, useWatch } from 'react-hook-form';
-import { Event, EventDecoration, EventPriceCategoryAttributes, RecurrenceOption } from '../../models/event';
+import { Event, EventDecoration, EventPriceCategoryAttributes, RecurrenceOption, EventType } from '../../models/event';
import EventAPI from '../../api/event';
import { useTranslation } from 'react-i18next';
import { FormInput } from '../form/form-input';
@@ -40,7 +40,7 @@ interface EventFormProps {
* Form to edit or create events
*/
export const EventForm: React.FC = ({ action, event, onError, onSuccess }) => {
- const { handleSubmit, register, control, setValue, formState } = useForm({ defaultValues: { ...event } });
+ const { handleSubmit, register, control, setValue, formState } = useForm({ defaultValues: Object.assign({ event_type: 'standard' }, event) });
const output = useWatch({ control });
const { fields, append, remove } = useFieldArray({ control, name: 'event_price_categories_attributes' });
@@ -168,6 +168,17 @@ export const EventForm: React.FC = ({ action, event, onError, on
];
};
+ /**
+ * This method provides event type options
+ */
+ const buildEventTypeOptions = (): Array> => {
+ return [
+ { label: t('app.admin.event_form.event_types.standard'), value: 'standard' },
+ { label: t('app.admin.event_form.event_types.nominative'), value: 'nominative' },
+ { label: t('app.admin.event_form.event_types.family'), value: 'family' }
+ ];
+ };
+
return (
@@ -203,6 +214,12 @@ export const EventForm: React.FC = ({ action, event, onError, on
label={t('app.admin.event_form.description')}
limit={null}
heading bulletList blockquote link video image />
+
= ({ action, event, onError, on
label={t('app.admin.event_form.seats_available')}
type="number"
tooltip={t('app.admin.event_form.seats_help')} />
-
{{ 'app.public.events_show.ticket' | translate:{NUMBER:reserve.nbReservePlaces} }}
-
+
{{ 'app.public.events_show.last_name_and_first_name '}}
@@ -132,7 +132,7 @@
{{ 'app.public.events_show.ticket' | translate:{NUMBER:reserve.tickets[price.id]} }}
-
+
{{ 'app.public.events_show.last_name_and_first_name '}}
diff --git a/app/models/event.rb b/app/models/event.rb
index 60bb3f70b..96ab48199 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -33,6 +33,8 @@ class Event < ApplicationRecord
has_many :cart_item_event_reservations, class_name: 'CartItem::EventReservation', dependent: :destroy
+ validates :event_type, inclusion: { in: %w[standard nominative family] }, presence: true
+
attr_accessor :recurrence, :recurrence_end_at
before_save :update_nb_free_places
diff --git a/app/views/api/events/_event.json.jbuilder b/app/views/api/events/_event.json.jbuilder
index 11be0748e..885a29d28 100644
--- a/app/views/api/events/_event.json.jbuilder
+++ b/app/views/api/events/_event.json.jbuilder
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-json.extract! event, :id, :title, :description, :booking_nominative
+json.extract! event, :id, :title, :description, :event_type
if event.event_image
json.event_image_attributes do
json.id event.event_image.id
diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml
index a8ccdbafc..8fcbfb195 100644
--- a/config/locales/app.admin.en.yml
+++ b/config/locales/app.admin.en.yml
@@ -139,8 +139,6 @@ en:
event_themes: "Event themes"
age_range: "Age range"
add_price: "Add a price"
- booking_nominative: "Nominative booking"
- booking_nominative_help: "If you check this option, the members will have to enter the names of the participants when booking."
save: "Save"
create_success: "The event was created successfully"
events_updated: "{COUNT, plural, =1{One event was} other{{COUNT} Events were}} successfully updated"
@@ -153,6 +151,11 @@ en:
every_week: "Every week"
every_month: "Every month"
every_year: "Every year"
+ event_type: "Event type"
+ event_types:
+ standard: "Event standard"
+ nominative: "Event nominative"
+ family: "Event family"
plan_form:
ACTION_title: "{ACTION, select, create{New} other{Update the}} plan"
tab_settings: "Settings"
diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml
index 67970617b..22cfd5e86 100644
--- a/config/locales/app.admin.fr.yml
+++ b/config/locales/app.admin.fr.yml
@@ -139,8 +139,6 @@ fr:
event_themes: "Thèmes de l'événement"
age_range: "Tranche d'âge"
add_price: "Ajouter un tarif"
- booking_nominative: "Réservation nominative"
- booking_nominative_help: "Si cette option est activée, les réservations seront nominatives. Les participants devront s'identifier pour réserver."
save: "Enregistrer"
create_success: "L'événement a bien été créé"
events_updated: "{COUNT, plural, one {}=1{Un événement à été} other{{COUNT} événements ont été}} mis à jour avec succès"
@@ -153,6 +151,11 @@ fr:
every_week: "Chaque semaine"
every_month: "Chaque mois"
every_year: "Chaque année"
+ event_type: "Type d'événement"
+ event_types:
+ standard: "Evénement standard"
+ nominative: "Evénement nominatif"
+ family: "Evénement famille"
plan_form:
ACTION_title: "{ACTION, select, create{Nouvelle} other{Mettre à jour la}} formule d'abonnement"
tab_settings: "Paramètres"
diff --git a/db/migrate/20230509121907_add_booking_nominative_to_event.rb b/db/migrate/20230509121907_add_booking_nominative_to_event.rb
deleted file mode 100644
index 1a709170d..000000000
--- a/db/migrate/20230509121907_add_booking_nominative_to_event.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# frozen_string_literal: true
-
-# add booking_nominative to event
-class AddBookingNominativeToEvent < ActiveRecord::Migration[7.0]
- def change
- add_column :events, :booking_nominative, :boolean, default: false
- end
-end
diff --git a/db/migrate/20230510141305_create_cart_item_event_reservation_booking_users.rb b/db/migrate/20230510141305_create_cart_item_event_reservation_booking_users.rb
index bbaae5626..e5d1a3465 100644
--- a/db/migrate/20230510141305_create_cart_item_event_reservation_booking_users.rb
+++ b/db/migrate/20230510141305_create_cart_item_event_reservation_booking_users.rb
@@ -5,7 +5,8 @@ class CreateCartItemEventReservationBookingUsers < ActiveRecord::Migration[7.0]
def change
create_table :cart_item_event_reservation_booking_users do |t|
t.string :name
- t.belongs_to :cart_item_event_reservation, foreign_key: true, index: { name: 'index_cart_item_booking_users_on_cart_item_event_reservation' }
+ t.belongs_to :cart_item_event_reservation, foreign_key: true,
+ index: { name: 'index_cart_item_booking_users_on_cart_item_event_reservation' }
t.references :event_price_category, foreign_key: true, index: { name: 'index_cart_item_booking_users_on_event_price_category' }
t.references :booked, polymorphic: true
diff --git a/db/migrate/20230511081018_add_event_type_to_event.rb b/db/migrate/20230511081018_add_event_type_to_event.rb
new file mode 100644
index 000000000..4f1f832a9
--- /dev/null
+++ b/db/migrate/20230511081018_add_event_type_to_event.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+# Add event_type to event model, to be able to create standard/nominative/family events
+class AddEventTypeToEvent < ActiveRecord::Migration[7.0]
+ def change
+ add_column :events, :event_type, :string, default: 'standard'
+ Event.reset_column_information
+ Event.update_all(event_type: 'standard')
+ end
+end
diff --git a/db/structure.sql b/db/structure.sql
index c19642c69..35a9af3e8 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -1243,7 +1243,7 @@ CREATE TABLE public.events (
age_range_id integer,
category_id integer,
deleted_at timestamp without time zone,
- booking_nominative boolean DEFAULT false
+ event_type character varying DEFAULT 'standard'::character varying
);
@@ -8927,6 +8927,8 @@ INSERT INTO "schema_migrations" (version) VALUES
('20230331132506'),
('20230509121907'),
('20230509161557'),
-('20230510141305');
+('20230510141305'),
+('20230511080650'),
+('20230511081018');
diff --git a/test/fixtures/history_values.yml b/test/fixtures/history_values.yml
index 42f684753..523ddea7f 100644
--- a/test/fixtures/history_values.yml
+++ b/test/fixtures/history_values.yml
@@ -858,5 +858,4 @@ history_value_101:
value: 'false'
created_at: '2023-03-31 14:38:40.000421'
updated_at: '2023-03-31 14:38:40.000421'
- footprint:
invoicing_profile_id: 1
diff --git a/test/frontend/components/events/event-form.test.tsx b/test/frontend/components/events/event-form.test.tsx
index 49f50ce79..219542a23 100644
--- a/test/frontend/components/events/event-form.test.tsx
+++ b/test/frontend/components/events/event-form.test.tsx
@@ -15,6 +15,7 @@ describe('EventForm', () => {
expect(screen.getByLabelText(/app.admin.event_form.title/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.event_form.matching_visual/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.event_form.description/)).toBeInTheDocument();
+ expect(screen.getByLabelText(/app.admin.event_form.event_type/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.event_form.event_category/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.event_form.event_themes/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.event_form.age_range/)).toBeInTheDocument();
@@ -27,7 +28,6 @@ describe('EventForm', () => {
expect(screen.getByLabelText(/app.admin.event_form._and_ends_on/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.event_form.seats_available/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.event_form.standard_rate/)).toBeInTheDocument();
- expect(screen.getByLabelText(/app.admin.event_form.booking_nominative/)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /app.admin.event_form.add_price/ })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /app.admin.event_form.add_a_new_file/ })).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.advanced_accounting_form.code/)).toBeInTheDocument();
diff --git a/test/integration/events/as_admin_test.rb b/test/integration/events/as_admin_test.rb
index e4d721ed4..5d59eef64 100644
--- a/test/integration/events/as_admin_test.rb
+++ b/test/integration/events/as_admin_test.rb
@@ -22,6 +22,7 @@ class Events::AsAdminTest < ActionDispatch::IntegrationTest
end_date: 1.week.from_now.utc,
end_time: 1.week.from_now.utc.change(hour: 20),
category_id: Category.first.id,
+ event_type: 'standard',
amount: 0
}
}.to_json,
From 1b655402573889155538804e3b36365186425cb5 Mon Sep 17 00:00:00 2001
From: Du Peng
Date: Mon, 15 May 2023 16:42:01 +0200
Subject: [PATCH 24/45] (wip) pay family and nominative event
---
app/controllers/api/children_controller.rb | 5 +-
.../components/events/event-form.tsx | 13 +-
.../src/javascript/controllers/events.js.erb | 164 ++++++++++++++----
app/frontend/src/javascript/services/child.js | 11 ++
app/frontend/templates/events/show.html | 34 +++-
app/policies/child_policy.rb | 7 -
6 files changed, 184 insertions(+), 50 deletions(-)
create mode 100644 app/frontend/src/javascript/services/child.js
diff --git a/app/controllers/api/children_controller.rb b/app/controllers/api/children_controller.rb
index f923f2bb3..618d859fb 100644
--- a/app/controllers/api/children_controller.rb
+++ b/app/controllers/api/children_controller.rb
@@ -7,7 +7,10 @@ class API::ChildrenController < API::APIController
before_action :set_child, only: %i[show update destroy]
def index
- @children = policy_scope(Child)
+ authorize Child
+ user_id = current_user.id
+ user_id = params[:user_id] if current_user.privileged? && params[:user_id]
+ @children = Child.where(user_id: user_id)
end
def show
diff --git a/app/frontend/src/javascript/components/events/event-form.tsx b/app/frontend/src/javascript/components/events/event-form.tsx
index 0a2ce1d8c..5ed6a7036 100644
--- a/app/frontend/src/javascript/components/events/event-form.tsx
+++ b/app/frontend/src/javascript/components/events/event-form.tsx
@@ -54,6 +54,7 @@ export const EventForm: React.FC = ({ action, event, onError, on
const [isOpenRecurrentModal, setIsOpenRecurrentModal] = useState(false);
const [updatingEvent, setUpdatingEvent] = useState(null);
const [isActiveAccounting, setIsActiveAccounting] = useState(false);
+ const [isActiveFamilyAccount, setIsActiveFamilyAccount] = useState(false);
useEffect(() => {
EventCategoryAPI.index()
@@ -69,6 +70,7 @@ export const EventForm: React.FC = ({ action, event, onError, on
.then(data => setPriceCategoriesOptions(data.map(c => decorationToOption(c))))
.catch(onError);
SettingAPI.get('advanced_accounting').then(res => setIsActiveAccounting(res.value === 'true')).catch(onError);
+ SettingAPI.get('family_account').then(res => setIsActiveFamilyAccount(res.value === 'true')).catch(onError);
}, []);
useEffect(() => {
@@ -172,11 +174,14 @@ export const EventForm: React.FC = ({ action, event, onError, on
* This method provides event type options
*/
const buildEventTypeOptions = (): Array> => {
- return [
- { label: t('app.admin.event_form.event_types.standard'), value: 'standard' },
- { label: t('app.admin.event_form.event_types.nominative'), value: 'nominative' },
- { label: t('app.admin.event_form.event_types.family'), value: 'family' }
+ const options = [
+ { label: t('app.admin.event_form.event_types.standard'), value: 'standard' as EventType },
+ { label: t('app.admin.event_form.event_types.nominative'), value: 'nominative' as EventType }
];
+ if (isActiveFamilyAccount) {
+ options.push({ label: t('app.admin.event_form.event_types.family'), value: 'family' as EventType });
+ }
+ return options;
};
return (
diff --git a/app/frontend/src/javascript/controllers/events.js.erb b/app/frontend/src/javascript/controllers/events.js.erb
index 32e266bfe..5782994be 100644
--- a/app/frontend/src/javascript/controllers/events.js.erb
+++ b/app/frontend/src/javascript/controllers/events.js.erb
@@ -136,8 +136,8 @@ Application.Controllers.controller('EventsController', ['$scope', '$state', 'Eve
}
]);
-Application.Controllers.controller('ShowEventController', ['$scope', '$state', '$rootScope', 'Event', '$uibModal', 'Member', 'Reservation', 'Price', 'CustomAsset', 'SlotsReservation', 'eventPromise', 'growl', '_t', 'Wallet', 'AuthService', 'helpers', 'dialogs', 'priceCategoriesPromise', 'settingsPromise', 'LocalPayment',
- function ($scope, $state,$rootScope, Event, $uibModal, Member, Reservation, Price, CustomAsset, SlotsReservation, eventPromise, growl, _t, Wallet, AuthService, helpers, dialogs, priceCategoriesPromise, settingsPromise, LocalPayment) {
+Application.Controllers.controller('ShowEventController', ['$scope', '$state', '$rootScope', 'Event', '$uibModal', 'Member', 'Reservation', 'Price', 'CustomAsset', 'SlotsReservation', 'eventPromise', 'growl', '_t', 'Wallet', 'AuthService', 'helpers', 'dialogs', 'priceCategoriesPromise', 'settingsPromise', 'LocalPayment', 'Child',
+ function ($scope, $state,$rootScope, Event, $uibModal, Member, Reservation, Price, CustomAsset, SlotsReservation, eventPromise, growl, _t, Wallet, AuthService, helpers, dialogs, priceCategoriesPromise, settingsPromise, LocalPayment, Child) {
/* PUBLIC SCOPE */
// reservations for the currently shown event
@@ -150,6 +150,9 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
$scope.ctrl =
{ member: {} };
+ // children for the member
+ $scope.children = [];
+
// parameters for a new reservation
$scope.reserve = {
nbPlaces: {
@@ -226,22 +229,12 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
});
};
- const hasMemberInBookingUsers = function () {
- const keys = Object.keys($scope.reserve.bookingUsers);
- for (const key of keys) {
- if ($scope.reserve.bookingUsers[key].find(u => u.booked_id === $scope.ctrl.member.id && u.booked_type === 'User')) {
- return true;
- }
- }
- return false;
- };
-
/**
* Callback to call when the number of tickets to book changes in the current booking
*/
$scope.changeNbPlaces = function (priceType) {
// compute the total remaining places
- let remain = $scope.event.nb_free_places - $scope.reserve.nbReservePlaces;
+ let remain = ($scope.event.event_type === 'family' ? ($scope.children.length + 1) : $scope.event.nb_free_places) - $scope.reserve.nbReservePlaces;
for (let ticket in $scope.reserve.tickets) {
remain -= $scope.reserve.tickets[ticket];
}
@@ -260,36 +253,41 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
}
}
- const nbBookingUsers = $scope.reserve.bookingUsers[priceType].length;
- const nbReservePlaces = priceType === 'normal' ? $scope.reserve.nbReservePlaces : $scope.reserve.tickets[priceType];
- if (nbReservePlaces > nbBookingUsers) {
- _.times(nbReservePlaces - nbBookingUsers, () => {
- /*
- if (!hasMemberInBookingUsers()) {
- $scope.reserve.bookingUsers[priceType].push({ event_price_category_id: priceType === 'normal' ? null : priceType, booked_id: $scope.ctrl.member.id, booked_type: 'User', name: $scope.ctrl.member.name });
- } else {
- $scope.reserve.bookingUsers[priceType].push({ event_price_category_id: priceType === 'normal' ? null : priceType });
- }
- */
- $scope.reserve.bookingUsers[priceType].push({ event_price_category_id: priceType === 'normal' ? null : priceType });
- });
- } else {
- _.times(nbBookingUsers - nbReservePlaces, () => {
- $scope.reserve.bookingUsers[priceType].pop();
- });
+ if ($scope.event.event_type === 'nominative' || $scope.event.event_type === 'family') {
+ const nbBookingUsers = $scope.reserve.bookingUsers[priceType].length;
+ const nbReservePlaces = priceType === 'normal' ? $scope.reserve.nbReservePlaces : $scope.reserve.tickets[priceType];
+ if (nbReservePlaces > nbBookingUsers) {
+ _.times(nbReservePlaces - nbBookingUsers, () => {
+ $scope.reserve.bookingUsers[priceType].push({ event_price_category_id: priceType === 'normal' ? null : priceType, bookedUsers: buildBookedUsersOptions() });
+ });
+ } else {
+ _.times(nbBookingUsers - nbReservePlaces, () => {
+ $scope.reserve.bookingUsers[priceType].pop();
+ });
+ }
}
// recompute the total price
return $scope.computeEventAmount();
};
+ $scope.changeBookedUser = function () {
+ for (const key of Object.keys($scope.reserve.bookingUsers)) {
+ for (const user of $scope.reserve.bookingUsers[key]) {
+ user.bookedUsers = buildBookedUsersOptions(user.booked);
+ }
+ }
+ }
+
/**
* Callback to reset the current reservation parameters
* @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
*/
$scope.cancelReserve = function (e) {
e.preventDefault();
- return resetEventReserve();
+ resetEventReserve();
+ updateNbReservePlaces();
+ return;
};
$scope.isUserValidatedByType = () => {
@@ -354,6 +352,9 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
Member.get({ id: $scope.ctrl.member.id }, function (member) {
$scope.ctrl.member = member;
getReservations($scope.event.id, 'Event', $scope.ctrl.member.id);
+ getChildren($scope.ctrl.member.id).then(() => {
+ updateNbReservePlaces();
+ });
});
}
};
@@ -615,6 +616,31 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
growl.error(message);
};
+ /**
+ * Checks if the reservation of current event is valid
+ */
+ $scope.reservationIsValid = () => {
+ if ($scope.event.event_type === 'nominative') {
+ for (const key of Object.keys($scope.reserve.bookingUsers)) {
+ for (const user of $scope.reserve.bookingUsers[key]) {
+ if (!_.trim(user.name)) {
+ return false;
+ }
+ }
+ }
+ }
+ if ($scope.event.event_type === 'family') {
+ for (const key of Object.keys($scope.reserve.bookingUsers)) {
+ for (const user of $scope.reserve.bookingUsers[key]) {
+ if (!user.booked) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
/* PRIVATE SCOPE */
/**
@@ -634,6 +660,9 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
// get the current user's reservations into $scope.reservations
if ($scope.currentUser) {
getReservations($scope.event.id, 'Event', $scope.currentUser.id);
+ getChildren($scope.currentUser.id).then(function (children) {
+ updateNbReservePlaces();
+ });
}
// watch when a coupon is applied to re-compute the total price
@@ -658,6 +687,72 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
}).$promise.then(function (reservations) { $scope.reservations = reservations; });
};
+ /**
+ * Retrieve the children for the user
+ * @param user_id {number} the user's id (current or managed)
+ */
+ const getChildren = function (user_id) {
+ return Child.query({
+ user_id
+ }).$promise.then(function (children) {
+ $scope.children = children;
+ return $scope.children;
+ });
+ };
+
+ /**
+ * Update the number of places reserved by the current user
+ */
+ const hasBookedUser = function (userKey) {
+ for (const key of Object.keys($scope.reserve.bookingUsers)) {
+ for (const user of $scope.reserve.bookingUsers[key]) {
+ if (user.booked && user.booked.key === userKey) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ /**
+ * Build the list of options for the select box of the booked users
+ * @param booked {object} the booked user
+ */
+ const buildBookedUsersOptions = function (booked) {
+ const options = [];
+ const userKey = `user_${$scope.ctrl.member.id}`;
+ if ((booked && booked.key === userKey) || !hasBookedUser(userKey)) {
+ options.push({ key: userKey, name: $scope.ctrl.member.name, type: 'User', id: $scope.ctrl.member.id });
+ }
+ for (const child of $scope.children) {
+ const key = `child_${child.id}`;
+ if ((booked && booked.key === key) || !hasBookedUser(key)) {
+ options.push({
+ key,
+ name: child.first_name + ' ' + child.last_name,
+ id: child.id,
+ type: 'Child'
+ });
+ }
+ }
+ return options;
+ };
+
+ /**
+ * update number of places available for each price category for the family event
+ */
+ const updateNbReservePlaces = function () {
+ if ($scope.event.event_type === 'family') {
+ const maxPlaces = $scope.children.length + 1;
+ if ($scope.event.nb_free_places > maxPlaces) {
+ $scope.reserve.nbPlaces.normal = __range__(0, maxPlaces, true);
+ for (let evt_px_cat of Array.from($scope.event.event_price_categories_attributes)) {
+ $scope.reserve.nbPlaces[evt_px_cat.id] = __range__(0, maxPlaces, true);
+ }
+ }
+ }
+ };
+
/**
* Create a hash map implementing the Reservation specs
* @param reserve {Object} Reservation parameters (places...)
@@ -694,9 +789,9 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
for (const user of $scope.reserve.bookingUsers[key]) {
reservation.booking_users_attributes.push({
event_price_category_id: user.event_price_category_id,
- name: user.name,
- booked_id: user.booked_id,
- booked_type: user.booked_type
+ name: user.booked ? user.booked.name : _.trim(user.name),
+ booked_id: user.booked ? user.booked.id : undefined,
+ booked_type: user.booked ? user.booked.type : undefined,
});
}
}
@@ -865,6 +960,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
$scope.reservations.push(reservation);
});
resetEventReserve();
+ updateNbReservePlaces();
$scope.reserveSuccess = true;
$scope.coupon.applied = null;
if ($scope.currentUser.role === 'admin') {
diff --git a/app/frontend/src/javascript/services/child.js b/app/frontend/src/javascript/services/child.js
new file mode 100644
index 000000000..07d0d916c
--- /dev/null
+++ b/app/frontend/src/javascript/services/child.js
@@ -0,0 +1,11 @@
+'use strict';
+
+Application.Services.factory('Child', ['$resource', function ($resource) {
+ return $resource('/api/children/:id',
+ { id: '@id' }, {
+ update: {
+ method: 'PUT'
+ }
+ }
+ );
+}]);
diff --git a/app/frontend/templates/events/show.html b/app/frontend/templates/events/show.html
index 1da00b830..72cd5aa07 100644
--- a/app/frontend/templates/events/show.html
+++ b/app/frontend/templates/events/show.html
@@ -122,7 +122,20 @@
+
+
+ {{ 'app.public.events_show.last_name_and_first_name '}}
+
+
+
@@ -135,7 +148,20 @@
+
+
+ {{ 'app.public.events_show.last_name_and_first_name '}}
+
+
+
@@ -202,11 +228,11 @@
-