Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ gem 'cloudinary'

# for internationalizing
gem 'rails-i18n'
# Windows: timezone data (required on Windows for tzinfo)
gem 'tzinfo-data', platforms: %i[ windows jruby ]

# as authentification framework
gem 'devise'
Expand Down
21 changes: 8 additions & 13 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,7 @@ GEM
faraday (~> 2.0)
fastimage (2.3.0)
feature (1.4.0)
ffi (1.17.0-arm64-darwin)
ffi (1.17.0-x86_64-darwin)
ffi (1.17.0-x64-mingw-ucrt)
ffi (1.17.0-x86_64-linux-gnu)
font-awesome-sass (6.5.1)
sassc (~> 2.0)
Expand Down Expand Up @@ -384,9 +383,7 @@ GEM
next_rails (1.3.0)
colorize (>= 0.8.1)
nio4r (2.7.0)
nokogiri (1.16.6-arm64-darwin)
racc (~> 1.4)
nokogiri (1.16.6-x86_64-darwin)
nokogiri (1.16.6-x64-mingw-ucrt)
racc (~> 1.4)
nokogiri (1.16.6-x86_64-linux)
racc (~> 1.4)
Expand Down Expand Up @@ -644,8 +641,7 @@ GEM
actionpack (>= 5.2)
activesupport (>= 5.2)
sprockets (>= 3.0.0)
sqlite3 (1.7.2-arm64-darwin)
sqlite3 (1.7.2-x86_64-darwin)
sqlite3 (1.7.2-x64-mingw-ucrt)
sqlite3 (1.7.2-x86_64-linux)
ssrf_filter (1.1.2)
stripe (5.55.0)
Expand All @@ -672,6 +668,8 @@ GEM
turbolinks-source (5.2.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
tzinfo-data (1.2025.3)
tzinfo (>= 1.0.0)
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
unicode-display_width (2.5.0)
Expand Down Expand Up @@ -707,11 +705,7 @@ GEM
zeitwerk (2.6.13)

PLATFORMS
arm64-darwin-20
arm64-darwin-23
arm64-darwin-24
x86_64-darwin-21
x86_64-darwin-23
x64-mingw-ucrt
x86_64-linux

DEPENDENCIES
Expand Down Expand Up @@ -824,6 +818,7 @@ DEPENDENCIES
timecop
transitions
turbolinks
tzinfo-data
uglifier (>= 1.3.0)
unobtrusive_flash (>= 3)
web-console
Expand All @@ -832,7 +827,7 @@ DEPENDENCIES
whenever

RUBY VERSION
ruby 3.3.8p144
ruby 3.3.10p183

BUNDLED WITH
2.5.6
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# [Snap!Con](https://snapcon.org) Base Repository
## [Snap!Con](https://snapcon.org) Production Repository
[![Specs](https://github.com/snap-cloud/snapcon/actions/workflows/spec.yml/badge.svg)](https://github.com/snap-cloud/snapcon/actions/workflows/spec.yml)
[![Maintainability](https://qlty.sh/gh/snap-cloud/projects/snapcon/maintainability.svg)](https://qlty.sh/gh/snap-cloud/projects/snapcon)
[![Code Coverage](https://qlty.sh/gh/snap-cloud/projects/snapcon/coverage.svg)](https://qlty.sh/gh/snap-cloud/projects/snapcon)
Expand Down
2 changes: 1 addition & 1 deletion app/assets/stylesheets/application.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@
*= require selectize.bootstrap3
*= require conferences

*= require fullcalendar-scheduler/main.css
*= require fullcalendar-scheduler/main.css
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a spurious space, but nbd.

*/
1 change: 1 addition & 0 deletions app/controllers/admin/physical_tickets_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def index
@physical_tickets = @conference.physical_tickets
@tickets_sold_distribution = @conference.tickets_sold_distribution
@tickets_turnover_distribution = @conference.tickets_turnover_distribution
@ticket_sales_by_currency_distribution = @conference.ticket_sales_by_currency_distribution
end
end
end
36 changes: 36 additions & 0 deletions app/models/conference.rb
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,42 @@ def tickets_turnover_distribution
result
end

##
# Gross ticket sales per enabled currency (no refunds/fees).
# Returns a hash suitable for donut chart: { "USD" => { value:, color: }, ... }
# Only includes currencies that are enabled for the conference (base + conversions).
def ticket_sales_by_currency_distribution
result = {}
enabled_currencies = enabled_currencies_list
return result if enabled_currencies.blank?

# Gross sales: sum(amount_paid_cents * quantity) per currency, paid only
sums = ticket_purchases.paid.group(:currency).sum('amount_paid_cents * quantity')
enabled_currencies.each do |currency|
total_cents = sums[currency].to_i
next if total_cents.zero?

amount = Money.new(total_cents, currency)
label = "#{currency} (#{ApplicationController.helpers.humanized_money(amount)})"
# Use amount in major units (e.g. 50 for $50) so chart tooltip shows readable numbers, not cents
result[label] = {
'value' => (total_cents / 100.0).round(2),
'color' => "\##{Digest::MD5.hexdigest(currency)[0..5]}"
}
end
result
end

##
# List of currencies enabled for this conference (base + conversion targets).
def enabled_currencies_list
base = tickets.first&.price_currency
return [] if base.blank?

targets = currency_conversions.pluck(:to_currency).uniq
[base] | targets
end

##
# Calculates the overall program minutes
#
Expand Down
3 changes: 3 additions & 0 deletions app/views/admin/physical_tickets/index.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
.col-md-4
= render 'donut_chart', title: 'Tickets turnover',
combined_data: @tickets_turnover_distribution
.col-md-4
= render 'donut_chart', title: 'Gross ticket sales by currency',
combined_data: @ticket_sales_by_currency_distribution
%br
- if @physical_tickets.any?
.row
Expand Down
7 changes: 4 additions & 3 deletions config/puma.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,16 @@
# Workers do not work on JRuby or Windows (both of which do not support
# processes).
#
workers ENV.fetch('WEB_CONCURRENCY') { 2 }
worker_count = ENV.fetch('WEB_CONCURRENCY') { Gem.win_platform? ? 0 : 2 }.to_i
workers worker_count
# Set a 10 minute timeout in development for debugging.
worker_timeout 60 * 60 * 10 if ENV.fetch('RAILS_ENV') == 'development'
worker_timeout 60 * 60 * 10 if ENV.fetch('RAILS_ENV') == 'development' && worker_count > 0

# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory.
preload_app!
preload_app! if worker_count > 0

lowlevel_error_handler do |ex, env|
Sentry.capture_exception(
Expand Down
27 changes: 27 additions & 0 deletions info.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
project:
name: 'snapcon' # Your project name, e.g. Cue-to-cue
owner: 'CS169L-26' # Do not change
teamId: '04' # Your team number, e.g. 02
identities:
heroku: 'https://sp26-04-snapcon.herokuapp.com' # Your Heroku app URL
members:
member1: # Add all project members
name: 'Ethan' # Member 1 name
surname: 'Stone' # Member 1 last name
githubUsername: 'Ethan-Stone1' # Member 1 GitHub username
herokuEmail: 'ethanstone@berkeley.edu' # Member 1 Heroku username
member2:
name: 'Benjamin'
surname: 'Sikes'
githubUsername: 'sikesbc'
herokuEmail: 'bcsikes@berkeley.edu'
member3:
name: 'Yijun'
surname: 'Zhou'
githubUsername: 'zhouyijun111'
herokuEmail: 'zhouyijun@berkeley.edu'
member4:
name: 'Xinwei'
surname: 'Li'
githubUsername: 'li-xinwei'
herokuEmail: 'xinweili@berkeley.edu'
71 changes: 71 additions & 0 deletions lib/tasks/demo_ticket_sales.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# frozen_string_literal: true

namespace :data do
desc 'Add demo paid ticket sales (USD + EUR) for testing "Gross ticket sales by currency" chart. Usage: CONF=123123 rake data:demo_ticket_sales'
task demo_ticket_sales: :environment do
short_title = ENV['CONF'] || Conference.first&.short_title
conference = Conference.find_by(short_title: short_title)
unless conference
puts "Conference not found. Set CONF=short_title (e.g. CONF=123123) or create a conference first."
next
end

base_currency = conference.tickets.first&.price_currency || 'USD'
# Ensure we have a non-free ticket for paid sales
paid_ticket = conference.tickets.find_by(registration_ticket: false).presence || conference.tickets.first
unless paid_ticket
puts "No ticket found for conference #{short_title}."
next
end

# If ticket is free, create a paid "Supporter" ticket
if paid_ticket.price_cents.zero?
paid_ticket = conference.tickets.create!(
title: 'Supporter',
price_cents: 2_000,
price_currency: base_currency,
description: 'Demo paid ticket',
registration_ticket: false,
visible: true
)
puts "Created paid ticket: #{paid_ticket.title} (#{Money.new(paid_ticket.price_cents, base_currency).format})"
end

# Add EUR conversion so "enabled currencies" includes EUR
if base_currency == 'USD' && conference.currency_conversions.find_by(from_currency: 'USD', to_currency: 'EUR').blank?
conference.currency_conversions.create!(from_currency: 'USD', to_currency: 'EUR', rate: 0.92)
puts 'Added USD -> EUR conversion (rate 0.92).'
end

user1 = User.first || User.create!(email: 'demo1@example.com', name: 'Demo User 1', password: 'password123456', confirmed_at: Time.current)
user2 = User.second || User.create!(email: 'demo2@example.com', name: 'Demo User 2', password: 'password123456', confirmed_at: Time.current)

# Payment + purchase in USD
payment_usd = Payment.create!(conference: conference, user: user1, currency: 'USD', status: :success, amount: 5_000)
purchase_usd = TicketPurchase.new(
conference: conference, user: user1, ticket: paid_ticket,
quantity: 2, currency: 'USD', amount_paid_cents: 2_500, amount_paid: 25.0
)
purchase_usd.payment = payment_usd
purchase_usd.save!(validate: false)
purchase_usd.update_columns(paid: true)
purchase_usd.quantity.times { purchase_usd.physical_tickets.create! }
puts "Created USD purchase: 2 x #{paid_ticket.title} = $50 (5000 cents)."

# Payment + purchase in EUR (if base is USD and we have conversion)
if conference.currency_conversions.exists?(to_currency: 'EUR')
payment_eur = Payment.create!(conference: conference, user: user2, currency: 'EUR', status: :success, amount: 4_600)
purchase_eur = TicketPurchase.new(
conference: conference, user: user2, ticket: paid_ticket,
quantity: 1, currency: 'EUR', amount_paid_cents: 4_600, amount_paid: 46.0
)
purchase_eur.payment = payment_eur
purchase_eur.save!(validate: false)
purchase_eur.update_columns(paid: true)
purchase_eur.physical_tickets.create!
puts "Created EUR purchase: 1 x #{paid_ticket.title} = 46 EUR (4600 cents)."
end

puts "Done. Refresh the Ticket Purchases page to see the 'Gross ticket sales by currency' chart."
end
end
Loading