diff options
Diffstat (limited to 'app/models/phone_number.rb')
-rw-r--r-- | app/models/phone_number.rb | 304 |
1 files changed, 304 insertions, 0 deletions
diff --git a/app/models/phone_number.rb b/app/models/phone_number.rb new file mode 100644 index 0000000..4c0cf46 --- /dev/null +++ b/app/models/phone_number.rb @@ -0,0 +1,304 @@ +class PhoneNumber < ActiveRecord::Base + NUMBER_TYPES_INBOUND = ['SipAccount', 'Conference', 'FaxAccount', 'Callthrough', 'HuntGroup'] + + attr_accessible :name, :number, :gs_node_id, :access_authorization_user_id + + has_many :call_forwards, :dependent => :destroy + + has_many :ringtones, :as => :ringtoneable, :dependent => :destroy + + belongs_to :phone_numberable, :polymorphic => true + + belongs_to :gs_node + + validates_uniqueness_of :number, :scope => [:phone_numberable_type, :phone_numberable_id] + + validate :validate_inbound_uniqueness + + before_save :save_value_of_to_s + after_create :copy_existing_call_forwards_if_necessary + before_validation :'parse_and_split_number!' + validate :validate_number, :if => Proc.new { |phone_number| STRICT_INTERNAL_EXTENSION_HANDLING && STRICT_DID_HANDLING } + validate :check_if_number_is_available, :if => Proc.new { |phone_number| STRICT_INTERNAL_EXTENSION_HANDLING && STRICT_DID_HANDLING } + + acts_as_list :scope => [:phone_numberable_id, :phone_numberable_type] + + # Sync other nodes when this is a cluster. + # + validates_presence_of :uuid + validates_uniqueness_of :uuid + after_create { self.create_on_other_gs_nodes('phone_numberable', self.phone_numberable.try(:uuid)) } + after_destroy :destroy_on_other_gs_nodes + after_update { self.update_on_other_gs_nodes('phone_numberable', self.phone_numberable.try(:uuid)) } + + # State machine: + # + default_scope where(:state => 'active') + state_machine :initial => :active do + + event :deactivate do + transition [:active] => :deactivated + end + + event :activate do + transition [:deactivated] => :active + end + end + + + def to_s + parts = [] + parts << "+#{self.country_code}" if self.country_code + parts << self.area_code if self.area_code + parts << self.central_office_code if self.central_office_code + parts << self.subscriber_number if self.subscriber_number + + if parts.empty? + return self.number + end + return parts.join("-") + end + + # Parse a number in a tenant's context (respect the tenant's country) + # + def self.parse( number, tenant=nil ) + number = number.to_s.gsub( /[^0-9+]/, '' ) + + if tenant.class.name == 'Tenant' + country = tenant.country + else + tenant = nil + country = GemeinschaftSetup.first.try(:country) + country ||= Country.where(:name => "Germany").first + end + + parts = { + :country_code => nil, + :area_code => nil, + :central_office_code => nil, + :subscriber_number => nil, + :extension => nil, + } + + if country + if ! country.international_call_prefix.blank? + number = number.gsub( /^#{Regexp.escape( country.international_call_prefix )}/, '+' ) + end + if ! country.trunk_prefix.blank? + number = number.gsub( /^#{Regexp.escape( country.trunk_prefix )}/, "+#{country.country_code}" ) + end + end + + if number.match( /^[+]/ ) + parts = self.parse_international_number( number.gsub(/[^0-9]/,'') ) + return nil if parts.nil? + else + # Check if the number is an internal extension. + if tenant + internal_extension_range = tenant.phone_number_ranges.where(:name => INTERNAL_EXTENSIONS).first + if internal_extension_range + if internal_extension_range.phone_numbers.where(:number => number).length > 0 + parts[:extension] = number + end + end + end + + # Otherwise assume the number is a special number such as an emergency number. + if ! parts[:extension] + parts[:subscriber_number] = number + end + end + + # return nil if all parts are blank: + return nil if ( + parts[:country_code].blank? && + parts[:area_code].blank? && + parts[:central_office_code].blank? && + parts[:subscriber_number].blank? && + parts[:extension].blank? + ) + parts # return value + end + + def self.parse_and_format( number, tenant=nil ) + attributes = PhoneNumber.parse(number, tenant) + if attributes + formated_number = attributes.map{|key,value| value}.delete_if{|x| x.nil?}.join('-') + formated_number = "+#{formated_number}" if attributes[:country_code] + return formated_number + end + return number + end + + # Parse an international number. + # Assumed format for +number+ is e.g. "49261200000" + # + def self.parse_international_number( number ) + number = number.to_s.gsub( /[^0-9]/, '' ) + + parts = { + :country_code => nil, + :area_code => nil, + :central_office_code => nil, + :subscriber_number => nil, + :extension => nil, + } + + # Find country by country code: + country = Country.where( :country_code => number[0, 3]).first + country ||= Country.where( :country_code => number[0, 2]).first + country ||= Country.where( :country_code => number[0, 1]).first + + return nil if ! country # invalid number format + + parts[:country_code] = country.country_code + remainder = number[ parts[:country_code].length, 999 ] # e.g. "261200000" + + case parts[:country_code] + + when '1' + # Assure an NANP number + return nil if ! remainder.match(/[2-9]{1}[0-9]{2}[2-9]{1}[0-9]{2}[0-9]{4}/) + + # Shortcut for NANPA closed dialplan: + parts[:area_code ] = remainder[ 0, 3] + parts[:central_office_code ] = remainder[ 3, 3] + parts[:subscriber_number ] = remainder[ 6, 4] + else + # variable-length dialplan, e.g. Germany + + # Find longest area_code for the country: + longest_area_code = country.area_codes.order( "LENGTH(area_code) DESC" ).first + + # Find a matching area_code: + if longest_area_code + longest_area_code.area_code.length.downto(1) do |area_code_length| + area_code = country.area_codes.where( :area_code => remainder[ 0, area_code_length ] ).first + if area_code + parts[:area_code] = area_code.area_code + break + end + end + + return nil if ! parts[:area_code] # No matching area_code for the country. + + remainder = remainder.gsub( /^#{parts[:area_code]}/, '' ) + #remainder = number[ parts[:area_code].length, 999 ] # e.g. "200000" + end + parts[:subscriber_number] = remainder + end + + parts # return value + end + + def parse_and_split_number! + if self.phone_numberable_type == 'PhoneNumberRange' && self.phone_numberable.name == INTERNAL_EXTENSIONS + # The parent is the PhoneNumberRange INTERNAL_EXTENSIONS. Therefor it must be an extensions. + # + self.country_code = nil + self.area_code = nil + self.subscriber_number = nil + self.central_office_code = nil + self.extension = self.number.to_s.strip + else + if self.tenant && + self.tenant.phone_number_ranges.exists?(:name => INTERNAL_EXTENSIONS) && + self.tenant.phone_number_ranges.where(:name => INTERNAL_EXTENSIONS).first.phone_numbers.exists?(:number => self.number) + self.country_code = nil + self.area_code = nil + self.subscriber_number = nil + self.central_office_code = nil + self.extension = self.number.to_s.strip + else + parsed_number = PhoneNumber.parse( self.number ) + if parsed_number + self.country_code = parsed_number[:country_code] + self.area_code = parsed_number[:area_code] + self.subscriber_number = parsed_number[:subscriber_number] + self.extension = parsed_number[:extension] + self.central_office_code = parsed_number[:central_office_code] + + self.number = self.to_s.gsub( /[^\+0-9]/, '' ) + end + end + end + end + + # Find the (grand-)parent tenant of this phone number: + # + def tenant + #OPTIMIZE Add a tenant_id to SipAccount + case self.phone_numberable + when SipAccount + self.phone_numberable.tenant + when Conference + case self.phone_numberable.conferenceable + when Tenant + self.phone_numberable.conferenceable + when User + self.phone_numberable.conferenceable.current_tenant #OPTIMIZE + when UserGroup + self.phone_numberable.conferenceable.tenant + end + end + end + + def move_up? + return self.position.to_i > PhoneNumber.where(:phone_numberable_id => self.phone_numberable_id, :phone_numberable_type => self.phone_numberable_type ).order(:position).first.position.to_i + end + + def move_down? + return self.position.to_i < PhoneNumber.where(:phone_numberable_id => self.phone_numberable_id, :phone_numberable_type => self.phone_numberable_type ).order(:position).last.position.to_i + end + + private + + def validate_number + if ! PhoneNumber.parse( self.number ) + errors.add( :number, "is invalid." ) + end + end + + def check_if_number_is_available + if self.phone_numberable_type != 'PhoneBookEntry' && self.tenant + + phone_number_ranges = self.tenant.phone_number_ranges.where( + :name => [INTERNAL_EXTENSIONS, DIRECT_INWARD_DIALING_NUMBERS] + ) + if !phone_number_ranges.empty? + if !PhoneNumber.where(:phone_numberable_type => 'PhoneNumberRange'). + where(:phone_numberable_id => phone_number_ranges). + exists?(:number => self.number) + errors.add(:number, "isn't defined as an extenation or DID for the tenant '#{self.tenant}'. #{phone_number_ranges.inspect}") + end + end + end + end + + def validate_inbound_uniqueness + if NUMBER_TYPES_INBOUND.include?(self.phone_numberable_type) + numbering_scope = PhoneNumber.where(:state => 'active', :number => self.number, :phone_numberable_type => NUMBER_TYPES_INBOUND) + if numbering_scope.where(:id => self.id).count == 0 && numbering_scope.count > 0 + errors.add(:number, 'not unique') + end + end + end + + def save_value_of_to_s + self.value_of_to_s = self.to_s + end + + def copy_existing_call_forwards_if_necessary + if self.phone_numberable.class == SipAccount && self.phone_numberable.callforward_rules_act_per_sip_account == true + sip_account = SipAccount.find(self.phone_numberable) + if sip_account.phone_numbers.where('id != ?', self.id).count > 0 + if sip_account.phone_numbers.where('id != ?', self.id).order(:created_at).first.call_forwards.count > 0 + sip_account.phone_numbers.where('id != ?', self.id).first.call_forwards.each do |call_forward| + call_forward.set_this_callforward_rule_to_all_phone_numbers_of_the_parent_sip_account + end + end + end + end + end + +end |