Compare commits
3 Commits
c1dde7914c
...
28eddb22ab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28eddb22ab | ||
|
|
a34eb7aa38 | ||
|
|
aa68885b84 |
@@ -5,7 +5,7 @@
|
|||||||
class Promoter::EventsController < ApplicationController
|
class Promoter::EventsController < ApplicationController
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
before_action :ensure_can_manage_events!
|
before_action :ensure_can_manage_events!
|
||||||
before_action :set_event, only: [ :show, :edit, :update, :destroy, :publish, :unpublish, :cancel, :mark_sold_out ]
|
before_action :set_event, only: [ :show, :edit, :update, :destroy, :publish, :unpublish, :cancel, :mark_sold_out, :duplicate ]
|
||||||
|
|
||||||
# Display all events for the current promoter
|
# Display all events for the current promoter
|
||||||
def index
|
def index
|
||||||
@@ -93,6 +93,18 @@ class Promoter::EventsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Duplicate an event and all its ticket types
|
||||||
|
def duplicate
|
||||||
|
clone_ticket_types = params[:clone_ticket_types] == "true"
|
||||||
|
@new_event = @event.duplicate(clone_ticket_types: clone_ticket_types)
|
||||||
|
|
||||||
|
if @new_event
|
||||||
|
redirect_to edit_promoter_event_path(@new_event), notice: "Événement dupliqué avec succès! Vous pouvez maintenant modifier les détails de l'événement copié."
|
||||||
|
else
|
||||||
|
redirect_to promoter_event_path(@event), alert: "Erreur lors de la duplication de l'événement."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def ensure_can_manage_events!
|
def ensure_can_manage_events!
|
||||||
|
|||||||
57
app/javascript/controllers/event_duplication_controller.js
Normal file
57
app/javascript/controllers/event_duplication_controller.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { Controller } from "@hotwired/stimulus"
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = ["modal", "cloneTicketTypes"]
|
||||||
|
static values = {
|
||||||
|
duplicateUrl: String
|
||||||
|
}
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
// Close modal when clicking outside
|
||||||
|
this.modalTarget.addEventListener('click', (event) => {
|
||||||
|
if (event.target === this.modalTarget) {
|
||||||
|
this.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
open() {
|
||||||
|
this.modalTarget.classList.remove('hidden')
|
||||||
|
document.body.classList.add('overflow-hidden')
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.modalTarget.classList.add('hidden')
|
||||||
|
document.body.classList.remove('overflow-hidden')
|
||||||
|
}
|
||||||
|
|
||||||
|
duplicate() {
|
||||||
|
const cloneTicketTypes = this.cloneTicketTypesTarget.checked
|
||||||
|
|
||||||
|
// Create form data
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('clone_ticket_types', cloneTicketTypes)
|
||||||
|
formData.append('authenticity_token', document.querySelector('meta[name="csrf-token"]').getAttribute('content'))
|
||||||
|
|
||||||
|
// Send request to duplicate endpoint
|
||||||
|
fetch(this.duplicateUrlValue, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (response.redirected) {
|
||||||
|
window.location.href = response.url
|
||||||
|
} else {
|
||||||
|
return response.json()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error)
|
||||||
|
alert('Erreur lors de la duplication de l\'événement.')
|
||||||
|
this.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,3 +27,6 @@ application.register("event-form", EventFormController);
|
|||||||
|
|
||||||
import CountdownController from "./countdown_controller";
|
import CountdownController from "./countdown_controller";
|
||||||
application.register("countdown", CountdownController);
|
application.register("countdown", CountdownController);
|
||||||
|
|
||||||
|
import EventDuplicationController from "./event_duplication_controller";
|
||||||
|
application.register("event-duplication", EventDuplicationController);
|
||||||
|
|||||||
@@ -102,6 +102,35 @@ class Event < ApplicationRecord
|
|||||||
!!allow_booking_during_event
|
!!allow_booking_during_event
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Duplicate an event with all its ticket types
|
||||||
|
def duplicate(clone_ticket_types: true)
|
||||||
|
# Duplicate the event
|
||||||
|
new_event = self.dup
|
||||||
|
new_event.name = "Copie de #{name}"
|
||||||
|
new_event.slug = "#{slug}-copy-#{Time.current.to_i}"
|
||||||
|
new_event.state = :draft
|
||||||
|
new_event.created_at = Time.current
|
||||||
|
new_event.updated_at = Time.current
|
||||||
|
|
||||||
|
Event.transaction do
|
||||||
|
if new_event.save
|
||||||
|
# Duplicate all ticket types if requested
|
||||||
|
if clone_ticket_types
|
||||||
|
ticket_types.each do |ticket_type|
|
||||||
|
new_ticket_type = ticket_type.dup
|
||||||
|
new_ticket_type.event = new_event
|
||||||
|
new_ticket_type.save!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
new_event
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Determine if we should perform server-side geocoding
|
# Determine if we should perform server-side geocoding
|
||||||
|
|||||||
@@ -1,5 +1,52 @@
|
|||||||
<% content_for(:title, @event.name) %>
|
<% content_for(:title, @event.name) %>
|
||||||
|
|
||||||
|
<div data-controller="event-duplication" data-event-duplication-duplicate-url-value="<%= duplicate_promoter_event_path(@event) %>">
|
||||||
|
<!-- Modal -->
|
||||||
|
<div data-event-duplication-target="modal" class="fixed inset-0 z-50 hidden overflow-y-auto">
|
||||||
|
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
||||||
|
<!-- Background overlay -->
|
||||||
|
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div>
|
||||||
|
|
||||||
|
<!-- Modal container -->
|
||||||
|
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
|
||||||
|
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||||
|
<div class="sm:flex sm:items-start">
|
||||||
|
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
|
<i data-lucide="copy" class="h-6 w-6 text-blue-600"></i>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-title">
|
||||||
|
Dupliquer l'événement
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2">
|
||||||
|
<p class="text-sm text-gray-500">
|
||||||
|
Choisissez les options de duplication pour "<%= @event.name %>".
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<input data-event-duplication-target="cloneTicketTypes" id="cloneTicketTypes" type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" checked>
|
||||||
|
<label for="cloneTicketTypes" class="ml-2 block text-sm text-gray-900">
|
||||||
|
Dupliquer également les types de billets (<%= @event.ticket_types.count %> type(s))
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
||||||
|
<button type="button" data-action="click->event-duplication#duplicate" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
|
||||||
|
Dupliquer
|
||||||
|
</button>
|
||||||
|
<button type="button" data-action="click->event-duplication#close" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
|
||||||
|
Annuler
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
|
|
||||||
<!-- Breadcrumb -->
|
<!-- Breadcrumb -->
|
||||||
@@ -39,6 +86,11 @@
|
|||||||
Modifier
|
Modifier
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<button type="button" data-action="click->event-duplication#open" class="w-full sm:w-auto inline-flex items-center justify-center px-4 py-2 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 transition-colors duration-200">
|
||||||
|
<i data-lucide="copy" class="w-4 h-4 mr-2"></i>
|
||||||
|
Dupliquer
|
||||||
|
</button>
|
||||||
|
|
||||||
<% if @event.draft? %>
|
<% if @event.draft? %>
|
||||||
<% if @event.ticket_types.blank? %>
|
<% if @event.ticket_types.blank? %>
|
||||||
<%= button_to publish_promoter_event_path(@event), method: :patch, disabled: true, class: "w-full sm:w-auto inline-flex items-center justify-center px-4 py-2 bg-gray-400 text-white font-medium rounded-lg cursor-not-allowed transition-colors duration-200", title: "Vous devez créer au moins un type de billet avant de publier" do %>
|
<%= button_to publish_promoter_event_path(@event), method: :patch, disabled: true, class: "w-full sm:w-auto inline-flex items-center justify-center px-4 py-2 bg-gray-400 text-white font-medium rounded-lg cursor-not-allowed transition-colors duration-200", title: "Vous devez créer au moins un type de billet avant de publier" do %>
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ Rails.application.routes.draw do
|
|||||||
patch :unpublish
|
patch :unpublish
|
||||||
patch :cancel
|
patch :cancel
|
||||||
patch :mark_sold_out
|
patch :mark_sold_out
|
||||||
|
post :duplicate
|
||||||
end
|
end
|
||||||
|
|
||||||
# Nested ticket types routes
|
# Nested ticket types routes
|
||||||
|
|||||||
@@ -26,5 +26,103 @@ class Promoter::EventsControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_not_includes assigns(:events), other_event
|
assert_not_includes assigns(:events), other_event
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "should duplicate an event with ticket types" do
|
||||||
|
sign_in @promoter
|
||||||
|
|
||||||
|
# Create ticket types for the event
|
||||||
|
ticket_type1 = TicketType.create!(
|
||||||
|
name: "Standard Ticket",
|
||||||
|
description: "A standard ticket for the event with all the basic access",
|
||||||
|
price_cents: 2000,
|
||||||
|
quantity: 100,
|
||||||
|
sale_start_at: 1.day.ago,
|
||||||
|
sale_end_at: @event.start_time - 1.hour,
|
||||||
|
event: @event
|
||||||
|
)
|
||||||
|
|
||||||
|
ticket_type2 = TicketType.create!(
|
||||||
|
name: "VIP Ticket",
|
||||||
|
description: "A VIP ticket for the event with special access",
|
||||||
|
price_cents: 5000,
|
||||||
|
quantity: 50,
|
||||||
|
sale_start_at: 1.day.ago,
|
||||||
|
sale_end_at: @event.start_time - 1.hour,
|
||||||
|
event: @event
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify that ticket types were created successfully
|
||||||
|
assert ticket_type1.valid?
|
||||||
|
assert ticket_type2.valid?
|
||||||
|
|
||||||
|
# Duplicate the event
|
||||||
|
assert_difference('Event.count', 1) do
|
||||||
|
post duplicate_promoter_event_path(@event), params: { clone_ticket_types: "true" }
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check that the new event was created
|
||||||
|
assert_redirected_to edit_promoter_event_path(Event.last)
|
||||||
|
assert_equal "Événement dupliqué avec succès! Vous pouvez maintenant modifier les détails de l'événement copié.", flash[:notice]
|
||||||
|
|
||||||
|
# Check that the new event has the correct attributes
|
||||||
|
new_event = Event.last
|
||||||
|
assert_equal "Copie de #{@event.name}", new_event.name
|
||||||
|
assert_equal "draft", new_event.state
|
||||||
|
assert_equal @event.venue_name, new_event.venue_name
|
||||||
|
assert_equal @event.venue_address, new_event.venue_address
|
||||||
|
|
||||||
|
# Check that ticket types were duplicated
|
||||||
|
assert_equal 2, new_event.ticket_types.count
|
||||||
|
assert_equal "Standard Ticket", new_event.ticket_types.find_by(name: "Standard Ticket").name
|
||||||
|
assert_equal "VIP Ticket", new_event.ticket_types.find_by(name: "VIP Ticket").name
|
||||||
|
end
|
||||||
|
|
||||||
|
test "should duplicate an event without ticket types" do
|
||||||
|
sign_in @promoter
|
||||||
|
|
||||||
|
# Create ticket types for the event
|
||||||
|
ticket_type1 = TicketType.create!(
|
||||||
|
name: "Standard Ticket",
|
||||||
|
description: "A standard ticket for the event with all the basic access",
|
||||||
|
price_cents: 2000,
|
||||||
|
quantity: 100,
|
||||||
|
sale_start_at: 1.day.ago,
|
||||||
|
sale_end_at: @event.start_time - 1.hour,
|
||||||
|
event: @event
|
||||||
|
)
|
||||||
|
|
||||||
|
ticket_type2 = TicketType.create!(
|
||||||
|
name: "VIP Ticket",
|
||||||
|
description: "A VIP ticket for the event with special access",
|
||||||
|
price_cents: 5000,
|
||||||
|
quantity: 50,
|
||||||
|
sale_start_at: 1.day.ago,
|
||||||
|
sale_end_at: @event.start_time - 1.hour,
|
||||||
|
event: @event
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify that ticket types were created successfully
|
||||||
|
assert ticket_type1.valid?
|
||||||
|
assert ticket_type2.valid?
|
||||||
|
|
||||||
|
# Duplicate the event without ticket types
|
||||||
|
assert_difference('Event.count', 1) do
|
||||||
|
post duplicate_promoter_event_path(@event), params: { clone_ticket_types: "false" }
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check that the new event was created
|
||||||
|
assert_redirected_to edit_promoter_event_path(Event.last)
|
||||||
|
assert_equal "Événement dupliqué avec succès! Vous pouvez maintenant modifier les détails de l'événement copié.", flash[:notice]
|
||||||
|
|
||||||
|
# Check that the new event has the correct attributes
|
||||||
|
new_event = Event.last
|
||||||
|
assert_equal "Copie de #{@event.name}", new_event.name
|
||||||
|
assert_equal "draft", new_event.state
|
||||||
|
assert_equal @event.venue_name, new_event.venue_name
|
||||||
|
assert_equal @event.venue_address, new_event.venue_address
|
||||||
|
|
||||||
|
# Check that ticket types were NOT duplicated
|
||||||
|
assert_equal 0, new_event.ticket_types.count
|
||||||
|
end
|
||||||
|
|
||||||
# Add tests for new, create, etc. as needed
|
# Add tests for new, create, etc. as needed
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -271,4 +271,50 @@ class EventTest < ActiveSupport::TestCase
|
|||||||
event = Event.new
|
event = Event.new
|
||||||
assert_not event.allow_booking_during_event?
|
assert_not event.allow_booking_during_event?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "should duplicate event with ticket types" do
|
||||||
|
user = User.create!(email: "test@example.com", password: "password123", password_confirmation: "password123")
|
||||||
|
event = Event.create!(name: "Original Event", slug: "original", description: "A description that is sufficiently long", venue_name: "v", venue_address: "a", user: user, latitude: 48.0, longitude: 2.0, start_time: 1.week.from_now, state: :published)
|
||||||
|
|
||||||
|
# Create ticket types
|
||||||
|
ticket_type1 = TicketType.create!(name: "Standard", description: "A standard ticket for the event", price_cents: 2000, quantity: 100, sale_start_at: 1.day.ago, sale_end_at: event.start_time - 1.hour, event: event)
|
||||||
|
ticket_type2 = TicketType.create!(name: "VIP", description: "A VIP ticket for the event", price_cents: 5000, quantity: 50, sale_start_at: 1.day.ago, sale_end_at: event.start_time - 1.hour, event: event)
|
||||||
|
|
||||||
|
# Duplicate the event
|
||||||
|
duplicated_event = event.duplicate
|
||||||
|
|
||||||
|
# Check that duplication was successful
|
||||||
|
assert_not_nil duplicated_event
|
||||||
|
assert_equal "Copie de #{event.name}", duplicated_event.name
|
||||||
|
assert_equal "draft", duplicated_event.state
|
||||||
|
assert_equal event.venue_name, duplicated_event.venue_name
|
||||||
|
assert_equal event.venue_address, duplicated_event.venue_address
|
||||||
|
|
||||||
|
# Check that ticket types were duplicated
|
||||||
|
assert_equal 2, duplicated_event.ticket_types.count
|
||||||
|
assert_equal "Standard", duplicated_event.ticket_types.find_by(name: "Standard").name
|
||||||
|
assert_equal "VIP", duplicated_event.ticket_types.find_by(name: "VIP").name
|
||||||
|
end
|
||||||
|
|
||||||
|
test "should duplicate event without ticket types" do
|
||||||
|
user = User.create!(email: "test@example.com", password: "password123", password_confirmation: "password123")
|
||||||
|
event = Event.create!(name: "Original Event", slug: "original", description: "A description that is sufficiently long", venue_name: "v", venue_address: "a", user: user, latitude: 48.0, longitude: 2.0, start_time: 1.week.from_now, state: :published)
|
||||||
|
|
||||||
|
# Create ticket types
|
||||||
|
ticket_type1 = TicketType.create!(name: "Standard", description: "A standard ticket for the event", price_cents: 2000, quantity: 100, sale_start_at: 1.day.ago, sale_end_at: event.start_time - 1.hour, event: event)
|
||||||
|
ticket_type2 = TicketType.create!(name: "VIP", description: "A VIP ticket for the event", price_cents: 5000, quantity: 50, sale_start_at: 1.day.ago, sale_end_at: event.start_time - 1.hour, event: event)
|
||||||
|
|
||||||
|
# Duplicate the event without ticket types
|
||||||
|
duplicated_event = event.duplicate(clone_ticket_types: false)
|
||||||
|
|
||||||
|
# Check that duplication was successful
|
||||||
|
assert_not_nil duplicated_event
|
||||||
|
assert_equal "Copie de #{event.name}", duplicated_event.name
|
||||||
|
assert_equal "draft", duplicated_event.state
|
||||||
|
assert_equal event.venue_name, duplicated_event.venue_name
|
||||||
|
assert_equal event.venue_address, duplicated_event.venue_address
|
||||||
|
|
||||||
|
# Check that ticket types were NOT duplicated
|
||||||
|
assert_equal 0, duplicated_event.ticket_types.count
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user