app/models/coop.rb
require 'one_click_orgs/cast_to_boolean'
class Coop < Organisation
# http://www.legislation.gov.uk/ukpga/2014/14/part/2/crossheading/maximum-shareholding/enacted
MAXIMUM_SHAREHOLDING_IN_POUNDS = 100_000
include OneClickOrgs::CastToBoolean
attr_accessible :reg_form_timing_factors, :reg_form_close_links,
:reg_form_financial_year_end,
:reg_form_signatories_attributes,
:reg_form_main_contact_organisation_name, :reg_form_main_contact_name,
:reg_form_main_contact_address, :reg_form_main_contact_phone,
:reg_form_main_contact_email,
:reg_form_financial_contact_name, :reg_form_financial_contact_phone,
:reg_form_financial_contact_email,
:reg_form_money_laundering_0_name,
:reg_form_money_laundering_0_date_of_birth,
:reg_form_money_laundering_0_address,
:reg_form_money_laundering_0_postcode,
:reg_form_money_laundering_0_residency_length,
:reg_form_money_laundering_1_name,
:reg_form_money_laundering_1_date_of_birth,
:reg_form_money_laundering_1_address,
:reg_form_money_laundering_1_postcode,
:reg_form_money_laundering_1_residency_length,
:reg_form_money_laundering_agreement,
:reg_form_business_carried_out,
:reg_form_funding,
:reg_form_members_benefit,
:reg_form_members_participate,
:reg_form_members_control,
:reg_form_profit_use
state_machine :initial => :pending do
event :propose do
transition :pending => :proposed
end
event :found do
transition :proposed => :active
end
event :reject_founding do
transition :proposed => :pending
end
after_transition :proposed => :active, :do => :destroy_pending_state_member_classes
end
scope :active, with_state(:active)
scope :proposed, with_state(:proposed)
scope :pending, with_state(:pending)
has_many :meetings, :foreign_key => 'organisation_id'
has_many :board_meetings, :foreign_key => 'organisation_id'
has_many :general_meetings, :foreign_key => 'organisation_id'
has_many :annual_general_meetings, :foreign_key => 'organisation_id' do
def persisted
select(&:persisted?)
end
end
has_many :resolutions, :foreign_key => 'organisation_id'
has_many :board_resolutions, :foreign_key => 'organisation_id'
has_many :change_meeting_notice_period_resolutions, :foreign_key => 'organisation_id'
has_many :change_quorum_resolutions, :foreign_key => 'organisation_id'
has_many :change_text_resolutions, :foreign_key => 'organisation_id'
has_many :change_boolean_resolutions, :foreign_key => 'organisation_id'
has_many :change_integer_resolutions, :foreign_key => 'organisation_id'
has_many :terminate_directorship_resolutions, :foreign_key => 'organisation_id'
has_many :resolution_proposals, :foreign_key => 'organisation_id'
has_many :general_meeting_proposals, foreign_key: 'organisation_id'
has_many :offices, :foreign_key => 'organisation_id'
has_many :officerships, :through => :offices
has_many :officers, :through => :officerships
has_many :directorships, :foreign_key => 'organisation_id'
has_many :elections, :foreign_key => 'organisation_id'
has_one :share_account, :as => :owner
has_many :withdrawals, :through => :share_account
has_many :deposits, :through => :share_account
def self.find_by_name(name)
includes(:clauses).where(["clauses.name = 'organisation_name' AND clauses.text_value = ?", name]).first
end
# Returns true if the requirements for moving to the 'proposed' state
# have been fulfilled.
def can_propose?
result = true
result &&= members.active.count >= 3
result &&= directors.count >= 3
result &&= !!secretary
result &&= rules_filled?
result &&= registration_form_filled?
result &&= signatories_and_secretary_contact_details_present?
result
end
def founder_members
members.founder_members(self)
end
after_create :create_default_offices
after_create :set_default_user_and_director_clauses
after_create :create_share_account_if_necessary
after_create :set_default_clauses
def member_count_for_proposal(proposal)
# TODO check that this is correct
members.active.count
end
# ATTRIBUTES / CLAUSES
def constitution_clause_names
super + [
:registered_office_address, :organisation_objectives,
:producer_members_description, :consumer_members_description,
:max_user_directors, :max_employee_directors, :max_supporter_directors,
:max_producer_directors, :max_consumer_directors,
:meeting_notice_period, :quorum_number, :quorum_percentage,
:user_members, :employee_members, :supporter_members,
:producer_members, :consumer_members, :single_shareholding,
:common_ownership
]
end
def meeting_notice_period=(new_meeting_notice_period)
clauses.set_integer!(:meeting_notice_period, new_meeting_notice_period)
end
def meeting_notice_period
clauses.get_integer(:meeting_notice_period)
end
def quorum_number=(new_quorum_number)
clauses.set_integer!(:quorum_number, new_quorum_number)
end
def quorum_number
clauses.get_integer(:quorum_number)
end
def quorum_percentage=(new_quorum_percentage)
clauses.set_integer!(:quorum_percentage, new_quorum_percentage)
end
def quorum_percentage
clauses.get_integer(:quorum_percentage)
end
def objectives
@objectives ||= clauses.get_text('organisation_objectives')
end
def objectives=(objectives)
clauses.build(:name => 'organisation_objectives', :text_value => objectives)
@objectives = objectives
end
def registered_office_address
@registered_office_address ||= clauses.get_text('registered_office_address')
end
def registered_office_address=(registered_office_address)
clauses.build(:name => 'registered_office_address', :text_value => registered_office_address)
@registered_office_address = registered_office_address
end
def max_user_directors
@max_user_directors ||= clauses.get_integer('max_user_directors')
end
def max_user_directors=(new_max_user_directors)
clauses.build(:name => :max_user_directors, :integer_value => new_max_user_directors)
@max_user_directors = new_max_user_directors
end
def max_employee_directors
@max_employee_directors ||= clauses.get_integer('max_employee_directors')
end
def max_employee_directors=(new_max_employee_directors)
clauses.build(:name => :max_employee_directors, :integer_value => new_max_employee_directors)
@max_employee_directors = new_max_employee_directors
end
def max_supporter_directors
@max_supporter_directors ||= clauses.get_integer('max_supporter_directors')
end
def max_supporter_directors=(new_max_supporter_directors)
clauses.build(:name => :max_supporter_directors, :integer_value => new_max_supporter_directors)
@max_supporter_directors = new_max_supporter_directors
end
def max_producer_directors
@max_producer_directors ||= clauses.get_integer('max_producer_directors')
end
def max_producer_directors=(new_max_producer_directors)
clauses.build(:name => :max_producer_directors, :integer_value => new_max_producer_directors)
@max_producer_directors = new_max_producer_directors
end
def max_consumer_directors
@max_consumer_directors ||= clauses.get_integer('max_consumer_directors')
end
def max_consumer_directors=(new_max_consumer_directors)
clauses.build(:name => :max_consumer_directors, :integer_value => new_max_consumer_directors)
@max_consumer_directors = new_max_consumer_directors
end
def user_members
@user_members ||= clauses.get_boolean('user_members')
end
def user_members=(new_user_members)
clauses.build(:name => :user_members, :boolean_value => new_user_members)
@user_members = new_user_members
end
def employee_members
@employee_members ||= clauses.get_boolean('employee_members')
end
def employee_members=(new_employee_members)
clauses.build(:name => :employee_members, :boolean_value => new_employee_members)
@employee_members = new_employee_members
end
def supporter_members
@supporter_members ||= clauses.get_boolean('supporter_members')
end
def supporter_members=(new_supporter_members)
clauses.build(:name => :supporter_members, :boolean_value => new_supporter_members)
@supporter_members = new_supporter_members
end
def producer_members
@producer_members ||= clauses.get_boolean('producer_members')
end
def producer_members=(new_producer_members)
clauses.build(:name => :producer_members, :boolean_value => new_producer_members)
@producer_members = new_producer_members
end
def consumer_members
@consumer_members ||= clauses.get_boolean('consumer_members')
end
def consumer_members=(new_consumer_members)
clauses.build(:name => :consumer_members, :boolean_value => new_consumer_members)
@consumer_members = new_consumer_members
end
def producer_members_description
@producer_members_description ||= clauses.get_text('producer_members_description')
end
def producer_members_description=(producer_members_description)
clauses.build(:name => 'producer_members_description', :text_value => producer_members_description)
@producer_members_description = producer_members_description
end
def consumer_members_description
@consumer_members_description ||= clauses.get_text('consumer_members_description')
end
def consumer_members_description=(consumer_members_description)
clauses.build(:name => 'consumer_members_description', :text_value => consumer_members_description)
@consumer_members_description = consumer_members_description
end
def single_shareholding
@single_shareholding ||= clauses.get_boolean('single_shareholding')
end
def single_shareholding=(new_single_shareholding)
clauses.build(:name => :single_shareholding, :boolean_value => !!new_single_shareholding)
@single_shareholding = !!new_single_shareholding
end
def common_ownership
@common_ownership ||= clauses.get_boolean('common_ownership')
end
def share_value=(new_share_value)
clauses.build(:name => :share_value, :integer_value => new_share_value.to_i)
@share_value = new_share_value.to_i
end
# Share value in pennies. Defaults to 100.
def share_value
@share_value ||= (clauses.get_integer('share_value') || 100)
end
def share_value_in_pounds=(new_share_value_in_pounds)
new_share_value_in_pounds = new_share_value_in_pounds.to_f
new_share_value = (new_share_value_in_pounds * 100.0).to_i
self.share_value = new_share_value
end
def share_value_in_pounds
share_value.to_f / 100.0
end
def minimum_shareholding=(new_minimum_shareholding)
new_minimum_shareholding = new_minimum_shareholding.to_i
clauses.build(:name => :minimum_shareholding, :integer_value => new_minimum_shareholding)
@minimum_shareholding = new_minimum_shareholding
end
def minimum_shareholding
@minimum_shareholding ||= (clauses.get_integer('minimum_shareholding') || 1)
end
def maximum_shareholding
(MAXIMUM_SHAREHOLDING_IN_POUNDS / (share_value / 100.0)).floor
end
def interest_rate
@interest_rate ||= clauses.get_decimal('interest_rate')
end
def interest_rate=(new_interest_rate)
new_interest_rate = new_interest_rate.to_f
clauses.build(:name => :interest_rate, :decimal_value => new_interest_rate)
@interest_rate = new_interest_rate
end
def membership_application_text
@membership_application_text ||= clauses.get_text(:membership_application_text)
end
def membership_application_text=(new_membership_application_text)
clauses.build(:name => :membership_application_text, :text_value => new_membership_application_text)
@membership_application_text = new_membership_application_text
end
# SETUP
def create_default_member_classes
members = member_classes.find_or_create_by_name('Member')
members.set_permission!(:resolution_proposal, true)
members.set_permission!(:vote, true)
founder_members = member_classes.find_or_create_by_name('Founder Member')
founder_members.set_permission!(:constitution, true)
founder_members.set_permission!(:organisation, true)
founder_members.set_permission!(:founder_member, true)
founder_members.set_permission!(:directorship, true)
founder_members.set_permission!(:officership, true)
directors = member_classes.find_or_create_by_name('Director')
directors.set_permission!(:resolution, true)
directors.set_permission!(:board_resolution, true)
directors.set_permission!(:vote, true)
directors.set_permission!(:meeting, true)
directors.set_permission!(:board_meeting, true)
directors.set_permission!(:share_account, true)
secretaries = member_classes.find_or_create_by_name('Secretary')
secretaries.set_permission!(:resolution, true)
secretaries.set_permission!(:board_resolution, true)
secretaries.set_permission!(:meeting, true)
secretaries.set_permission!(:vote, true)
secretaries.set_permission!(:board_meeting, true)
secretaries.set_permission!(:member, true)
secretaries.set_permission!(:organisation, true)
secretaries.set_permission!(:directorship, true)
secretaries.set_permission!(:officership, true)
secretaries.set_permission!(:share_account, true)
secretaries.set_permission!(:share_transaction, true)
external_directors = member_classes.find_or_create_by_name('External Director')
external_directors.set_permission!(:board_resolution, true)
external_directors.set_permission!(:board_meeting, true)
end
def create_default_offices
offices.find_or_create_by_title('Secretary')
offices.find_or_create_by_title('Chairperson')
end
def set_default_voting_period
constitution.set_voting_period(14.days)
end
def set_default_user_and_director_clauses
# TODO Verify these are sensible defaults for the Rules
self.user_members = true
self.employee_members = true
self.supporter_members = true
self.producer_members = true
self.consumer_members = true
self.max_user_directors = 3
self.max_employee_directors = 3
self.max_supporter_directors = 3
self.max_producer_directors = 3
self.max_consumer_directors = 3
self.save!
end
def set_default_clauses
self.quorum_number = 3
self.quorum_percentage = 25
self.meeting_notice_period = 14
self.save!
end
def destroy_pending_state_member_classes
end
def create_share_account_if_necessary
unless share_account
create_share_account!
end
end
def member_eligible_to_vote?(member, proposal)
true
end
def secretary
secretary_member_class = member_classes.find_by_name('Secretary')
secretary_member_class ? secretary_member_class.members.last : nil
end
def directors
members.where([
'member_class_id IN (?)',
[
member_classes.find_by_name!('Director').id,
member_classes.find_by_name!('Secretary').id,
member_classes.find_by_name!('External Director').id
]
])
end
def build_directorship(attributes={})
Directorship.new({:organisation => self}.merge(attributes))
end
# Returns the directors who should retire at the next AGM.
def directors_retiring
# At the first AGM, all directors stand down.
return directors if annual_general_meetings.persisted.empty?
number_to_retire = (directors.count / 3.0).round
directors.sort{|a, b| a.directorship.elected_on <=> b.directorship.elected_on}[0..(number_to_retire - 1)]
end
def build_general_meeting_or_annual_general_meeting(attributes={})
attributes = attributes.dup.with_indifferent_access
agm = cast_to_boolean(attributes.delete(:annual_general_meeting))
if agm
begin
annual_general_meetings.build(attributes)
rescue ActiveRecord::MultiparameterAssignmentErrors => e
raise e.errors.map{|error| [error.exception, error.attribute]}.inspect
end
else
general_meetings.build(attributes)
end
end
def welcome_email_action
if pending?
:welcome_coop_founding_member
elsif active?
:welcome_coop_new_member
end
end
def build_founder_member(attributes={})
FounderMember.new(attributes).tap{|m|
m.organisation = self
m.member_class = member_classes.find_by_name("Founder Member")
}
end
def meeting_classes
[GeneralMeeting, AnnualGeneralMeeting, BoardMeeting]
end
def build_minute(attributes={})
Minute.new(attributes).tap{|m|
m.organisation = self
}
end
# True if the minimum required fields in the Rules have been filled in.
def rules_filled?
name.present? &&
registered_office_address.present? &&
objectives.present?
end
# Registration form
def reg_form_timing_factors
@reg_form_timing_factors ||= clauses.get_text('reg_form_timing_factors')
end
def reg_form_timing_factors=(new_reg_form_timing_factors)
new_reg_form_timing_factors = ' ' if new_reg_form_timing_factors.blank?
clauses.build(:name => 'reg_form_timing_factors', :text_value => new_reg_form_timing_factors)
@reg_form_timing_factors = new_reg_form_timing_factors
end
def reg_form_close_links
@reg_form_close_links ||= clauses.get_text('reg_form_close_links')
end
def reg_form_close_links=(new_reg_form_close_links)
new_reg_form_close_links = ' ' if new_reg_form_close_links.blank?
clauses.build(:name => 'reg_form_close_links', :text_value => new_reg_form_close_links)
@reg_form_close_links = new_reg_form_close_links
end
def reg_form_financial_year_end
@reg_form_financial_year_end ||= clauses.get_text('reg_form_financial_year_end')
end
def reg_form_financial_year_end=(new_reg_form_financial_year_end)
new_reg_form_financial_year_end = ' ' if new_reg_form_financial_year_end.blank?
clauses.build(:name => 'reg_form_financial_year_end', :text_value => new_reg_form_financial_year_end)
@reg_form_finacial_year_end = new_reg_form_financial_year_end
end
def reg_form_signatories_attributes=(attributes)
attributes = attributes.reject{|k, v| v['selected'] != '1'}
signatory_ids = []
attributes.each{|k, v| signatory_ids[k.to_i] = v['id'].to_i}
signatory_ids = signatory_ids.compact[0..2]
signatories = signatory_ids.map{|id| members.find(id)}
self.signatories = signatories
end
def reg_form_business_carried_out
@reg_form_business_carried_out ||= clauses.get_text('reg_form_business_carried_out')
end
def reg_form_business_carried_out=(new_reg_form_business_carried_out)
new_reg_form_business_carried_out = ' ' if new_reg_form_business_carried_out.blank?
clauses.build(:name => 'reg_form_business_carried_out', :text_value => new_reg_form_business_carried_out)
@reg_form_business_carried_out = new_reg_form_business_carried_out
end
def reg_form_funding
@reg_form_funding ||= clauses.get_text('reg_form_funding')
end
def reg_form_funding=(new_reg_form_funding)
new_reg_form_funding = ' ' if new_reg_form_funding.blank?
clauses.build(:name => 'reg_form_funding', :text_value => new_reg_form_funding)
@reg_form_funding = new_reg_form_funding
end
def reg_form_members_benefit
@reg_form_members_benefit ||= clauses.get_text('reg_form_members_benefit')
end
def reg_form_members_benefit=(new_reg_form_members_benefit)
new_reg_form_members_benefit = ' ' if new_reg_form_members_benefit.blank?
clauses.build(:name => 'reg_form_members_benefit', :text_value => new_reg_form_members_benefit)
@reg_form_members_benefit = new_reg_form_members_benefit
end
def reg_form_members_participate
@reg_form_members_participate ||= clauses.get_text('reg_form_members_participate')
end
def reg_form_members_participate=(new_reg_form_members_participate)
new_reg_form_members_participate = ' ' if new_reg_form_members_participate.blank?
clauses.build(:name => 'reg_form_members_participate', :text_value => new_reg_form_members_participate)
@reg_form_members_participate = new_reg_form_members_participate
end
def reg_form_members_control
@reg_form_members_control ||= clauses.get_text('reg_form_members_control')
end
def reg_form_members_control=(new_reg_form_members_control)
new_reg_form_members_control = ' ' if new_reg_form_members_control.blank?
clauses.build(:name => 'reg_form_members_control', :text_value => new_reg_form_members_control)
@reg_form_members_control = new_reg_form_members_control
end
def reg_form_profit_use
@reg_form_profit_use ||= clauses.get_text('reg_form_profit_use')
end
def reg_form_profit_use=(new_reg_form_profit_use)
new_reg_form_profit_use = ' ' if new_reg_form_profit_use.blank?
clauses.build(:name => 'reg_form_profit_use', :text_value => new_reg_form_profit_use)
@reg_form_profit_use = new_reg_form_profit_use
end
def signatories
[
clauses.get_integer(:reg_form_signatories_0),
clauses.get_integer(:reg_form_signatories_1),
clauses.get_integer(:reg_form_signatories_2)
].map{|id| members.find_by_id(id)}.compact
end
def signatories=(new_signatories)
clauses.set_integer!(:reg_form_signatories_0, new_signatories[0].id) if new_signatories[0]
clauses.set_integer!(:reg_form_signatories_1, new_signatories[1].id) if new_signatories[1]
clauses.set_integer!(:reg_form_signatories_2, new_signatories[2].id) if new_signatories[2]
end
def members_with_signatories_selected
signatories = self.signatories
members.each{|m| m.selected = true if signatories.include?(m)}
end
def signatories_and_secretary_contact_details_present?
signatories.push(secretary).compact.inject(true){|memo, member| memo && member.contact_details_present?}
end
def signatories_and_secretary_without_contact_details
signatories.push(secretary).compact.uniq.select{|m| !m.contact_details_present?}
end
def reg_form_money_laundering_agreement=(new_money_laundering_agreement)
new_money_laundering_agreement = cast_to_boolean(new_money_laundering_agreement)
clauses.build(:name => :reg_form_money_laundering_agreement, :boolean_value => new_money_laundering_agreement)
@reg_form_money_laundering_agreement = new_money_laundering_agreement
end
def reg_form_money_laundering_agreement
if @reg_form_money_laundering_agreement.nil?
@reg_form_money_laundering_agreement = clauses.get_boolean(:reg_form_money_laundering_agreement)
end
@reg_form_money_laundering_agreement
end
def reg_form_money_laundering_agreement?
!!reg_form_money_laundering_agreement
end
# Define some more text attributes for the registration form.
[
:main_contact_organisation_name,
:main_contact_name,
:main_contact_address,
:main_contact_phone,
:main_contact_email,
:financial_contact_name,
:financial_contact_phone,
:financial_contact_email,
:money_laundering_0_name,
:money_laundering_0_date_of_birth,
:money_laundering_0_address,
:money_laundering_0_postcode,
:money_laundering_0_residency_length,
:money_laundering_1_name,
:money_laundering_1_date_of_birth,
:money_laundering_1_address,
:money_laundering_1_postcode,
:money_laundering_1_residency_length
].each do |reg_form_attr_name|
class_eval("
def reg_form_#{reg_form_attr_name}=(new_#{reg_form_attr_name})
clauses.build(:name => :reg_form_#{reg_form_attr_name}, :text_value => new_#{reg_form_attr_name})
@reg_form_#{reg_form_attr_name} = new_#{reg_form_attr_name}
end
def reg_form_#{reg_form_attr_name}
@reg_form_#{reg_form_attr_name} ||= clauses.get_text(:reg_form_#{reg_form_attr_name})
end
")
end
# Returns true if the minimum required registration details have been filled in
def registration_form_filled?
clauses.get_integer(:reg_form_signatories_0) &&
clauses.get_integer(:reg_form_signatories_1) &&
clauses.get_integer(:reg_form_signatories_2) &&
(clauses.get_text(:reg_form_main_contact_organisation_name) || clauses.get_text(:reg_form_main_contact_name)) &&
clauses.get_text(:reg_form_main_contact_address) &&
clauses.get_text(:reg_form_main_contact_phone) &&
clauses.get_text(:reg_form_main_contact_email) &&
clauses.get_text(:reg_form_money_laundering_0_name) &&
clauses.get_text(:reg_form_money_laundering_0_date_of_birth) &&
clauses.get_text(:reg_form_money_laundering_0_address) &&
clauses.get_text(:reg_form_money_laundering_0_postcode) &&
clauses.get_text(:reg_form_money_laundering_0_residency_length) &&
clauses.get_text(:reg_form_money_laundering_1_name) &&
clauses.get_text(:reg_form_money_laundering_1_date_of_birth) &&
clauses.get_text(:reg_form_money_laundering_1_address) &&
clauses.get_text(:reg_form_money_laundering_1_postcode) &&
clauses.get_text(:reg_form_money_laundering_1_residency_length) &&
clauses.get_boolean(:reg_form_money_laundering_agreement)
end
# The lesser of 10% of the membership and 100 members is required to force a resolution.
def members_required_to_force_resolution
ten_percent_of_membership = (members.active.count / 10.0).ceil
if ten_percent_of_membership < 100
ten_percent_of_membership
else
100
end
end
def self.run_daily_job
# Find all Coop members who have not attained the minimum shareholding within 12 months
# of membership.
all.each do |coop|
if !coop.single_shareholding && coop.minimum_shareholding
old_members = coop.members.where(["inducted_at <= ?", 12.months.ago])
old_members.select{|m| m.find_or_build_share_account.balance < coop.minimum_shareholding}.each do |member|
unless member.tasks.current.where(:subject_id => member.id, :subject_type => 'Member', :action => :minimum_shareholding_missed).first
member.tasks.create(:subject => member, :action => :minimum_shareholding_missed)
coop.secretary.tasks.create(:subject => member, :action => :minimum_shareholding_missed)
end
end
end
end
end
end