houdini/lib/metrics/nonprofit_metrics.rb
Bradley M. Kuhn 6772312ea7 Relicense all .rb files under new project license.
The primary license of the project is changing to:
  AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later

with some specific files to be licensed under the one of two licenses:
   CC0-1.0
   LGPL-3.0-or-later

This commit is one of the many steps to relicense the entire codebase.

Documentation granting permission for this relicensing (from all past
contributors who hold copyrights) is on file with Software Freedom
Conservancy, Inc.
2018-03-25 15:10:40 -04:00

156 lines
6.2 KiB
Ruby

# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
module NonprofitMetrics
def self.payments(np_id)
Qx.select(
"(SUM(payments.gross_amount) / 100.0)::money::text AS total",
"(AVG(payments.gross_amount) / 100.0)::money::text AS average",
"(SUM(week.gross_amount) / 100.0)::money::text AS week",
"(SUM(month.gross_amount) / 100.0)::money::text AS month",
"(SUM(year.gross_amount) / 100.0)::money::text AS year",
)
.from(:payments)
.left_join(
['payments week', "week.id=payments.id AND week.date > date_trunc('week', NOW())"],
['payments month', "month.id=payments.id AND month.date > date_trunc('month', NOW())"],
['payments year', "year.id=payments.id AND year.date > date_trunc('year', NOW())"]
)
.where("payments.nonprofit_id=$id", id: np_id)
.execute.last
end
def self.recurring(np_id)
# total, average, this month
Qx.select(
"(SUM(recurring_donations.amount) / 100.0)::money::text AS total",
"(AVG(recurring_donations.amount) / 100.0)::money::text AS average",
"(SUM(month.amount) / 100.0)::money::text AS month",
)
.from(:recurring_donations)
.left_join("recurring_donations month", "month.id=recurring_donations.id AND month.created_at > date_trunc('month', NOW())")
.where("recurring_donations.active=TRUE")
.and_where("recurring_donations.n_failures < 3")
.and_where("recurring_donations.nonprofit_id=$id", id: np_id)
.execute.last
end
def self.supporters(np_id)
Qx.select(
"COUNT(supporters) AS total",
"COUNT(week) AS week",
"COUNT(month) AS month"
)
.from(:supporters)
.left_join("supporters week", "week.id=supporters.id AND week.created_at > date_trunc('week', NOW()) AND week.imported_at IS NULL")
.add_left_join("supporters month", "month.id=supporters.id AND month.created_at > date_trunc('month', NOW()) AND month.imported_at IS NULL")
.where("coalesce(supporters.deleted, FALSE) = FALSE")
.and_where("supporters.nonprofit_id=$id", id: np_id)
.execute.last
end
def self.recent_donations(np_id)
Qx.select(
"(payments.gross_amount / 100)::money::text AS amount",
"payments.date",
"payments.id AS payment_id",
"supporters.name AS supporter_name",
"supporters.email AS supporter_email"
)
.from(:payments)
.join("supporters", "payments.supporter_id=supporters.id")
.where("payments.nonprofit_id=$id", id: np_id)
.and_where("payments.kind IN ('Donation', 'RecurringDonation', 'Ticket')")
.limit(10)
.order_by("payments.date DESC")
.execute
end
def self.recent_supporters(np_id)
Qx.select("name", "email", "id", "created_at")
.from(:supporters)
.where("supporters.nonprofit_id=$id", id: np_id)
.and_where("coalesce(supporters.deleted, FALSE) = FALSE")
.and_where("supporters.import_id IS NULL")
.limit(10)
.order_by("supporters.created_at DESC")
.execute
end
def self.all_metrics(np_id)
keys = [:payments, :recurring, :supporters, :recent_donations, :recent_supporters, :published_campaigns]
keys.reduce({}) do |accum, elem|
accum[elem] = NonprofitMetrics.send(elem, np_id)
accum
end
end
def self.published_campaigns(np_id)
Qx.select(
"campaigns.name",
"campaigns.id",
"campaigns.created_at",
"campaigns.end_datetime",
"COUNT(supporters.id) AS supporter_count",
"(SUM(one_time.amount)/ 100)::money::text AS total_one_time",
"(SUM(recurring_donations.amount)/ 100)::money::text AS total_recurring"
)
.from(:campaigns)
.left_join("donations", "donations.campaign_id=campaigns.id")
.add_left_join("donations AS one_time", "donations.id=one_time.id AND one_time.recurring_donation_id IS NULL")
.add_left_join("recurring_donations", "recurring_donations.donation_id=donations.id AND recurring_donations.active=TRUE")
.add_left_join("supporters", "supporters.id=donations.supporter_id")
.group_by("campaigns.id")
.where("campaigns.nonprofit_id=$id", id: np_id)
.and_where("campaigns.published = TRUE")
.order_by("campaigns.end_datetime DESC")
.execute
end
# Given a starting date, ending date, and time interval,
# generate a hash for every date between start and end by the interval
# each hash has the format {'time_span' => date}
# each hash is nested in an outer hash, set to a key that is also the date, lol
# this is used in the payment_history query to fill in missing dates in the data.
def self.payment_history_timespans(params)
raise ArgumentError.new("Invalid timespan") unless ['year', 'month', 'week', 'day'].include? params[:timeSpan]
date_hash = {}
beginning_of = 'beginning_of_' + params[:timeSpan]
current_date = Chronic.parse(params[:startDate]).send(beginning_of)
end_date = Chronic.parse(params[:endDate]).send(beginning_of)
while current_date <= end_date
date = current_date.strftime("%F")
date_hash[date] = {'time_span' => date}
current_date += 1.send(params[:timeSpan])
end
return date_hash
end
def self.payment_history(params)
results = Qx.select(
"to_char(date_trunc('#{params[:timeSpan]}', MAX(payments.date)), 'YYYY-MM-DD') AS time_span",
"coalesce(SUM(payments.gross_amount), 0) AS total_cents",
"coalesce(SUM(onetime.gross_amount ), 0) AS onetime_cents",
"coalesce(SUM(recurring.gross_amount ), 0) AS recurring_cents",
"coalesce(SUM(tickets.gross_amount ), 0) AS tickets_cents"
)
.from(:payments)
.left_join(
["payments AS onetime", "onetime.id=payments.id AND onetime.kind='Donation'"],
["payments AS recurring", "recurring.id=payments.id AND recurring.kind='RecurringDonation'"],
["payments AS tickets", "tickets.id=payments.id AND tickets.kind='Ticket'"]
)
.where("payments.nonprofit_id" => params[:id])
.and_where("payments.date >= $d", d: params[:startDate])
.and_where("payments.date <= $d", d: params[:endDate])
.group_by("date_trunc('#{params[:timeSpan]}', payments.date)")
.order_by("MAX(payments.date)")
.execute
date_hash = payment_history_timespans(params)
return results.reduce(date_hash){|acc, r| acc[r['time_span']] = r; acc}.values
end
end