mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
Merge pull request #21826 from opf/bug/71089-move-to-next-meeting-and-duplicate-in-next-meeting-select-cancelled-meeting
[#71089] "Move to next meeting" and "Duplicate in next meeting" select cancelled meeting
This commit is contained in:
+1
-1
@@ -32,7 +32,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
dialog.with_show_button { title }
|
||||
dialog.with_feedback_message(icon_arguments: { icon: :none }) do |message|
|
||||
message.with_heading(tag: :h2).with_content(title)
|
||||
message.with_description_content(confirmation_message)
|
||||
message.with_description_content(simple_format(confirmation_message))
|
||||
end
|
||||
dialog.with_footer do
|
||||
concat(
|
||||
|
||||
+17
-2
@@ -33,11 +33,12 @@ module MeetingAgendaItems
|
||||
include ApplicationHelper
|
||||
include OpTurbo::Streamable
|
||||
|
||||
def initialize(agenda_item:, datetime:)
|
||||
def initialize(agenda_item:, datetime:, skipped: nil)
|
||||
super
|
||||
|
||||
@agenda_item = agenda_item
|
||||
@datetime = datetime
|
||||
@skipped = skipped
|
||||
end
|
||||
|
||||
private
|
||||
@@ -47,11 +48,25 @@ module MeetingAgendaItems
|
||||
def title = I18n.t(:label_agenda_item_duplicate_in_next_title)
|
||||
|
||||
def confirmation_message
|
||||
I18n.t(
|
||||
base_message = I18n.t(
|
||||
:text_agenda_item_duplicate_in_next_meeting,
|
||||
date: format_date(@datetime),
|
||||
time: format_time(@datetime, include_date: false)
|
||||
)
|
||||
|
||||
if @skipped.present?
|
||||
"#{base_message}\n\n#{skipped_message}"
|
||||
else
|
||||
base_message
|
||||
end
|
||||
end
|
||||
|
||||
def skipped_message
|
||||
if @skipped.one?
|
||||
I18n.t(:text_agenda_item_dialog_skipping_cancelled_one, date: format_date(DateTime.iso8601(@skipped.first)))
|
||||
else
|
||||
I18n.t(:text_agenda_item_dialog_skipping_cancelled_many, count: @skipped.size)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+14
-5
@@ -233,14 +233,19 @@ module MeetingAgendaItems
|
||||
def next_meeting_action_item(menu, label:, action:, icon:)
|
||||
return unless has_next_occurrence?
|
||||
|
||||
next_date = @series.next_occurrence(from_time: next_occurrence_from_time)
|
||||
from_time = @meeting.start_time.past? ? Time.current : @meeting.start_time
|
||||
result = @series.first_non_cancelled_occurrence(from_time:)
|
||||
return if result.nil?
|
||||
|
||||
next_date = result[:occurrence]
|
||||
skipped_dates = result[:skipped]
|
||||
|
||||
menu.with_item(
|
||||
label:,
|
||||
tag: :button,
|
||||
content_arguments: { data: {
|
||||
action: "click->meetings--submit#intercept",
|
||||
href: path_for_next_button(action: action, next_date: next_date),
|
||||
href: path_for_next_button(action:, next_date:, skipped_dates:),
|
||||
method: "GET"
|
||||
} }
|
||||
) do |item|
|
||||
@@ -424,18 +429,22 @@ module MeetingAgendaItems
|
||||
end
|
||||
end
|
||||
|
||||
def path_for_next_button(action:, next_date:)
|
||||
def path_for_next_button(action:, next_date:, skipped_dates:)
|
||||
skipped_iso_dates = skipped_dates.map(&:iso8601) if skipped_dates.present?
|
||||
|
||||
case action
|
||||
when :move_to_next
|
||||
move_to_next_dialog_project_meeting_agenda_item_path(@meeting.project,
|
||||
@meeting,
|
||||
@meeting_agenda_item,
|
||||
datetime: next_date.iso8601)
|
||||
datetime: next_date.iso8601,
|
||||
skipped: skipped_iso_dates)
|
||||
when :duplicate_in_next
|
||||
duplicate_in_next_dialog_project_meeting_agenda_item_path(@meeting.project,
|
||||
@meeting,
|
||||
@meeting_agenda_item,
|
||||
datetime: next_date.iso8601)
|
||||
datetime: next_date.iso8601,
|
||||
skipped: skipped_iso_dates)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
) do |dialog|
|
||||
dialog.with_confirmation_message do |message|
|
||||
message.with_heading(tag: :h2) { title }
|
||||
message.with_description_content(confirmation_message)
|
||||
message.with_description_content(simple_format(confirmation_message))
|
||||
end
|
||||
end
|
||||
%>
|
||||
|
||||
+17
-2
@@ -33,11 +33,12 @@ module MeetingAgendaItems
|
||||
include ApplicationHelper
|
||||
include OpTurbo::Streamable
|
||||
|
||||
def initialize(agenda_item:, datetime:)
|
||||
def initialize(agenda_item:, datetime:, skipped: nil)
|
||||
super
|
||||
|
||||
@agenda_item = agenda_item
|
||||
@datetime = datetime
|
||||
@skipped = skipped
|
||||
end
|
||||
|
||||
private
|
||||
@@ -45,11 +46,25 @@ module MeetingAgendaItems
|
||||
def title = I18n.t(:label_agenda_item_move_to_next_title)
|
||||
|
||||
def confirmation_message
|
||||
I18n.t(
|
||||
base_message = I18n.t(
|
||||
:text_agenda_item_move_next_meeting,
|
||||
date: format_date(@datetime),
|
||||
time: format_time(@datetime, include_date: false)
|
||||
)
|
||||
|
||||
if @skipped.present?
|
||||
"#{base_message}\n\n#{skipped_message}"
|
||||
else
|
||||
base_message
|
||||
end
|
||||
end
|
||||
|
||||
def skipped_message
|
||||
if @skipped.one?
|
||||
I18n.t(:text_agenda_item_dialog_skipping_cancelled_one, date: format_date(DateTime.iso8601(@skipped.first)))
|
||||
else
|
||||
I18n.t(:text_agenda_item_dialog_skipping_cancelled_many, count: @skipped.size)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -221,14 +221,16 @@ class MeetingAgendaItemsController < ApplicationController
|
||||
def move_to_next_meeting_dialog
|
||||
respond_with_dialog MeetingAgendaItems::MoveToNextMeetingDialogComponent.new(
|
||||
agenda_item: @meeting_agenda_item,
|
||||
datetime: params[:datetime]
|
||||
datetime: params[:datetime],
|
||||
skipped: params[:skipped]
|
||||
)
|
||||
end
|
||||
|
||||
def duplicate_in_next_meeting_dialog
|
||||
respond_with_dialog MeetingAgendaItems::DuplicateInNextMeetingDialogComponent.new(
|
||||
agenda_item: @meeting_agenda_item,
|
||||
datetime: params[:datetime]
|
||||
datetime: params[:datetime],
|
||||
skipped: params[:skipped]
|
||||
)
|
||||
end
|
||||
|
||||
@@ -240,7 +242,7 @@ class MeetingAgendaItemsController < ApplicationController
|
||||
|
||||
if update_call.success?
|
||||
render_success_flash_message_via_turbo_stream(
|
||||
message: I18n.t(:text_agenda_item_moved_to_next_meeting, date: format_date(next_occurrence.start_time))
|
||||
message: message_for_next_meeting_action(:text_agenda_item_moved_to_next_meeting, next_occurrence)
|
||||
)
|
||||
remove_item_via_turbo_stream(clear_slate: @meeting.agenda_items.empty?)
|
||||
update_header_component_via_turbo_stream
|
||||
@@ -259,7 +261,7 @@ class MeetingAgendaItemsController < ApplicationController
|
||||
if duplicate_call.success?
|
||||
close_dialog_via_turbo_stream("#duplicate-in-next-meeting-dialog")
|
||||
render_success_flash_message_via_turbo_stream(
|
||||
message: I18n.t(:text_agenda_item_duplicated_in_next_meeting, date: format_date(next_occurrence.start_time))
|
||||
message: message_for_next_meeting_action(:text_agenda_item_duplicated_in_next_meeting, next_occurrence)
|
||||
)
|
||||
update_header_component_via_turbo_stream
|
||||
respond_with_turbo_streams
|
||||
@@ -376,13 +378,23 @@ class MeetingAgendaItemsController < ApplicationController
|
||||
|
||||
def find_existing_occurrence
|
||||
next_occurrence = @series.scheduled_meetings.find_by(start_time: @next_meeting_time)
|
||||
return if next_occurrence.nil?
|
||||
|
||||
if next_occurrence.cancelled?
|
||||
respond_with_flash_error(message: I18n.t(:text_agenda_item_move_next_meeting_cancelled))
|
||||
else
|
||||
@next_occurrence = next_occurrence.meeting
|
||||
if next_occurrence&.cancelled?
|
||||
result = @series.first_non_cancelled_occurrence(from_time: @next_meeting_time)
|
||||
|
||||
if result.nil?
|
||||
return respond_with_flash_error(message: I18n.t(:text_agenda_item_no_available_occurrence))
|
||||
end
|
||||
|
||||
@next_meeting_time = result[:occurrence]
|
||||
next_occurrence = @series.scheduled_meetings.find_by(start_time: @next_meeting_time)
|
||||
end
|
||||
|
||||
@next_occurrence = next_occurrence&.meeting
|
||||
end
|
||||
|
||||
def message_for_next_meeting_action(base_key, next_occurrence)
|
||||
I18n.t(base_key, date: format_date(next_occurrence.start_time))
|
||||
end
|
||||
|
||||
def assign_drop_params # rubocop:disable Metrics/AbcSize
|
||||
|
||||
@@ -256,6 +256,22 @@ class RecurringMeeting < ApplicationRecord
|
||||
schedule.next_occurrence(from_time)&.to_time
|
||||
end
|
||||
|
||||
def first_non_cancelled_occurrence(from_time: Time.current)
|
||||
skipped = []
|
||||
time = from_time
|
||||
|
||||
while (occurrence = next_occurrence(from_time: time))
|
||||
if scheduled_meetings.cancelled.exists?(start_time: occurrence)
|
||||
skipped << occurrence
|
||||
time = occurrence
|
||||
else
|
||||
return { occurrence:, skipped: }
|
||||
end
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def previous_occurrence(from_time: Time.current)
|
||||
schedule.previous_occurrence(from_time)&.to_time
|
||||
end
|
||||
|
||||
@@ -683,7 +683,9 @@ en:
|
||||
text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated."
|
||||
text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}"
|
||||
text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet."
|
||||
text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled."
|
||||
text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled."
|
||||
text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}."
|
||||
text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences."
|
||||
text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.'
|
||||
text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting."
|
||||
|
||||
|
||||
+74
-5
@@ -132,23 +132,92 @@ RSpec.describe "Recurring meetings duplicate in next meeting", :js do
|
||||
|
||||
context "with manage_agendas permission, but next occurrence is cancelled" do
|
||||
let(:current_user) { user_with_manage_permissions }
|
||||
let(:first_occurrence_time) { series.next_occurrence(from_time: Time.current) }
|
||||
let(:second_occurrence_time) { series.next_occurrence(from_time: first_occurrence_time) }
|
||||
|
||||
let!(:next_meeting) { nil }
|
||||
let!(:target_meeting) do
|
||||
RecurringMeetings::InitNextOccurrenceJob.perform_now(series, second_occurrence_time)
|
||||
series.meetings.not_templated.find_by(start_time: second_occurrence_time)
|
||||
end
|
||||
|
||||
let!(:cancelled_occurrence) do
|
||||
create(:scheduled_meeting,
|
||||
:cancelled,
|
||||
recurring_meeting: series,
|
||||
start_time: series.next_occurrence)
|
||||
start_time: first_occurrence_time)
|
||||
end
|
||||
|
||||
it "shows an error message" do
|
||||
let(:target_meeting_page) { Pages::Meetings::Show.new(target_meeting) }
|
||||
|
||||
it "skips the cancelled occurrence and duplicates to the next available one" do
|
||||
meeting_page.visit!
|
||||
meeting_page.expect_agenda_item(title: "Test agenda item")
|
||||
|
||||
meeting_page.duplicate_item_in_next_meeting(agenda_item)
|
||||
meeting_page.open_menu(agenda_item) do
|
||||
click_on "Duplicate"
|
||||
click_on "Duplicate in next occurrence"
|
||||
end
|
||||
|
||||
expect(page).to have_text "Unable to move to the next meeting since it has been cancelled."
|
||||
expect(page).to have_text("Duplicate in next occurrence?")
|
||||
expect(page).to have_text("Note: Skipping cancelled occurrence")
|
||||
|
||||
page.within_modal "Duplicate in next occurrence?" do
|
||||
click_on "Duplicate"
|
||||
end
|
||||
|
||||
expect_and_dismiss_flash(message: "Agenda item duplicated in the next meeting")
|
||||
|
||||
target_meeting_page.visit!
|
||||
target_meeting_page.expect_agenda_item(title: "Test agenda item")
|
||||
end
|
||||
end
|
||||
|
||||
context "with manage_agendas permission, but multiple next occurrences are cancelled" do
|
||||
let(:current_user) { user_with_manage_permissions }
|
||||
let(:first_occurrence_time) { series.next_occurrence(from_time: Time.current) }
|
||||
let(:second_occurrence_time) { series.next_occurrence(from_time: first_occurrence_time) }
|
||||
let(:third_occurrence_time) { series.next_occurrence(from_time: second_occurrence_time) }
|
||||
|
||||
let!(:target_meeting) do
|
||||
RecurringMeetings::InitNextOccurrenceJob.perform_now(series, third_occurrence_time)
|
||||
series.meetings.not_templated.find_by(start_time: third_occurrence_time)
|
||||
end
|
||||
|
||||
let!(:first_cancelled_occurrence) do
|
||||
create(:scheduled_meeting,
|
||||
:cancelled,
|
||||
recurring_meeting: series,
|
||||
start_time: first_occurrence_time)
|
||||
end
|
||||
let!(:second_cancelled_occurrence) do
|
||||
create(:scheduled_meeting,
|
||||
:cancelled,
|
||||
recurring_meeting: series,
|
||||
start_time: second_occurrence_time)
|
||||
end
|
||||
|
||||
let(:target_meeting_page) { Pages::Meetings::Show.new(target_meeting) }
|
||||
|
||||
it "skips all cancelled occurrences and shows the count in the dialog" do
|
||||
meeting_page.visit!
|
||||
meeting_page.expect_agenda_item(title: "Test agenda item")
|
||||
|
||||
meeting_page.open_menu(agenda_item) do
|
||||
click_on "Duplicate"
|
||||
click_on "Duplicate in next occurrence"
|
||||
end
|
||||
|
||||
expect(page).to have_text("Duplicate in next occurrence?")
|
||||
expect(page).to have_text("Note: Skipping 2 cancelled occurrences")
|
||||
|
||||
page.within_modal "Duplicate in next occurrence?" do
|
||||
click_on "Duplicate"
|
||||
end
|
||||
|
||||
expect_and_dismiss_flash(message: "Agenda item duplicated in the next meeting")
|
||||
|
||||
target_meeting_page.visit!
|
||||
target_meeting_page.expect_agenda_item(title: "Test agenda item")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+48
-4
@@ -102,20 +102,64 @@ RSpec.describe "Recurring meetings move to next meeting", :js do
|
||||
|
||||
context "with manage_agendas permission, but next occurrence is cancelled" do
|
||||
let(:current_user) { user_with_manage_permissions }
|
||||
let(:first_occurrence_time) { series.next_occurrence(from_time: Time.current) }
|
||||
let!(:cancelled_occurrence) do
|
||||
create(:scheduled_meeting,
|
||||
:cancelled,
|
||||
recurring_meeting: series,
|
||||
start_time: series.next_occurrence(from_time: Time.current))
|
||||
start_time: first_occurrence_time)
|
||||
end
|
||||
|
||||
it "shows the move to next meeting option" do
|
||||
it "skips the cancelled occurrence and moves to the next available one" do
|
||||
meeting_page.visit!
|
||||
meeting_page.expect_agenda_item(title: "Test notes")
|
||||
|
||||
meeting_page.move_item_to_next_meeting(agenda_item)
|
||||
meeting_page.select_action(agenda_item, "Move to next meeting")
|
||||
|
||||
expect(page).to have_text "Unable to move to the next meeting since it has been cancelled."
|
||||
expect(page).to have_text("Move to next meeting?")
|
||||
expect(page).to have_text("Note: Skipping cancelled occurrence")
|
||||
|
||||
page.within_modal "Move to next meeting?" do
|
||||
click_on "Move"
|
||||
end
|
||||
|
||||
expect_and_dismiss_flash(message: "Agenda item moved to the next meeting")
|
||||
meeting_page.expect_no_agenda_item(title: "Test notes")
|
||||
end
|
||||
end
|
||||
|
||||
context "with manage_agendas permission, but multiple next occurrences are cancelled" do
|
||||
let(:current_user) { user_with_manage_permissions }
|
||||
let(:first_occurrence_time) { series.next_occurrence(from_time: Time.current) }
|
||||
let(:second_occurrence_time) { series.next_occurrence(from_time: first_occurrence_time) }
|
||||
let!(:first_cancelled_occurrence) do
|
||||
create(:scheduled_meeting,
|
||||
:cancelled,
|
||||
recurring_meeting: series,
|
||||
start_time: first_occurrence_time)
|
||||
end
|
||||
let!(:second_cancelled_occurrence) do
|
||||
create(:scheduled_meeting,
|
||||
:cancelled,
|
||||
recurring_meeting: series,
|
||||
start_time: second_occurrence_time)
|
||||
end
|
||||
|
||||
it "skips all cancelled occurrences and shows the count in the dialog" do
|
||||
meeting_page.visit!
|
||||
meeting_page.expect_agenda_item(title: "Test notes")
|
||||
|
||||
meeting_page.select_action(agenda_item, "Move to next meeting")
|
||||
|
||||
expect(page).to have_text("Move to next meeting?")
|
||||
expect(page).to have_text("Note: Skipping 2 cancelled occurrences")
|
||||
|
||||
page.within_modal "Move to next meeting?" do
|
||||
click_on "Move"
|
||||
end
|
||||
|
||||
expect_and_dismiss_flash(message: "Agenda item moved to the next meeting")
|
||||
meeting_page.expect_no_agenda_item(title: "Test notes")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user