2019-07-30 21:29:24 +00:00
# frozen_string_literal: true
2020-06-12 20:03:43 +00:00
# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later
# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE
2019-02-01 19:40:24 +00:00
class Nonprofit < ApplicationRecord
2020-04-17 22:51:59 +00:00
attr_accessor :register_np_only , :user_id , :user
2019-07-30 21:29:24 +00:00
Categories = [ 'Public Benefit' , 'Human Services' , 'Education' , 'Civic Duty' , 'Human Rights' , 'Animals' , 'Environment' , 'Health' , 'Arts, Culture, Humanities' , 'International' , 'Children' , 'Religion' , 'LGBTQ' , " Women's Rights " , 'Disaster Relief' , 'Veterans' ] . freeze
2018-03-25 17:30:42 +00:00
2020-05-12 21:11:44 +00:00
include Image :: AttachmentExtensions
2021-02-12 21:29:55 +00:00
include Model :: Jbuilder
2019-08-06 14:06:41 +00:00
# :name, # str
# :stripe_account_id, # str
# :summary, # text: paragraph-sized organization summary
# :tagline, # str
# :email, # str: public organization contact email
# :phone, # str: public org contact phone
# :main_image, # str: url of featured image - first image in profile carousel
# :second_image, # str: url of 2nd image in carousel
# :third_image, # str: url of 3rd image in carousel
# :background_image, # str: url of large profile background
# :remove_background_image, #bool carrierwave
# :logo, # str: small logo image url for searching
# :zip_code, # int
# :website, # str: their own website url
# :categories, # text [str]: see the constant Categories
# :achievements, # text [str]: highlights about this org
# :full_description, # text
# :state_code, # str: two-letter state code (eg. CA)
# :statement, # str: bank statement for donations towards the nonprofit
# :city, # str
# :slug, # str
# :city_slug, #str
# :state_code_slug, #str
# :ein, # str: employee identification number
# :published, # boolean; whether to display this profile
# :vetted, # bool: Whether a super admin (one of CommitChange's employees) have approved this org
# :verification_status, # str (either 'pending', 'unverified', 'escalated', 'verified' -- whether the org has submitted the identity verification form and it has been approved)
# :timezone, # str
# :address, # text
# :thank_you_note, # text
# :referrer, # str
# :no_anon, # bool: whether to allow anonymous donations
# :roles_attributes,
# :brand_font, #string (lowercase key eg. 'helvetica')
# :brand_color, #string (hex color value)
# :hide_activity_feed, # bool
# :tracking_script,
# :facebook, #string (url)
# :twitter, #string (url)
# :youtube, #string (url)
# :instagram, #string (url)
# :blog, #string (url)
# :card_failure_message_top, # text
# :card_failure_message_bottom, # text
# :autocomplete_supporter_address # boolean
2018-03-25 17:30:42 +00:00
has_many :payouts
has_many :charges
has_many :refunds , through : :charges
has_many :donations
has_many :recurring_donations
has_many :payments
has_many :supporters , dependent : :destroy
has_many :supporter_notes , through : :supporters
has_many :profiles , through : :donations
has_many :campaigns , dependent : :destroy
has_many :events , dependent : :destroy
2021-02-09 01:37:47 +00:00
has_many :object_event_hook_configs , dependent : :destroy
2018-03-25 17:30:42 +00:00
has_many :tickets , through : :events
2019-12-04 22:22:48 +00:00
has_many :roles , as : :host , dependent : :destroy
2018-03-25 17:30:42 +00:00
has_many :users , through : :roles
has_many :tag_masters , dependent : :destroy
has_many :custom_field_masters , dependent : :destroy
2019-12-04 22:22:48 +00:00
2018-03-25 17:30:42 +00:00
has_many :activities , as : :host , dependent : :destroy
has_many :imports
has_many :email_settings
has_many :cards , as : :holder
2019-07-30 21:29:24 +00:00
has_one :bank_account , - > { where ( 'COALESCE(deleted, false) = false' ) } ,
2018-11-09 00:29:00 +00:00
dependent : :destroy
2018-03-25 17:30:42 +00:00
has_one :billing_subscription , dependent : :destroy
has_one :billing_plan , through : :billing_subscription
has_one :miscellaneous_np_info
validates :name , presence : true
validates :city , presence : true
validates :state_code , presence : true
2020-04-21 18:47:49 +00:00
validates_format_of :email , with : Email :: Regex , allow_nil : true
validates_format_of :website , with : URI . regexp ( [ 'https' , 'http' ] ) , allow_nil : true
2018-03-25 17:30:42 +00:00
validates_presence_of :slug
2020-04-21 18:47:49 +00:00
validates_uniqueness_of :slug , scope : % i [ city_slug state_code_slug ]
2020-04-17 22:51:59 +00:00
validates_presence_of :user_id , on : :create , unless : - > { register_np_only }
validate :user_is_valid , on : :create , unless : - > { register_np_only }
2020-04-17 20:30:54 +00:00
validate :user_registerable_as_admin , on : :create , unless : - > { register_np_only }
2018-03-25 17:30:42 +00:00
2019-07-30 21:29:24 +00:00
scope :vetted , - > { where ( vetted : true ) }
scope :identity_verified , - > { where ( verification_status : 'verified' ) }
scope :published , - > { where ( published : true ) }
2018-03-25 17:30:42 +00:00
2020-04-29 22:22:13 +00:00
has_one_attached :main_image
has_one_attached :second_image
has_one_attached :third_image
has_one_attached :background_image
has_one_attached :logo
2020-05-12 21:11:44 +00:00
# way too wordy
has_one_attached_with_sizes ( :logo , { small : 30 , normal : 100 , large : 180 } )
has_one_attached_with_sizes ( :background_image , { normal : [ 1000 , 600 ] } )
has_one_attached_with_sizes ( :main_image , { nonprofit_carousel : [ 590 , 338 ] , thumb : [ 188 , 120 ] , thumb_explore : [ 100 , 100 ] } )
has_one_attached_with_sizes ( :second_image , { nonprofit_carousel : [ 590 , 338 ] , thumb : [ 188 , 120 ] , thumb_explore : [ 100 , 100 ] } )
has_one_attached_with_sizes ( :third_image , { nonprofit_carousel : [ 590 , 338 ] , thumb : [ 188 , 120 ] , thumb_explore : [ 100 , 100 ] } )
2020-06-10 22:31:47 +00:00
has_one_attached_with_default ( :logo , Houdini . defaults . image . profile ,
filename : " logo_ #{ SecureRandom . uuid } #{ Pathname . new ( Houdini . defaults . image . profile ) . extname } " )
has_one_attached_with_default ( :background_image , Houdini . defaults . image . nonprofit ,
filename : " background_image_ #{ SecureRandom . uuid } #{ Pathname . new ( Houdini . defaults . image . nonprofit ) . extname } " )
has_one_attached_with_default ( :main_image , Houdini . defaults . image . profile ,
filename : " main_image_ #{ SecureRandom . uuid } #{ Pathname . new ( Houdini . defaults . image . profile ) . extname } " )
has_one_attached_with_default ( :second_image , Houdini . defaults . image . profile ,
filename : " second_image_ #{ SecureRandom . uuid } #{ Pathname . new ( Houdini . defaults . image . profile ) . extname } " )
has_one_attached_with_default ( :third_image , Houdini . defaults . image . profile ,
filename : " third_image_ #{ SecureRandom . uuid } #{ Pathname . new ( Houdini . defaults . image . profile ) . extname } " )
2020-05-15 17:32:47 +00:00
2018-03-25 17:30:42 +00:00
serialize :achievements , Array
serialize :categories , Array
2021-02-04 01:18:07 +00:00
2018-03-25 17:30:42 +00:00
before_validation ( on : :create ) do
2019-07-30 21:29:24 +00:00
set_slugs
2020-04-17 22:51:59 +00:00
set_user
2020-06-22 22:10:08 +00:00
add_billing_subscription
2018-03-25 17:30:42 +00:00
self
end
2020-04-17 20:30:54 +00:00
after_create :build_admin_role , unless : - > { register_np_only }
2018-03-25 17:30:42 +00:00
# Register (create) a nonprofit with an initial admin
def self . register ( user , params )
2019-07-30 21:29:24 +00:00
np = create ConstructNonprofit . construct ( user , params )
2018-03-25 17:30:42 +00:00
role = Role . create ( user : user , name : 'nonprofit_admin' , host : np ) if np . valid?
2019-07-30 21:29:24 +00:00
np
2018-03-25 17:30:42 +00:00
end
def nonprofit_personnel_emails
2019-07-30 21:29:24 +00:00
roles . nonprofit_personnel . joins ( :user ) . pluck ( 'users.email' )
2018-03-25 17:30:42 +00:00
end
def total_recurring
recurring_donations . active . sum ( :amount )
end
def donation_history_monthly
donation_history_monthly = [ ]
2019-07-30 21:29:24 +00:00
donations . order ( 'created_at' )
. group_by { | d | d . created_at . beginning_of_month }
. each { | _ , ds | donation_history_monthly . push ( ds . map ( & :amount ) . sum ) }
2018-03-25 17:30:42 +00:00
donation_history_monthly
end
def as_json ( options = { } )
h = super ( options )
2019-07-30 21:29:24 +00:00
h [ :url ] = url
2018-03-25 17:30:42 +00:00
h
end
def url
2019-07-30 21:29:24 +00:00
" / #{ state_code_slug } / #{ city_slug } / #{ slug } "
2018-03-25 17:30:42 +00:00
end
def set_slugs
2019-07-30 21:29:24 +00:00
self . slug = Format :: Url . convert_to_slug name unless slug
self . city_slug = Format :: Url . convert_to_slug city unless city_slug
2018-05-21 20:03:46 +00:00
2019-07-30 21:29:24 +00:00
unless state_code_slug
self . state_code_slug = Format :: Url . convert_to_slug state_code
2018-05-21 20:03:46 +00:00
end
2020-04-17 22:51:59 +00:00
if Nonprofit . where ( slug : slug , city_slug : city_slug , state_code_slug : state_code_slug ) . any?
correct_nonunique_slug
end
2018-03-25 17:30:42 +00:00
self
end
2020-04-10 19:51:34 +00:00
def correct_nonunique_slug
2020-04-17 22:51:59 +00:00
begin
slug = SlugNonprofitNamingAlgorithm . new ( self . state_code_slug , self . city_slug ) . create_copy_name ( self . slug )
self . slug = slug
rescue UnableToCreateNameCopyError
2020-04-21 18:47:49 +00:00
errors . add ( :slug , " could not be created. " )
2020-04-17 22:51:59 +00:00
end
end
def set_user
if ( user_id && User . where ( id : user_id ) . any? )
@user = User . find ( user_id )
2020-04-10 19:51:34 +00:00
end
2020-04-17 22:51:59 +00:00
self
end
2018-03-25 17:30:42 +00:00
def full_address
2019-07-30 21:29:24 +00:00
Format :: Address . full_address ( address , city , state_code )
2018-03-25 17:30:42 +00:00
end
def total_raised
2019-07-30 21:29:24 +00:00
QueryPayments . get_payout_totals ( QueryPayments . ids_for_payout ( id ) ) [ 'net_amount' ]
2018-03-25 17:30:42 +00:00
end
def can_make_payouts
2019-07-30 21:29:24 +00:00
vetted &&
verification_status == 'verified' &&
bank_account &&
! bank_account . pending_verification
2018-03-25 17:30:42 +00:00
end
def active_cards
2019-07-30 21:29:24 +00:00
cards . where ( 'COALESCE(cards.inactive, FALSE) = FALSE' )
2018-03-25 17:30:42 +00:00
end
# @param [Card] card the new active_card
def active_card = ( card )
unless card . class == Card
2019-07-30 21:29:24 +00:00
raise ArgumentError , 'Pass a card to active_card or else'
2018-03-25 17:30:42 +00:00
end
2019-07-30 21:29:24 +00:00
2018-03-25 17:30:42 +00:00
Card . transaction do
2019-07-30 21:29:24 +00:00
active_cards . update_all inactive : true
2018-03-25 17:30:42 +00:00
return cards << card
end
end
def active_card
active_cards . first
end
def create_active_card ( card_data )
2019-07-30 21:29:24 +00:00
if card_data [ :inactive ]
raise ArgumentError , 'This method is for creating active cards only'
2018-03-25 17:30:42 +00:00
end
2019-07-30 21:29:24 +00:00
active_cards . update_all inactive : true
cards . create ( card_data )
2018-03-25 17:30:42 +00:00
end
def currency_symbol
2020-06-10 22:31:47 +00:00
Houdini . intl . all_currencies [ currency . downcase . to_sym ] [ :symbol ]
2018-03-25 17:30:42 +00:00
end
2020-04-17 20:30:54 +00:00
2021-01-11 22:34:48 +00:00
def to_builder ( * expand )
2021-02-12 21:29:55 +00:00
init_builder ( * expand ) do | json |
json . ( self , :name )
2021-01-11 22:34:48 +00:00
end
end
2020-04-17 20:30:54 +00:00
private
def build_admin_role
role = user . roles . build ( host : self , name : 'nonprofit_admin' )
role . save!
end
def add_billing_subscription
2020-06-22 22:10:08 +00:00
billing_plan = BillingPlan . find ( Houdini . default_bp )
2020-04-17 20:30:54 +00:00
b_sub = build_billing_subscription ( billing_plan : billing_plan , status : 'active' )
end
2020-04-21 18:47:49 +00:00
def user_registerable_as_admin
if user && user . roles . nonprofit_admins . any?
errors . add ( :user_id , " cannot already be an admin for a nonprofit. " )
end
end
def user_is_valid
( user && user . is_a? ( User ) ) || errors . add ( :user_id , " is not a valid user " )
end
2018-03-25 17:30:42 +00:00
end