587 lines
22 KiB
Ruby
587 lines
22 KiB
Ruby
class JamRuby::AffiliatePartner < ActiveRecord::Base
|
|
self.table_name = 'affiliate_partners'
|
|
|
|
belongs_to :partner_user, :class_name => "JamRuby::User", :foreign_key => :partner_user_id, inverse_of: :affiliate_partner
|
|
has_one :school, class_name: "JamRuby::School"
|
|
has_one :retailer, class_name: "JamRuby::Retailer"
|
|
has_many :user_referrals, :class_name => "JamRuby::User", :foreign_key => :affiliate_referral_id
|
|
belongs_to :affiliate_legalese, :class_name => "JamRuby::AffiliateLegalese", :foreign_key => :legalese_id
|
|
has_many :sale_line_items, :class_name => 'JamRuby::SaleLineItem', foreign_key: :affiliate_referral_id
|
|
has_many :quarters, :class_name => 'JamRuby::AffiliateQuarterlyPayment', foreign_key: :affiliate_partner_id, inverse_of: :affiliate_partner
|
|
has_many :months, :class_name => 'JamRuby::AffiliateMonthlyPayment', foreign_key: :affiliate_partner_id, inverse_of: :affiliate_partner
|
|
has_many :traffic_totals, :class_name => 'JamRuby::AffiliateTrafficTotal', foreign_key: :affiliate_partner_id, inverse_of: :affiliate_partner
|
|
has_many :visits, :class_name => 'JamRuby::AffiliateReferralVisit', foreign_key: :affiliate_partner_id, inverse_of: :affiliate_partner
|
|
has_many :affiliate_distributions, :class_name => "JamRuby::AffiliateDistribution", foreign_key: :affiliate_referral_id
|
|
has_many :links, :class_name => "JamRuby::AffiliateLink", foreign_key: :affiliate_partner_id
|
|
attr_accessible :partner_name, :partner_code, :partner_user_id, :entity_type, :rate, as: :admin
|
|
|
|
ENTITY_TYPES = %w{ Individual Sole\ Proprietor Limited\ Liability\ Company\ (LLC) Partnership Trust/Estate S\ Corporation C\ Corporation Other }
|
|
|
|
KEY_ADDR1 = 'address1'
|
|
KEY_ADDR2 = 'address2'
|
|
KEY_CITY = 'city'
|
|
KEY_STATE = 'state'
|
|
KEY_POSTAL = 'postal_code'
|
|
KEY_COUNTRY = 'country'
|
|
|
|
GUITAR_CENTER = 'guitar_center'
|
|
|
|
# ten dollars in cents
|
|
PAY_THRESHOLD = 10 * 100
|
|
|
|
AFFILIATE_PARAMS="utm_source=affiliate&utm_medium=affiliate&utm_campaign=2015-affiliate-custom&affiliate="
|
|
|
|
ADDRESS_SCHEMA = {
|
|
KEY_ADDR1 => '',
|
|
KEY_ADDR2 => '',
|
|
KEY_CITY => '',
|
|
KEY_STATE => '',
|
|
KEY_POSTAL => '',
|
|
KEY_COUNTRY => '',
|
|
}
|
|
|
|
PARAM_REFERRAL = :ref
|
|
PARAM_COOKIE = :affiliate_ref
|
|
|
|
PARTNER_CODE_REGEX = /^[#{Regexp.escape('abcdefghijklmnopqrstuvwxyz0123456789-._+,')}]+{2,128}$/i
|
|
|
|
#validates :user_email, format: {with: JamRuby::User::VALID_EMAIL_REGEX}, :if => :user_email
|
|
#validates :partner_code, format: { with: PARTNER_CODE_REGEX }, :allow_blank => true
|
|
validates :entity_type, inclusion: {in: ENTITY_TYPES, message: "invalid entity type"}
|
|
|
|
#serialize :address, JSON
|
|
|
|
before_save do |record|
|
|
record.address ||= ADDRESS_SCHEMA.clone
|
|
record.entity_type ||= ENTITY_TYPES.first
|
|
record.partner_user_id = nil if record.partner_user_id == '' #for activeadmin coercion
|
|
end
|
|
|
|
def display_name
|
|
partner_name || (partner_user ? partner_user.name : 'abandoned')
|
|
end
|
|
|
|
def admin_url
|
|
APP_CONFIG.admin_root_url + "/admin/affiliates/#{id}"
|
|
end
|
|
|
|
# used by admin
|
|
def self.create_with_params(params={})
|
|
raise 'not supported'
|
|
oo = AffiliatePartner.new
|
|
oo.partner_name = params[:partner_name].try(:strip)
|
|
oo.partner_code = params[:partner_code].try(:strip).try(:downcase)
|
|
oo.partner_user = User.where(:email => params[:user_email].try(:strip)).limit(1).first
|
|
oo.partner_user_id = oo.partner_user.try(:id)
|
|
oo.entity_type = params[:entity_type] || ENTITY_TYPES.first
|
|
oo.save
|
|
oo
|
|
end
|
|
|
|
# used by web
|
|
def self.create_with_web_params(user, params={})
|
|
oo = AffiliatePartner.new
|
|
oo.partner_name = params[:partner_name].try(:strip)
|
|
oo.partner_user = user if user # user is not required
|
|
oo.entity_type = params[:entity_type] || ENTITY_TYPES.first
|
|
oo.signed_at = Time.now
|
|
oo.save
|
|
oo
|
|
end
|
|
|
|
def self.create_from_school(school)
|
|
oo = AffiliatePartner.new
|
|
oo.partner_name = "Affiliate from School #{school.id}"
|
|
oo.partner_user = school.owner
|
|
oo.entity_type = 'Other'
|
|
oo.school = school
|
|
oo.signed_at = nil
|
|
oo.save
|
|
end
|
|
|
|
def self.create_from_retailer(retailer)
|
|
oo = AffiliatePartner.new
|
|
oo.partner_name = "Affiliate from Retailer #{retailer.id}"
|
|
oo.partner_user = retailer.owner
|
|
oo.entity_type = 'Other'
|
|
oo.retailer = retailer
|
|
oo.signed_at = nil
|
|
oo.save
|
|
end
|
|
|
|
|
|
def self.coded_id(code=nil)
|
|
self.where(:partner_code => code).limit(1).pluck(:id).first if code.present?
|
|
end
|
|
|
|
def self.is_code?(code)
|
|
self.where(:partner_code => code).limit(1).pluck(:id).present?
|
|
end
|
|
|
|
def referrals_by_date
|
|
by_date = User.where(:affiliate_referral_id => self.id)
|
|
.group('DATE(created_at)')
|
|
.having("COUNT(*) > 0")
|
|
.order('date_created_at DESC')
|
|
.count
|
|
block_given? ? yield(by_date) : by_date
|
|
end
|
|
|
|
def signed_legalese(legalese)
|
|
self.affiliate_legalese = legalese
|
|
self.signed_at = Time.now
|
|
save!
|
|
end
|
|
|
|
def update_address_value(key, val)
|
|
self.address[key] = val
|
|
self.update_attribute(:address, self.address)
|
|
end
|
|
|
|
def address_value(key)
|
|
self.address[key]
|
|
end
|
|
|
|
def created_within_affiliate_window(user, sale_time)
|
|
sale_time - user.created_at < 3.years
|
|
end
|
|
|
|
def should_attribute_sale?(shopping_cart, user_to_check, instance)
|
|
raise "Not a JamTrack sale" if !shopping_cart.is_jam_track?
|
|
|
|
if created_within_affiliate_window(user_to_check, Time.now)
|
|
product_info = shopping_cart.product_info(instance)
|
|
# subtract the total quantity from the freebie quantity, to see how much we should attribute to them
|
|
real_quantity = product_info[:quantity].to_i - product_info[:marked_for_redeem].to_i
|
|
|
|
{fee_in_cents: (rate * product_info[:real_price]).round}
|
|
# if shopping_cart.is_lesson?
|
|
# applicable_rate = lesson_rate
|
|
# else
|
|
# applicable_rate = rate
|
|
# end
|
|
#{fee_in_cents: (product_info[:price] * 100 * real_quantity * applicable_rate.to_f).round}
|
|
|
|
# { fee_in_cents: (real_quantity * jamtrack_share_in_cents.to_f).round}
|
|
else
|
|
false
|
|
end
|
|
end
|
|
|
|
def cumulative_earnings_in_dollars
|
|
cumulative_earnings_in_cents.to_f / 100.to_f
|
|
end
|
|
|
|
def self.quarter_info(date)
|
|
|
|
year = date.year
|
|
|
|
# which quarter?
|
|
quarter = -1
|
|
if date.month >= 1 && date.month <= 3
|
|
quarter = 0
|
|
elsif date.month >= 4 && date.month <= 6
|
|
quarter = 1
|
|
elsif date.month >= 7 && date.month <= 9
|
|
quarter = 2
|
|
elsif date.month >= 10 && date.month <= 12
|
|
quarter = 3
|
|
end
|
|
|
|
raise 'quarter should never be -1' if quarter == -1
|
|
|
|
previous_quarter = quarter - 1
|
|
previous_year = date.year
|
|
if previous_quarter == -1
|
|
previous_quarter = 3
|
|
previous_year = year - 1
|
|
end
|
|
|
|
raise 'previous quarter should never be -1' if previous_quarter == -1
|
|
|
|
{year: year, quarter: quarter, previous_quarter: previous_quarter, previous_year: previous_year}
|
|
end
|
|
|
|
def self.did_quarter_elapse?(quarter_info, last_tallied_info)
|
|
if last_tallied_info.nil?
|
|
true
|
|
else
|
|
quarter_info == last_tallied_info
|
|
end
|
|
end
|
|
|
|
# meant to be run regularly; this routine will make summarized counts in the
|
|
# AffiliateQuarterlyPayment table
|
|
# AffiliatePartner.cumulative_earnings_in_cents, AffiliatePartner.referral_user_count
|
|
def self.tally_up(day)
|
|
|
|
AffiliatePartner.transaction do
|
|
quarter_info = quarter_info(day)
|
|
last_tallied_info = quarter_info(GenericState.affiliate_tallied_at) if GenericState.affiliate_tallied_at
|
|
|
|
quarter_elapsed = did_quarter_elapse?(quarter_info, last_tallied_info)
|
|
|
|
if quarter_elapsed
|
|
tally_monthly_payments(quarter_info[:previous_year], quarter_info[:previous_quarter])
|
|
tally_quarterly_payments(quarter_info[:previous_year], quarter_info[:previous_quarter])
|
|
end
|
|
tally_monthly_payments(quarter_info[:year], quarter_info[:quarter])
|
|
tally_quarterly_payments(quarter_info[:year], quarter_info[:quarter])
|
|
|
|
# we aren't tracking visits anymore, so we don't need to tally_traffic_totals
|
|
#tally_traffic_totals(GenericState.affiliate_tallied_at, day)
|
|
|
|
tally_partner_totals
|
|
|
|
state = GenericState.singleton
|
|
state.affiliate_tallied_at = day
|
|
state.save!
|
|
end
|
|
|
|
end
|
|
|
|
# this just makes sure that the quarter rows exist before later manipulations with UPDATEs
|
|
def self.ensure_quarters_exist(year, quarter)
|
|
|
|
sql = %{
|
|
INSERT INTO affiliate_quarterly_payments (quarter, year, affiliate_partner_id)
|
|
(SELECT #{quarter}, #{year}, affiliate_partners.id FROM affiliate_partners WHERE affiliate_partners.partner_user_id IS NOT NULL AND affiliate_partners.id NOT IN
|
|
(SELECT affiliate_partner_id FROM affiliate_quarterly_payments WHERE year = #{year} AND quarter = #{quarter}))
|
|
}
|
|
|
|
ActiveRecord::Base.connection.execute(sql)
|
|
end
|
|
|
|
# this just makes sure that the quarter rows exist before later manipulations with UPDATEs
|
|
def self.ensure_months_exist(year, quarter)
|
|
|
|
months = [1, 2, 3].collect! { |i| quarter * 3 + i }
|
|
|
|
months.each do |month|
|
|
sql = %{
|
|
INSERT INTO affiliate_monthly_payments (month, year, affiliate_partner_id)
|
|
(SELECT #{month}, #{year}, affiliate_partners.id FROM affiliate_partners WHERE affiliate_partners.partner_user_id IS NOT NULL AND affiliate_partners.id NOT IN
|
|
(SELECT affiliate_partner_id FROM affiliate_monthly_payments WHERE year = #{year} AND month = #{month}))
|
|
}
|
|
|
|
ActiveRecord::Base.connection.execute(sql)
|
|
end
|
|
end
|
|
|
|
|
|
def self.sale_items_subquery(start_date, end_date, table_name)
|
|
%{
|
|
FROM affiliate_distributions inner join sale_line_items ON affiliate_distributions.sale_line_item_id = sale_line_items.id
|
|
WHERE
|
|
(DATE(affiliate_distributions.created_at) >= DATE('#{start_date}') AND DATE(affiliate_distributions.created_at) <= DATE('#{end_date}'))
|
|
AND
|
|
affiliate_distributions.affiliate_referral_id = #{table_name}.affiliate_partner_id
|
|
}
|
|
end
|
|
|
|
def self.sale_items_refunded_subquery(start_date, end_date, table_name)
|
|
%{
|
|
FROM affiliate_distributions inner join sale_line_items ON affiliate_distributions.sale_line_item_id = sale_line_items.id
|
|
WHERE
|
|
(DATE(affiliate_distributions.affiliate_refunded_at) >= DATE('#{start_date}') AND DATE(affiliate_distributions.affiliate_refunded_at) <= DATE('#{end_date}'))
|
|
AND
|
|
affiliate_distributions.affiliate_referral_id = #{table_name}.affiliate_partner_id
|
|
AND
|
|
affiliate_distributions.affiliate_refunded = TRUE
|
|
}
|
|
end
|
|
|
|
def self.subscription_distribution_sub_query(start_date, end_date, table_name)
|
|
%{
|
|
FROM affiliate_distributions INNER JOIN affiliate_partners
|
|
ON affiliate_distributions.affiliate_referral_id = affiliate_partners.id
|
|
WHERE
|
|
affiliate_distributions.product_type = 'Subscription'
|
|
AND
|
|
(DATE(affiliate_distributions.created_at) >= DATE('#{start_date}'))
|
|
AND
|
|
(DATE(affiliate_distributions.created_at) <= DATE('#{end_date}'))
|
|
AND affiliate_distributions.affiliate_referral_id = #{table_name}.affiliate_partner_id
|
|
}
|
|
end
|
|
|
|
# total up quarters by looking in sale_line_items for items that are marked as having a affiliate_referral_id
|
|
# don't forget to substract any sale_line_items that have a affiliate_refunded = TRUE
|
|
def self.total_months(year, quarter)
|
|
|
|
months = [1, 2, 3].collect! { |i| quarter * 3 + i }
|
|
|
|
|
|
months.each do |month|
|
|
|
|
start_date, end_date = boundary_dates_for_month(year, month)
|
|
|
|
sql = %{
|
|
UPDATE affiliate_monthly_payments
|
|
SET
|
|
last_updated = NOW(),
|
|
jamtracks_sold =
|
|
COALESCE(
|
|
(SELECT COUNT(CASE WHEN sale_line_items.product_type = 'JamTrack' AND affiliate_distributions.affiliate_referral_fee_in_cents > 0 THEN 1 ELSE NULL END)
|
|
#{sale_items_subquery(start_date, end_date, 'affiliate_monthly_payments')}
|
|
), 0)
|
|
+
|
|
COALESCE(
|
|
(SELECT -COUNT(CASE WHEN sale_line_items.product_type = 'JamTrack' AND affiliate_distributions.affiliate_referral_fee_in_cents > 0 THEN 1 ELSE NULL END)
|
|
#{sale_items_refunded_subquery(start_date, end_date, 'affiliate_monthly_payments')}
|
|
), 0),
|
|
due_amount_in_cents =
|
|
COALESCE(
|
|
(SELECT SUM(affiliate_distributions.affiliate_referral_fee_in_cents)
|
|
#{sale_items_subquery(start_date, end_date, 'affiliate_monthly_payments')}
|
|
), 0)
|
|
+
|
|
COALESCE(
|
|
(SELECT -SUM(affiliate_distributions.affiliate_referral_fee_in_cents)
|
|
#{sale_items_refunded_subquery(start_date, end_date, 'affiliate_monthly_payments')}
|
|
), 0)
|
|
+
|
|
COALESCE(
|
|
(SELECT SUM(affiliate_distributions.affiliate_referral_fee_in_cents)
|
|
#{subscription_distribution_sub_query(start_date, end_date, 'affiliate_monthly_payments')}
|
|
), 0)
|
|
WHERE closed = FALSE AND year = #{year} AND month = #{month}
|
|
}
|
|
|
|
ActiveRecord::Base.connection.execute(sql)
|
|
end
|
|
end
|
|
|
|
# close any quarters that are done, so we don't manipulate them again
|
|
def self.close_months(year, quarter)
|
|
# close any quarters that occurred before this quarter
|
|
month = quarter * 3 + 1
|
|
|
|
sql = %{
|
|
UPDATE affiliate_monthly_payments
|
|
SET
|
|
closed = TRUE, closed_at = NOW()
|
|
WHERE year < #{year} AND month < #{month}
|
|
}
|
|
|
|
ActiveRecord::Base.connection.execute(sql)
|
|
|
|
end
|
|
|
|
# total up quarters by looking in sale_line_items for items that are marked as having a affiliate_referral_id
|
|
# don't forget to substract any sale_line_items that have a affiliate_refunded = TRUE
|
|
def self.total_quarters(year, quarter)
|
|
start_date, end_date = boundary_dates(year, quarter)
|
|
|
|
sql = %{
|
|
UPDATE affiliate_quarterly_payments
|
|
SET
|
|
last_updated = NOW(),
|
|
jamtracks_sold =
|
|
COALESCE(
|
|
(SELECT COUNT(CASE WHEN sale_line_items.product_type = 'JamTrack' AND affiliate_distributions.affiliate_referral_fee_in_cents > 0 THEN 1 ELSE NULL END)
|
|
#{sale_items_subquery(start_date, end_date, 'affiliate_quarterly_payments')}
|
|
), 0)
|
|
+
|
|
COALESCE(
|
|
(SELECT -COUNT(CASE WHEN sale_line_items.product_type = 'JamTrack' AND affiliate_distributions.affiliate_referral_fee_in_cents > 0 THEN 1 ELSE NULL END)
|
|
#{sale_items_refunded_subquery(start_date, end_date, 'affiliate_quarterly_payments')}
|
|
), 0),
|
|
due_amount_in_cents =
|
|
COALESCE(
|
|
(SELECT SUM(affiliate_distributions.affiliate_referral_fee_in_cents)
|
|
#{sale_items_subquery(start_date, end_date, 'affiliate_quarterly_payments')}
|
|
), 0)
|
|
+
|
|
COALESCE(
|
|
(SELECT -SUM(affiliate_distributions.affiliate_referral_fee_in_cents)
|
|
#{sale_items_refunded_subquery(start_date, end_date, 'affiliate_quarterly_payments')}
|
|
), 0)
|
|
+
|
|
COALESCE(
|
|
(SELECT SUM(affiliate_distributions.affiliate_referral_fee_in_cents)
|
|
#{subscription_distribution_sub_query(start_date, end_date, 'affiliate_quarterly_payments')}
|
|
), 0)
|
|
|
|
WHERE closed = FALSE AND paid = FALSE AND year = #{year} AND quarter = #{quarter}
|
|
}
|
|
|
|
ActiveRecord::Base.connection.execute(sql)
|
|
end
|
|
|
|
# close any quarters that are done, so we don't manipulate them again
|
|
def self.close_quarters(year, quarter)
|
|
# close any quarters that occurred before this quarter
|
|
sql = %{
|
|
UPDATE affiliate_quarterly_payments
|
|
SET
|
|
closed = TRUE, closed_at = NOW()
|
|
WHERE year < #{year} OR (year = #{year} AND quarter < #{quarter})
|
|
}
|
|
|
|
ActiveRecord::Base.connection.execute(sql)
|
|
end
|
|
|
|
def self.tally_quarterly_payments(year, quarter)
|
|
ensure_quarters_exist(year, quarter)
|
|
|
|
total_quarters(year, quarter)
|
|
|
|
close_quarters(year, quarter)
|
|
end
|
|
|
|
|
|
def self.tally_monthly_payments(year, quarter)
|
|
ensure_months_exist(year, quarter)
|
|
|
|
total_months(year, quarter)
|
|
|
|
close_months(year, quarter)
|
|
end
|
|
|
|
# This was added because the tally_traffic_totals runs once a day, which is not often enough to get a fresh count of signups
|
|
# so as users sign up, we increment the signups count for the day
|
|
# jam=# \d affiliate_traffic_totals
|
|
# Table "public.affiliate_traffic_totals"
|
|
# Column | Type | Modifiers
|
|
#----------------------+-----------------------------+------------------------
|
|
# day | date | not null
|
|
# signups | integer | not null default 0
|
|
# visits | integer | not null default 0
|
|
# affiliate_partner_id | integer | not null
|
|
# created_at | timestamp without time zone | not null default now()
|
|
|
|
def self.increment_signups(user)
|
|
sql = "SELECT count(day) FROM affiliate_traffic_totals WHERE day = '#{user.created_at.to_date}' AND affiliate_partner_id = #{user.affiliate_referral_id}"
|
|
count = ActiveRecord::Base.connection.execute(sql)
|
|
if count > 0
|
|
sql = %{
|
|
UPDATE affiliate_traffic_totals SET signups = signups + 1 WHERE day = '#{user.created_at.to_date}' AND affiliate_partner_id = #{user.affiliate_referral_id}
|
|
}
|
|
else
|
|
sql = %{
|
|
INSERT INTO affiliate_traffic_totals (day, signups, visits, affiliate_partner_id) VALUES ('#{user.created_at.to_date}', 1, 0, #{user.affiliate_referral_id})
|
|
}
|
|
end
|
|
ActiveRecord::Base.connection.execute(sql)
|
|
|
|
end
|
|
|
|
def self.tally_partner_totals
|
|
sql = %{
|
|
UPDATE affiliate_partners SET
|
|
referral_user_count = (SELECT count(*) FROM users WHERE affiliate_partners.id = users.affiliate_referral_id),
|
|
cumulative_earnings_in_cents = (SELECT COALESCE(SUM(due_amount_in_cents), 0) FROM affiliate_quarterly_payments AS aqp WHERE aqp.affiliate_partner_id = affiliate_partners.id AND closed = TRUE and paid = TRUE)
|
|
}
|
|
ActiveRecord::Base.connection.execute(sql)
|
|
end
|
|
|
|
def self.tally_traffic_totals(last_tallied_at, target_day)
|
|
|
|
if last_tallied_at
|
|
start_date = last_tallied_at.to_date
|
|
end_date = target_day.to_date
|
|
else
|
|
start_date = target_day.to_date - 1
|
|
end_date = target_day.to_date
|
|
end
|
|
|
|
if start_date == end_date
|
|
return
|
|
end
|
|
|
|
# Because we now increment_signups, as users sign up, it's possible this row already exists. however, if there were no signups and only visits, there may still not be a row here.
|
|
# So we need to insert the rows, and if they already exist, the INSERT will be a no-op, as long as we also update this statement to not fail if the row already exists.
|
|
sql = %{
|
|
INSERT INTO affiliate_traffic_totals(SELECT day, 0, 0, ap.id FROM affiliate_partners AS ap CROSS JOIN (select (generate_series('#{start_date}', '#{end_date - 1}', '1 day'::interval))::date as day) AS lurp)
|
|
}
|
|
ActiveRecord::Base.connection.execute(sql)
|
|
|
|
sql = %{
|
|
UPDATE affiliate_traffic_totals traffic SET visits = COALESCE((SELECT COALESCE(count(affiliate_partner_id), 0) FROM affiliate_referral_visits v WHERE DATE(v.created_at) >= DATE('#{start_date}') AND DATE(v.created_at) < DATE('#{end_date}') AND v.created_at::date = traffic.day AND v.affiliate_partner_id = traffic.affiliate_partner_id GROUP BY affiliate_partner_id, v.created_at::date ), 0) WHERE traffic.day >= DATE('#{start_date}') AND traffic.day < DATE('#{end_date}')
|
|
}
|
|
ActiveRecord::Base.connection.execute(sql)
|
|
|
|
sql = %{
|
|
UPDATE affiliate_traffic_totals traffic SET signups = COALESCE((SELECT COALESCE(count(v.id), 0) FROM users v WHERE DATE(v.created_at) >= DATE('#{start_date}') AND DATE(v.created_at) < DATE('#{end_date}') AND v.created_at::date = traffic.day AND v.affiliate_referral_id = traffic.affiliate_partner_id GROUP BY affiliate_referral_id, v.created_at::date ), 0) WHERE traffic.day >= DATE('#{start_date}') AND traffic.day < DATE('#{end_date}')
|
|
}
|
|
ActiveRecord::Base.connection.execute(sql)
|
|
end
|
|
|
|
def self.boundary_dates(year, quarter)
|
|
if quarter == 0
|
|
[Date.new(year, 1, 1), Date.new(year, 3, 31)]
|
|
elsif quarter == 1
|
|
[Date.new(year, 4, 1), Date.new(year, 6, 30)]
|
|
elsif quarter == 2
|
|
[Date.new(year, 7, 1), Date.new(year, 9, 30)]
|
|
elsif quarter == 3
|
|
[Date.new(year, 10, 1), Date.new(year, 12, 31)]
|
|
else
|
|
raise "invalid quarter #{quarter}"
|
|
end
|
|
end
|
|
|
|
# 1-based month
|
|
def self.boundary_dates_for_month(year, month)
|
|
[Date.new(year, month, 1), Date.civil(year, month, -1)]
|
|
end
|
|
|
|
# Finds all affiliates that need to be paid
|
|
def self.unpaid
|
|
|
|
joins(:quarters)
|
|
.where('affiliate_quarterly_payments.paid = false').where('affiliate_quarterly_payments.closed = true')
|
|
.group('affiliate_partners.id')
|
|
.having('sum(due_amount_in_cents) >= ?', PAY_THRESHOLD)
|
|
.order('sum(due_amount_in_cents) DESC')
|
|
|
|
end
|
|
|
|
# does this one affiliate need to be paid?
|
|
def unpaid
|
|
due_amount_in_cents > PAY_THRESHOLD
|
|
end
|
|
|
|
# admin function: mark the affiliate paid
|
|
def mark_paid
|
|
if unpaid
|
|
transaction do
|
|
now = Time.now
|
|
quarters.where(paid:false, closed:true).update_all(paid:true, paid_at: now)
|
|
self.last_paid_at = now
|
|
self.save!
|
|
end
|
|
end
|
|
end
|
|
|
|
# how much is this affiliate due?
|
|
def due_amount_in_cents
|
|
total_in_cents = 0
|
|
quarters.where(paid:false, closed:true).each do |quarter|
|
|
total_in_cents = total_in_cents + quarter.due_amount_in_cents
|
|
end
|
|
total_in_cents
|
|
end
|
|
|
|
def affiliate_query_params
|
|
AffiliatePartner::AFFILIATE_PARAMS + self.id.to_s
|
|
end
|
|
|
|
# def subscribed_user_referrals
|
|
# user_referrals.joins(:sales).where("sales.sale_type = ?", Sale::SUBSCRIPTION_SALE)
|
|
# end
|
|
|
|
def subscribed_user_referrals
|
|
user_referrals.where("first_subscribed_at IS NOT NULL")
|
|
end
|
|
|
|
# def revenues_from_subscriptions
|
|
# subscribed_user_referrals.select("sales.recurly_total_in_cents").inject(0){ | sum, cent | sum += cent } / 100.0
|
|
# end
|
|
|
|
def to_s
|
|
display_name
|
|
end
|
|
end
|