From 019e16cabfcbc0cb2170f75938adc3e940e9fdbe Mon Sep 17 00:00:00 2001 From: Dmitry Sinina Date: Sun, 23 Jun 2024 22:39:09 +0300 Subject: [PATCH] initial package billing (#1486) * initial package billing --- app/admin/billing/package_counters.rb | 45 + app/admin/cdr/cdrs.rb | 2 + app/admin/routing/destinations.rb | 8 +- app/models/billing/package_counter.rb | 35 + app/models/cdr/cdr.rb | 5 +- app/models/report/realtime/bad_routing.rb | 1 + .../report/realtime/not_authenticated.rb | 1 + .../realtime/origination_performance.rb | 1 + .../realtime/termination_distribution.rb | 1 + app/models/routing/destination.rb | 1 + .../billing/package_counter_policy.rb | 9 + config/policy_roles.yml.distr | 5 + .../20240617084103_package_billing_cdr.rb | 1122 ++++ db/cdr_structure.sql | 28 +- db/custom_seeds/full_cdrs.rb | 4 +- db/migrate/20240615214442_package_billing.rb | 4751 +++++++++++++++++ ...22173924_package_billing_bill_cdr_batch.rb | 249 + db/seeds/main/switch21.sql | 1 + db/structure.sql | 232 +- spec/factories/billing/package_counter.rb | 11 + spec/factories/billing/services.rb | 1 + .../export_package_counters_spec.rb | 30 + .../index_package_counters_spec.rb | 64 + .../destinations/export_destination_spec.rb | 1 + spec/models/cdr/cdr_spec.rb | 1 + .../report/realtime/bad_routing_spec.rb | 1 + .../report/realtime/not_authenticated_spec.rb | 1 + spec/models/routing/destination_spec.rb | 1 + spec/sql/billing/bill_cdr_batch_spec.rb | 61 +- spec/sql/switch/writecdr_spec.rb | 19 +- ...r_with_internal_disconnect_code_id_spec.rb | 19 +- spec/sql/switch21/route_spec.rb | 79 +- 32 files changed, 6716 insertions(+), 74 deletions(-) create mode 100644 app/admin/billing/package_counters.rb create mode 100644 app/models/billing/package_counter.rb create mode 100644 app/policies/billing/package_counter_policy.rb create mode 100644 db/cdr_migrate/20240617084103_package_billing_cdr.rb create mode 100644 db/migrate/20240615214442_package_billing.rb create mode 100644 db/migrate/20240622173924_package_billing_bill_cdr_batch.rb create mode 100644 spec/factories/billing/package_counter.rb create mode 100644 spec/features/billing/package_counters/export_package_counters_spec.rb create mode 100644 spec/features/billing/package_counters/index_package_counters_spec.rb diff --git a/app/admin/billing/package_counters.rb b/app/admin/billing/package_counters.rb new file mode 100644 index 000000000..dca2e8c94 --- /dev/null +++ b/app/admin/billing/package_counters.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +ActiveAdmin.register Billing::PackageCounter, as: 'Package Counters' do + menu parent: 'Billing', label: 'Package Counters', priority: 11 + + actions :index, :show + + acts_as_export :id, + [:account_name, proc { |row| row.account.try(:name) }], + [:service_name, proc { |row| row.service.try(:name) }], + :prefix, + :exclude, + :duration + + includes :account, :service + + filter :id + account_filter :account_id_eq + filter :service + filter :prefix + filter :duration + filter :exclude + + index do + selectable_column + id_column + actions + column :account + column :service + column :prefix + column :exclude + column :duration + end + + show do + attributes_table do + row :id + row :account + row :service + row :prefix + row :exclude + row :duration + end + end +end diff --git a/app/admin/cdr/cdrs.rb b/app/admin/cdr/cdrs.rb index e7e9c1c84..62975cffd 100644 --- a/app/admin/cdr/cdrs.rb +++ b/app/admin/cdr/cdrs.rb @@ -48,6 +48,7 @@ def scoped_collection scope :with_trace, show_count: false scope :not_authorized, show_count: false scope :bad_routing, show_count: false + scope :package_billing, show_count: false filter :id filter :routing_tag_ids_include, @@ -580,6 +581,7 @@ def scoped_collection row :destination_initial_rate row :destination_next_interval row :destination_next_rate + row :package_counter row :routing_plan row :routing_group diff --git a/app/admin/routing/destinations.rb b/app/admin/routing/destinations.rb index ed1bc8e28..ba601b001 100644 --- a/app/admin/routing/destinations.rb +++ b/app/admin/routing/destinations.rb @@ -45,7 +45,7 @@ :dp_margin_fixed, :dp_margin_percent, :profit_control_mode_name, :valid_from, :valid_till, - :asr_limit, :acd_limit, :short_calls_limit, :reverse_billing, + :asr_limit, :acd_limit, :short_calls_limit, :reverse_billing, :allow_package_billing, [:routing_tag_names, proc { |row| row.model.routing_tags.map(&:name).join(', ') }], [:routing_tag_mode_name, proc { |row| row.routing_tag_mode.try(:name) }] @@ -66,6 +66,7 @@ filter :initial_rate filter :next_rate filter :connect_fee + filter :allow_package_billing filter :network_prefix_country_id_eq, as: :select, @@ -105,7 +106,7 @@ :dp_margin_percent, :rate_policy_id, :reverse_billing, :initial_rate, :reject_calls, :use_dp_intervals, :test, :profit_control_mode_id, :valid_from, :valid_till, :asr_limit, :acd_limit, :short_calls_limit, :batch_prefix, - :reverse_billing, :routing_tag_mode_id, routing_tag_ids: [] + :reverse_billing, :routing_tag_mode_id, :allow_package_billing, routing_tag_ids: [] action_item :show_rates, only: [:show] do link_to 'Show Rates', destination_destination_next_rates_path(resource.id) @@ -174,6 +175,7 @@ def scoped_collection column :rate_policy, &:rate_policy_name column :reverse_billing + column :allow_package_billing ## fixed price column :initial_interval @@ -222,6 +224,7 @@ def scoped_collection f.input :valid_till, as: :date_time_picker f.input :rate_policy_id, as: :select, include_blank: false, collection: Routing::DestinationRatePolicy::POLICIES.invert f.input :reverse_billing + f.input :allow_package_billing f.input :initial_interval f.input :next_interval f.input :use_dp_intervals @@ -272,6 +275,7 @@ def scoped_collection row :valid_till, &:decorated_valid_till row :rate_policy, &:rate_policy_name row :reverse_billing + row :allow_package_billing row :initial_interval row :next_interval row :use_dp_intervals diff --git a/app/models/billing/package_counter.rb b/app/models/billing/package_counter.rb new file mode 100644 index 000000000..d7d43b735 --- /dev/null +++ b/app/models/billing/package_counter.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: billing.package_counters +# +# id :bigint(8) not null, primary key +# duration :integer(4) default(0), not null +# exclude :boolean default(FALSE), not null +# prefix :string not null +# account_id :integer(4) not null +# service_id :bigint(8) +# +# Indexes +# +# package_counters_account_id_idx (account_id) +# package_counters_prefix_idx (((prefix)::prefix_range)) USING gist +# package_counters_service_id_idx (service_id) +# +# Foreign Keys +# +# package_counters_account_id_fkey (account_id => accounts.id) +# +class Billing::PackageCounter < ApplicationRecord + self.table_name = 'billing.package_counters' + + belongs_to :account, class_name: 'Account' + belongs_to :service, class_name: 'Billing::Service', optional: true + + validates :duration, presence: true + + def display_name + "PC##{id}" + end +end diff --git a/app/models/cdr/cdr.rb b/app/models/cdr/cdr.rb index 19902c6fa..e586153af 100644 --- a/app/models/cdr/cdr.rb +++ b/app/models/cdr/cdr.rb @@ -128,6 +128,7 @@ # orig_call_id :string # orig_gw_external_id :bigint(8) # orig_gw_id :integer(4) +# package_counter_id :bigint(8) # pop_id :integer(4) # rateplan_id :integer(4) # routing_group_id :integer(4) @@ -205,7 +206,7 @@ class Cdr::Cdr < Cdr::Base routing_plan vendor term_gw orig_gw customer_auth vendor_acc customer_acc dst_area customer rateplan pop src_area lnp_database - node sign_term_transport_protocol + node sign_term_transport_protocol package_counter ].freeze include Partitionable @@ -241,6 +242,7 @@ class Cdr::Cdr < Cdr::Base belongs_to :auth_orig_transport_protocol, class_name: 'Equipment::TransportProtocol', foreign_key: :auth_orig_transport_protocol_id, optional: true belongs_to :sign_orig_transport_protocol, class_name: 'Equipment::TransportProtocol', foreign_key: :sign_orig_transport_protocol_id, optional: true belongs_to :sign_term_transport_protocol, class_name: 'Equipment::TransportProtocol', foreign_key: :sign_term_transport_protocol_id, optional: true + belongs_to :package_counter, class_name: 'Billing::PackageCounter', foreign_key: :package_counter_id, optional: true scope :success, -> { where success: true } scope :failure, -> { where success: false } @@ -270,6 +272,7 @@ class Cdr::Cdr < Cdr::Base scope :not_authorized, -> { where('customer_auth_id is null') } scope :bad_routing, -> { where('customer_auth_id is not null AND disconnect_initiator_id=0') } scope :with_trace, -> { where('dump_level_id > 0') } + scope :package_billing, -> { where('package_counter_id is not null') } scope :account_id_eq, ->(account_id) { where('vendor_acc_id =? OR customer_acc_id =?', account_id, account_id) } diff --git a/app/models/report/realtime/bad_routing.rb b/app/models/report/realtime/bad_routing.rb index cbd476a38..1aaf18fa7 100644 --- a/app/models/report/realtime/bad_routing.rb +++ b/app/models/report/realtime/bad_routing.rb @@ -128,6 +128,7 @@ # orig_call_id :string # orig_gw_external_id :bigint(8) # orig_gw_id :integer(4) +# package_counter_id :bigint(8) # pop_id :integer(4) # rateplan_id :integer(4) # routing_group_id :integer(4) diff --git a/app/models/report/realtime/not_authenticated.rb b/app/models/report/realtime/not_authenticated.rb index 9d1326cc4..3edccbad1 100644 --- a/app/models/report/realtime/not_authenticated.rb +++ b/app/models/report/realtime/not_authenticated.rb @@ -128,6 +128,7 @@ # orig_call_id :string # orig_gw_external_id :bigint(8) # orig_gw_id :integer(4) +# package_counter_id :bigint(8) # pop_id :integer(4) # rateplan_id :integer(4) # routing_group_id :integer(4) diff --git a/app/models/report/realtime/origination_performance.rb b/app/models/report/realtime/origination_performance.rb index 108bd22f7..c796bd9fd 100644 --- a/app/models/report/realtime/origination_performance.rb +++ b/app/models/report/realtime/origination_performance.rb @@ -128,6 +128,7 @@ # orig_call_id :string # orig_gw_external_id :bigint(8) # orig_gw_id :integer(4) +# package_counter_id :bigint(8) # pop_id :integer(4) # rateplan_id :integer(4) # routing_group_id :integer(4) diff --git a/app/models/report/realtime/termination_distribution.rb b/app/models/report/realtime/termination_distribution.rb index 730af7491..a7dde5e51 100644 --- a/app/models/report/realtime/termination_distribution.rb +++ b/app/models/report/realtime/termination_distribution.rb @@ -128,6 +128,7 @@ # orig_call_id :string # orig_gw_external_id :bigint(8) # orig_gw_id :integer(4) +# package_counter_id :bigint(8) # pop_id :integer(4) # rateplan_id :integer(4) # routing_group_id :integer(4) diff --git a/app/models/routing/destination.rb b/app/models/routing/destination.rb index a137e7e3d..f88f02e7e 100644 --- a/app/models/routing/destination.rb +++ b/app/models/routing/destination.rb @@ -6,6 +6,7 @@ # # id :bigint(8) not null, primary key # acd_limit :float default(0.0), not null +# allow_package_billing :boolean default(FALSE), not null # asr_limit :float default(0.0), not null # connect_fee :decimal(, ) default(0.0) # dp_margin_fixed :decimal(, ) default(0.0), not null diff --git a/app/policies/billing/package_counter_policy.rb b/app/policies/billing/package_counter_policy.rb new file mode 100644 index 000000000..662b10ae3 --- /dev/null +++ b/app/policies/billing/package_counter_policy.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Billing + class PackageCounterPolicy < ::RolePolicy + section 'Billing/PackageCounter' + class Scope < ::RolePolicy::Scope + end + end +end diff --git a/config/policy_roles.yml.distr b/config/policy_roles.yml.distr index 2f5287cef..064faa837 100644 --- a/config/policy_roles.yml.distr +++ b/config/policy_roles.yml.distr @@ -55,6 +55,11 @@ user: change: true remove: true perform: true + Billing/PackageCounter: + read: true + change: true + remove: true + perform: true Billing/Contact: read: true change: true diff --git a/db/cdr_migrate/20240617084103_package_billing_cdr.rb b/db/cdr_migrate/20240617084103_package_billing_cdr.rb new file mode 100644 index 000000000..a9b9f8fe6 --- /dev/null +++ b/db/cdr_migrate/20240617084103_package_billing_cdr.rb @@ -0,0 +1,1122 @@ +class PackageBillingCdr < ActiveRecord::Migration[7.0] + def up + execute %q{ + alter table cdr.cdr add package_counter_id bigint; + alter type switch.dynamic_cdr_data_ty add attribute package_counter_id bigint; + +CREATE or replace FUNCTION switch.writecdr(i_is_master boolean, i_node_id integer, i_pop_id integer, i_routing_attempt integer, i_is_last_cdr boolean, i_lega_transport_protocol_id smallint, i_lega_local_ip character varying, i_lega_local_port integer, i_lega_remote_ip character varying, i_lega_remote_port integer, i_legb_transport_protocol_id smallint, i_legb_local_ip character varying, i_legb_local_port integer, i_legb_remote_ip character varying, i_legb_remote_port integer, i_legb_ruri character varying, i_legb_outbound_proxy character varying, i_time_data json, i_early_media_present boolean, i_legb_disconnect_code integer, i_legb_disconnect_reason character varying, i_disconnect_initiator integer, i_internal_disconnect_code integer, i_internal_disconnect_reason character varying, i_lega_disconnect_code integer, i_lega_disconnect_reason character varying, i_orig_call_id character varying, i_term_call_id character varying, i_local_tag character varying, i_legb_local_tag character varying, i_msg_logger_path character varying, i_dump_level_id smallint, i_audio_recorded boolean, i_rtp_stats_data json, i_rtp_statistics json, i_global_tag character varying, i_resources character varying, i_active_resources json, i_failed_resource_type_id smallint, i_failed_resource_id bigint, i_dtmf_events json, i_versions json, i_is_redirected boolean, i_dynamic json, i_lega_headers json, i_legb_headers json, i_lega_identity json) RETURNS integer + LANGUAGE plpgsql SECURITY DEFINER COST 10 + AS $$ +DECLARE + v_cdr cdr.cdr%rowtype; + + v_time_data switch.time_data_ty; + v_version_data switch.versions_ty; + v_dynamic switch.dynamic_cdr_data_ty; + + v_nozerolen boolean; + v_config sys.config%rowtype; + + v_rtp_rx_stream_data rtp_statistics.rx_streams%rowtype; + v_rtp_tx_stream_data rtp_statistics.tx_streams%rowtype; + + v_lega_headers switch.lega_headers_ty; + v_legb_headers switch.legb_headers_ty; + v_lega_reason switch.reason_ty; + v_legb_reason switch.reason_ty; +BEGIN + -- raise warning 'type: % id: %', i_failed_resource_type_id, i_failed_resource_id; + -- RAISE warning 'DTMF: %', i_dtmf_events; + + v_time_data:=json_populate_record(null::switch.time_data_ty, i_time_data); + v_version_data:=json_populate_record(null::switch.versions_ty, i_versions); + v_dynamic:=json_populate_record(null::switch.dynamic_cdr_data_ty, i_dynamic); + + v_lega_headers:=json_populate_record(null::switch.lega_headers_ty, i_lega_headers); + v_legb_headers:=json_populate_record(null::switch.legb_headers_ty, i_legb_headers); + + v_cdr.p_charge_info_in = v_lega_headers.p_charge_info; + + v_lega_reason = v_lega_headers.reason; + v_cdr.lega_q850_cause = v_lega_reason.q850_cause; + v_cdr.lega_q850_text = v_lega_reason.q850_text; + v_cdr.lega_q850_params = v_lega_reason.q850_params; + + v_legb_reason = v_legb_headers.reason; + v_cdr.legb_q850_cause = v_legb_reason.q850_cause; + v_cdr.legb_q850_text = v_legb_reason.q850_text; + v_cdr.legb_q850_params = v_legb_reason.q850_params; + + v_cdr.lega_identity = i_lega_identity; + v_cdr.lega_ss_status_id = v_dynamic.lega_ss_status_id; + v_cdr.legb_ss_status_id = v_dynamic.legb_ss_status_id; + + v_cdr.metadata = v_dynamic.metadata::jsonb; + + v_cdr.core_version=v_version_data.core; + v_cdr.yeti_version=v_version_data.yeti; + v_cdr.lega_user_agent=v_version_data.aleg; + v_cdr.legb_user_agent=v_version_data.bleg; + + v_cdr.pop_id=i_pop_id; + v_cdr.node_id=i_node_id; + + v_cdr.src_name_in:=v_dynamic.src_name_in; + v_cdr.src_name_out:=v_dynamic.src_name_out; + + v_cdr.diversion_in:=v_dynamic.diversion_in; + v_cdr.diversion_out:=v_dynamic.diversion_out; + + v_cdr.customer_id:=v_dynamic.customer_id; + v_cdr.customer_external_id:=v_dynamic.customer_external_id; + + v_cdr.customer_acc_id:=v_dynamic.customer_acc_id; + v_cdr.customer_account_check_balance=v_dynamic.customer_acc_check_balance; + v_cdr.customer_acc_external_id=v_dynamic.customer_acc_external_id; + v_cdr.customer_acc_vat:=v_dynamic.customer_acc_vat; + + v_cdr.customer_auth_id:=v_dynamic.customer_auth_id; + v_cdr.customer_auth_external_id:=v_dynamic.customer_auth_external_id; + v_cdr.customer_auth_external_type:=v_dynamic.customer_auth_external_type; + v_cdr.customer_auth_name:=v_dynamic.customer_auth_name; + + v_cdr.vendor_id:=v_dynamic.vendor_id; + v_cdr.vendor_external_id:=v_dynamic.vendor_external_id; + v_cdr.vendor_acc_id:=v_dynamic.vendor_acc_id; + v_cdr.vendor_acc_external_id:=v_dynamic.vendor_acc_external_id; + + v_cdr.destination_id:=v_dynamic.destination_id; + v_cdr.destination_prefix:=v_dynamic.destination_prefix; + v_cdr.dialpeer_id:=v_dynamic.dialpeer_id; + v_cdr.dialpeer_prefix:=v_dynamic.dialpeer_prefix; + + v_cdr.orig_gw_id:=v_dynamic.orig_gw_id; + v_cdr.orig_gw_external_id:=v_dynamic.orig_gw_external_id; + v_cdr.term_gw_id:=v_dynamic.term_gw_id; + v_cdr.term_gw_external_id:=v_dynamic.term_gw_external_id; + + v_cdr.routing_group_id:=v_dynamic.routing_group_id; + v_cdr.rateplan_id:=v_dynamic.rateplan_id; + + v_cdr.routing_attempt=i_routing_attempt; + v_cdr.is_last_cdr=i_is_last_cdr; + + v_cdr.package_counter_id = v_dynamic.package_counter_id; + v_cdr.destination_initial_rate:=v_dynamic.destination_initial_rate::numeric; + v_cdr.destination_next_rate:=v_dynamic.destination_next_rate::numeric; + v_cdr.destination_initial_interval:=v_dynamic.destination_initial_interval; + v_cdr.destination_next_interval:=v_dynamic.destination_next_interval; + v_cdr.destination_fee:=v_dynamic.destination_fee; + v_cdr.destination_rate_policy_id:=v_dynamic.destination_rate_policy_id; + v_cdr.destination_reverse_billing=v_dynamic.destination_reverse_billing; + + v_cdr.dialpeer_initial_rate:=v_dynamic.dialpeer_initial_rate::numeric; + v_cdr.dialpeer_next_rate:=v_dynamic.dialpeer_next_rate::numeric; + v_cdr.dialpeer_initial_interval:=v_dynamic.dialpeer_initial_interval; + v_cdr.dialpeer_next_interval:=v_dynamic.dialpeer_next_interval; + v_cdr.dialpeer_fee:=v_dynamic.dialpeer_fee; + v_cdr.dialpeer_reverse_billing=v_dynamic.dialpeer_reverse_billing; + + /* sockets addresses */ + v_cdr.sign_orig_transport_protocol_id=i_lega_transport_protocol_id; + v_cdr.sign_orig_ip=i_lega_remote_ip; + v_cdr.sign_orig_port=NULLIF(i_lega_remote_port,0); + v_cdr.sign_orig_local_ip=i_lega_local_ip; + v_cdr.sign_orig_local_port=NULLIF(i_lega_local_port,0); + + v_cdr.sign_term_transport_protocol_id=i_legb_transport_protocol_id; + v_cdr.sign_term_ip=i_legb_remote_ip; + v_cdr.sign_term_port=NULLIF(i_legb_remote_port,0); + v_cdr.sign_term_local_ip=i_legb_local_ip; + v_cdr.sign_term_local_port=NULLIF(i_legb_local_port,0); + + v_cdr.local_tag=i_local_tag; + v_cdr.legb_local_tag=i_legb_local_tag; + v_cdr.legb_ruri=i_legb_ruri; + v_cdr.legb_outbound_proxy=i_legb_outbound_proxy; + + v_cdr.is_redirected=i_is_redirected; + + /* Call time data */ + v_cdr.time_start:=to_timestamp(v_time_data.time_start); + + select into strict v_config * from sys.config; + + if v_time_data.time_connect is not null then + v_cdr.time_connect:=to_timestamp(v_time_data.time_connect); + v_cdr.duration:=switch.duration_round(v_config, v_time_data.time_end-v_time_data.time_connect); -- rounding + v_nozerolen:=true; + v_cdr.success=true; + else + v_cdr.time_connect:=NULL; + v_cdr.duration:=0; + v_nozerolen:=false; + v_cdr.success=false; + end if; + v_cdr.routing_delay=(v_time_data.leg_b_time-v_time_data.time_start)::real; + v_cdr.pdd=(coalesce(v_time_data.time_18x,v_time_data.time_connect)-v_time_data.time_start)::real; + v_cdr.rtt=(coalesce(v_time_data.time_1xx,v_time_data.time_18x,v_time_data.time_connect)-v_time_data.leg_b_time)::real; + v_cdr.early_media_present=i_early_media_present; + + v_cdr.time_end:=to_timestamp(v_time_data.time_end); + + -- DC processing + v_cdr.legb_disconnect_code:=i_legb_disconnect_code; + v_cdr.legb_disconnect_reason:=i_legb_disconnect_reason; + v_cdr.disconnect_initiator_id:=i_disconnect_initiator; + v_cdr.internal_disconnect_code:=i_internal_disconnect_code; + v_cdr.internal_disconnect_reason:=i_internal_disconnect_reason; + v_cdr.lega_disconnect_code:=i_lega_disconnect_code; + v_cdr.lega_disconnect_reason:=i_lega_disconnect_reason; + + v_cdr.src_prefix_in:=v_dynamic.src_prefix_in; + v_cdr.src_prefix_out:=v_dynamic.src_prefix_out; + v_cdr.dst_prefix_in:=v_dynamic.dst_prefix_in; + v_cdr.dst_prefix_out:=v_dynamic.dst_prefix_out; + + v_cdr.orig_call_id=i_orig_call_id; + v_cdr.term_call_id=i_term_call_id; + + /* removed */ + --v_cdr.dump_file:=i_msg_logger_path; + + v_cdr.dump_level_id:=i_dump_level_id; + v_cdr.audio_recorded:=i_audio_recorded; + + v_cdr.auth_orig_transport_protocol_id=v_dynamic.auth_orig_protocol_id; + v_cdr.auth_orig_ip:=v_dynamic.auth_orig_ip; + v_cdr.auth_orig_ip:=v_dynamic.auth_orig_ip; + v_cdr.auth_orig_port:=v_dynamic.auth_orig_port; + + perform switch.write_rtp_statistics( + i_rtp_statistics, + i_pop_id, + i_node_id, + v_dynamic.orig_gw_id, + v_dynamic.orig_gw_external_id, + v_dynamic.term_gw_id, + v_dynamic.term_gw_external_id, + i_local_tag, + i_legb_local_tag + ); + + v_cdr.global_tag=i_global_tag; + + v_cdr.src_country_id=v_dynamic.src_country_id; + v_cdr.src_network_id=v_dynamic.src_network_id; + v_cdr.dst_country_id=v_dynamic.dst_country_id; + v_cdr.dst_network_id=v_dynamic.dst_network_id; + v_cdr.dst_prefix_routing=v_dynamic.dst_prefix_routing; + v_cdr.src_prefix_routing=v_dynamic.src_prefix_routing; + v_cdr.routing_plan_id=v_dynamic.routing_plan_id; + v_cdr.lrn=v_dynamic.lrn; + v_cdr.lnp_database_id=v_dynamic.lnp_database_id; + + v_cdr.ruri_domain=v_dynamic.ruri_domain; + v_cdr.to_domain=v_dynamic.to_domain; + v_cdr.from_domain=v_dynamic.from_domain; + + v_cdr.src_area_id=v_dynamic.src_area_id; + v_cdr.dst_area_id=v_dynamic.dst_area_id; + v_cdr.routing_tag_ids=v_dynamic.routing_tag_ids; + + + v_cdr.id:=nextval('cdr.cdr_id_seq'::regclass); + v_cdr.uuid:=public.uuid_generate_v1(); + + v_cdr.pai_in=v_dynamic.pai_in; + v_cdr.ppi_in=v_dynamic.ppi_in; + v_cdr.privacy_in=v_dynamic.privacy_in; + v_cdr.rpid_in=v_dynamic.rpid_in; + v_cdr.rpid_privacy_in=v_dynamic.rpid_privacy_in; + v_cdr.pai_out=v_dynamic.pai_out; + v_cdr.ppi_out=v_dynamic.ppi_out; + v_cdr.privacy_out=v_dynamic.privacy_out; + v_cdr.rpid_out=v_dynamic.rpid_out; + v_cdr.rpid_privacy_out=v_dynamic.rpid_privacy_out; + + v_cdr.failed_resource_type_id = i_failed_resource_type_id; + v_cdr.failed_resource_id = i_failed_resource_id; + + v_cdr:=billing.bill_cdr(v_cdr); + + if not v_config.disable_realtime_statistics then + perform stats.update_rt_stats(v_cdr); + end if; + + v_cdr.customer_price = switch.customer_price_round(v_config, v_cdr.customer_price); + v_cdr.customer_price_no_vat = switch.customer_price_round(v_config, v_cdr.customer_price_no_vat); + v_cdr.vendor_price = switch.vendor_price_round(v_config, v_cdr.vendor_price); + + -- generate event to billing engine + perform event.billing_insert_event('cdr_full',v_cdr); + perform event.streaming_insert_event(v_cdr); + INSERT INTO cdr.cdr VALUES( v_cdr.*); + RETURN 0; +END; +$$; + +CREATE or replace FUNCTION switch.writecdr(i_is_master boolean, i_node_id integer, i_pop_id integer, i_routing_attempt integer, i_is_last_cdr boolean, i_lega_transport_protocol_id smallint, i_lega_local_ip character varying, i_lega_local_port integer, i_lega_remote_ip character varying, i_lega_remote_port integer, i_legb_transport_protocol_id smallint, i_legb_local_ip character varying, i_legb_local_port integer, i_legb_remote_ip character varying, i_legb_remote_port integer, i_legb_ruri character varying, i_legb_outbound_proxy character varying, i_time_data json, i_early_media_present boolean, i_legb_disconnect_code integer, i_legb_disconnect_reason character varying, i_disconnect_initiator integer, i_internal_disconnect_code integer, i_internal_disconnect_reason character varying, i_lega_disconnect_code integer, i_lega_disconnect_reason character varying, i_internal_disconnect_code_id smallint, i_orig_call_id character varying, i_term_call_id character varying, i_local_tag character varying, i_legb_local_tag character varying, i_msg_logger_path character varying, i_dump_level_id smallint, i_audio_recorded boolean, i_rtp_stats_data json, i_rtp_statistics json, i_global_tag character varying, i_resources character varying, i_active_resources json, i_failed_resource_type_id smallint, i_failed_resource_id bigint, i_dtmf_events json, i_versions json, i_is_redirected boolean, i_dynamic json, i_lega_headers json, i_legb_headers json, i_lega_identity json) RETURNS integer + LANGUAGE plpgsql SECURITY DEFINER COST 10 + AS $$ +DECLARE + v_cdr cdr.cdr%rowtype; + + v_time_data switch.time_data_ty; + v_version_data switch.versions_ty; + v_dynamic switch.dynamic_cdr_data_ty; + + v_nozerolen boolean; + v_config sys.config%rowtype; + + v_rtp_rx_stream_data rtp_statistics.rx_streams%rowtype; + v_rtp_tx_stream_data rtp_statistics.tx_streams%rowtype; + + v_lega_headers switch.lega_headers_ty; + v_legb_headers switch.legb_headers_ty; + v_lega_reason switch.reason_ty; + v_legb_reason switch.reason_ty; +BEGIN + -- raise warning 'type: % id: %', i_failed_resource_type_id, i_failed_resource_id; + -- RAISE warning 'DTMF: %', i_dtmf_events; + + v_time_data:=json_populate_record(null::switch.time_data_ty, i_time_data); + v_version_data:=json_populate_record(null::switch.versions_ty, i_versions); + v_dynamic:=json_populate_record(null::switch.dynamic_cdr_data_ty, i_dynamic); + + v_lega_headers:=json_populate_record(null::switch.lega_headers_ty, i_lega_headers); + v_legb_headers:=json_populate_record(null::switch.legb_headers_ty, i_legb_headers); + + v_cdr.p_charge_info_in = v_lega_headers.p_charge_info; + + v_lega_reason = v_lega_headers.reason; + v_cdr.lega_q850_cause = v_lega_reason.q850_cause; + v_cdr.lega_q850_text = v_lega_reason.q850_text; + v_cdr.lega_q850_params = v_lega_reason.q850_params; + + v_legb_reason = v_legb_headers.reason; + v_cdr.legb_q850_cause = v_legb_reason.q850_cause; + v_cdr.legb_q850_text = v_legb_reason.q850_text; + v_cdr.legb_q850_params = v_legb_reason.q850_params; + + v_cdr.lega_identity = i_lega_identity; + v_cdr.lega_ss_status_id = v_dynamic.lega_ss_status_id; + v_cdr.legb_ss_status_id = v_dynamic.legb_ss_status_id; + + v_cdr.metadata = v_dynamic.metadata::jsonb; + + v_cdr.core_version=v_version_data.core; + v_cdr.yeti_version=v_version_data.yeti; + v_cdr.lega_user_agent=v_version_data.aleg; + v_cdr.legb_user_agent=v_version_data.bleg; + + v_cdr.pop_id=i_pop_id; + v_cdr.node_id=i_node_id; + + v_cdr.src_name_in:=v_dynamic.src_name_in; + v_cdr.src_name_out:=v_dynamic.src_name_out; + + v_cdr.diversion_in:=v_dynamic.diversion_in; + v_cdr.diversion_out:=v_dynamic.diversion_out; + + v_cdr.customer_id:=v_dynamic.customer_id; + v_cdr.customer_external_id:=v_dynamic.customer_external_id; + + v_cdr.customer_acc_id:=v_dynamic.customer_acc_id; + v_cdr.customer_account_check_balance=v_dynamic.customer_acc_check_balance; + v_cdr.customer_acc_external_id=v_dynamic.customer_acc_external_id; + v_cdr.customer_acc_vat:=v_dynamic.customer_acc_vat; + + v_cdr.customer_auth_id:=v_dynamic.customer_auth_id; + v_cdr.customer_auth_external_id:=v_dynamic.customer_auth_external_id; + v_cdr.customer_auth_external_type:=v_dynamic.customer_auth_external_type; + v_cdr.customer_auth_name:=v_dynamic.customer_auth_name; + + v_cdr.vendor_id:=v_dynamic.vendor_id; + v_cdr.vendor_external_id:=v_dynamic.vendor_external_id; + v_cdr.vendor_acc_id:=v_dynamic.vendor_acc_id; + v_cdr.vendor_acc_external_id:=v_dynamic.vendor_acc_external_id; + + v_cdr.package_counter_id = v_dynamic.package_counter_id; + v_cdr.destination_id:=v_dynamic.destination_id; + v_cdr.destination_prefix:=v_dynamic.destination_prefix; + v_cdr.dialpeer_id:=v_dynamic.dialpeer_id; + v_cdr.dialpeer_prefix:=v_dynamic.dialpeer_prefix; + + v_cdr.orig_gw_id:=v_dynamic.orig_gw_id; + v_cdr.orig_gw_external_id:=v_dynamic.orig_gw_external_id; + v_cdr.term_gw_id:=v_dynamic.term_gw_id; + v_cdr.term_gw_external_id:=v_dynamic.term_gw_external_id; + + v_cdr.routing_group_id:=v_dynamic.routing_group_id; + v_cdr.rateplan_id:=v_dynamic.rateplan_id; + + v_cdr.routing_attempt=i_routing_attempt; + v_cdr.is_last_cdr=i_is_last_cdr; + + v_cdr.destination_initial_rate:=v_dynamic.destination_initial_rate::numeric; + v_cdr.destination_next_rate:=v_dynamic.destination_next_rate::numeric; + v_cdr.destination_initial_interval:=v_dynamic.destination_initial_interval; + v_cdr.destination_next_interval:=v_dynamic.destination_next_interval; + v_cdr.destination_fee:=v_dynamic.destination_fee; + v_cdr.destination_rate_policy_id:=v_dynamic.destination_rate_policy_id; + v_cdr.destination_reverse_billing=v_dynamic.destination_reverse_billing; + + v_cdr.dialpeer_initial_rate:=v_dynamic.dialpeer_initial_rate::numeric; + v_cdr.dialpeer_next_rate:=v_dynamic.dialpeer_next_rate::numeric; + v_cdr.dialpeer_initial_interval:=v_dynamic.dialpeer_initial_interval; + v_cdr.dialpeer_next_interval:=v_dynamic.dialpeer_next_interval; + v_cdr.dialpeer_fee:=v_dynamic.dialpeer_fee; + v_cdr.dialpeer_reverse_billing=v_dynamic.dialpeer_reverse_billing; + + /* sockets addresses */ + v_cdr.sign_orig_transport_protocol_id=i_lega_transport_protocol_id; + v_cdr.sign_orig_ip=i_lega_remote_ip; + v_cdr.sign_orig_port=NULLIF(i_lega_remote_port,0); + v_cdr.sign_orig_local_ip=i_lega_local_ip; + v_cdr.sign_orig_local_port=NULLIF(i_lega_local_port,0); + + v_cdr.sign_term_transport_protocol_id=i_legb_transport_protocol_id; + v_cdr.sign_term_ip=i_legb_remote_ip; + v_cdr.sign_term_port=NULLIF(i_legb_remote_port,0); + v_cdr.sign_term_local_ip=i_legb_local_ip; + v_cdr.sign_term_local_port=NULLIF(i_legb_local_port,0); + + v_cdr.local_tag=i_local_tag; + v_cdr.legb_local_tag=i_legb_local_tag; + v_cdr.legb_ruri=i_legb_ruri; + v_cdr.legb_outbound_proxy=i_legb_outbound_proxy; + + v_cdr.is_redirected=i_is_redirected; + + /* Call time data */ + v_cdr.time_start:=to_timestamp(v_time_data.time_start); + + select into strict v_config * from sys.config; + + if v_time_data.time_connect is not null then + v_cdr.time_connect:=to_timestamp(v_time_data.time_connect); + v_cdr.duration:=switch.duration_round(v_config, v_time_data.time_end-v_time_data.time_connect); -- rounding + v_nozerolen:=true; + v_cdr.success=true; + else + v_cdr.time_connect:=NULL; + v_cdr.duration:=0; + v_nozerolen:=false; + v_cdr.success=false; + end if; + v_cdr.routing_delay=(v_time_data.leg_b_time-v_time_data.time_start)::real; + v_cdr.pdd=(coalesce(v_time_data.time_18x,v_time_data.time_connect)-v_time_data.time_start)::real; + v_cdr.rtt=(coalesce(v_time_data.time_1xx,v_time_data.time_18x,v_time_data.time_connect)-v_time_data.leg_b_time)::real; + v_cdr.early_media_present=i_early_media_present; + + v_cdr.time_end:=to_timestamp(v_time_data.time_end); + + -- DC processing + v_cdr.legb_disconnect_code=i_legb_disconnect_code; + v_cdr.legb_disconnect_reason=i_legb_disconnect_reason; + v_cdr.disconnect_initiator_id=i_disconnect_initiator; + v_cdr.internal_disconnect_code_id=i_internal_disconnect_code_id; + v_cdr.internal_disconnect_code=i_internal_disconnect_code; + v_cdr.internal_disconnect_reason=i_internal_disconnect_reason; + v_cdr.lega_disconnect_code=i_lega_disconnect_code; + v_cdr.lega_disconnect_reason=i_lega_disconnect_reason; + + v_cdr.src_prefix_in:=v_dynamic.src_prefix_in; + v_cdr.src_prefix_out:=v_dynamic.src_prefix_out; + v_cdr.dst_prefix_in:=v_dynamic.dst_prefix_in; + v_cdr.dst_prefix_out:=v_dynamic.dst_prefix_out; + + v_cdr.orig_call_id=i_orig_call_id; + v_cdr.term_call_id=i_term_call_id; + + /* removed */ + --v_cdr.dump_file:=i_msg_logger_path; + + v_cdr.dump_level_id:=i_dump_level_id; + v_cdr.audio_recorded:=i_audio_recorded; + + v_cdr.auth_orig_transport_protocol_id=v_dynamic.auth_orig_protocol_id; + v_cdr.auth_orig_ip:=v_dynamic.auth_orig_ip; + v_cdr.auth_orig_ip:=v_dynamic.auth_orig_ip; + v_cdr.auth_orig_port:=v_dynamic.auth_orig_port; + + perform switch.write_rtp_statistics( + i_rtp_statistics, + i_pop_id, + i_node_id, + v_dynamic.orig_gw_id, + v_dynamic.orig_gw_external_id, + v_dynamic.term_gw_id, + v_dynamic.term_gw_external_id, + i_local_tag, + i_legb_local_tag + ); + + v_cdr.global_tag=i_global_tag; + + v_cdr.src_country_id=v_dynamic.src_country_id; + v_cdr.src_network_id=v_dynamic.src_network_id; + v_cdr.dst_country_id=v_dynamic.dst_country_id; + v_cdr.dst_network_id=v_dynamic.dst_network_id; + v_cdr.dst_prefix_routing=v_dynamic.dst_prefix_routing; + v_cdr.src_prefix_routing=v_dynamic.src_prefix_routing; + v_cdr.routing_plan_id=v_dynamic.routing_plan_id; + v_cdr.lrn=v_dynamic.lrn; + v_cdr.lnp_database_id=v_dynamic.lnp_database_id; + + v_cdr.ruri_domain=v_dynamic.ruri_domain; + v_cdr.to_domain=v_dynamic.to_domain; + v_cdr.from_domain=v_dynamic.from_domain; + + v_cdr.src_area_id=v_dynamic.src_area_id; + v_cdr.dst_area_id=v_dynamic.dst_area_id; + v_cdr.routing_tag_ids=v_dynamic.routing_tag_ids; + + + v_cdr.id:=nextval('cdr.cdr_id_seq'::regclass); + v_cdr.uuid:=public.uuid_generate_v1(); + + v_cdr.pai_in=v_dynamic.pai_in; + v_cdr.ppi_in=v_dynamic.ppi_in; + v_cdr.privacy_in=v_dynamic.privacy_in; + v_cdr.rpid_in=v_dynamic.rpid_in; + v_cdr.rpid_privacy_in=v_dynamic.rpid_privacy_in; + v_cdr.pai_out=v_dynamic.pai_out; + v_cdr.ppi_out=v_dynamic.ppi_out; + v_cdr.privacy_out=v_dynamic.privacy_out; + v_cdr.rpid_out=v_dynamic.rpid_out; + v_cdr.rpid_privacy_out=v_dynamic.rpid_privacy_out; + + v_cdr.failed_resource_type_id = i_failed_resource_type_id; + v_cdr.failed_resource_id = i_failed_resource_id; + + v_cdr:=billing.bill_cdr(v_cdr); + + if not v_config.disable_realtime_statistics then + perform stats.update_rt_stats(v_cdr); + end if; + + v_cdr.customer_price = switch.customer_price_round(v_config, v_cdr.customer_price); + v_cdr.customer_price_no_vat = switch.customer_price_round(v_config, v_cdr.customer_price_no_vat); + v_cdr.vendor_price = switch.vendor_price_round(v_config, v_cdr.vendor_price); + + -- generate event to billing engine + perform event.billing_insert_event('cdr_full',v_cdr); + perform event.streaming_insert_event(v_cdr); + INSERT INTO cdr.cdr VALUES( v_cdr.*); + RETURN 0; +END; +$$; + +CREATE or replace FUNCTION billing.bill_cdr(i_cdr cdr.cdr) RETURNS cdr.cdr + LANGUAGE plpgsql COST 10 + AS $$ +DECLARE + _v billing.interval_billing_data%rowtype; +BEGIN + if i_cdr.duration>0 and i_cdr.success then -- run billing. + if i_cdr.package_counter_id is not null then + -- running billing with fake rates to calculate duration + _v=billing.interval_billing( + i_cdr.duration, + '1.0'::numeric, + '1.0'::numeric, + '1.0'::numeric, + i_cdr.destination_initial_interval, + i_cdr.destination_next_interval, + 0::integer); + i_cdr.customer_price=0; + i_cdr.customer_price_no_vat=0; + i_cdr.customer_duration=_v.duration; + else + _v=billing.interval_billing( + i_cdr.duration, + i_cdr.destination_fee, + i_cdr.destination_initial_rate, + i_cdr.destination_next_rate, + i_cdr.destination_initial_interval, + i_cdr.destination_next_interval, + i_cdr.customer_acc_vat); + i_cdr.customer_price=_v.amount; + i_cdr.customer_price_no_vat=_v.amount_no_vat; + i_cdr.customer_duration=_v.duration; + end if; + + _v=billing.interval_billing( + i_cdr.duration, + i_cdr.dialpeer_fee, + i_cdr.dialpeer_initial_rate, + i_cdr.dialpeer_next_rate, + i_cdr.dialpeer_initial_interval, + i_cdr.dialpeer_next_interval, + 0); + i_cdr.vendor_price=_v.amount; + i_cdr.vendor_duration=_v.duration; + i_cdr.profit=i_cdr.customer_price-i_cdr.vendor_price; + else + i_cdr.customer_price=0; + i_cdr.customer_price_no_vat=0; + i_cdr.vendor_price=0; + i_cdr.profit=0; + end if; + RETURN i_cdr; +END; +$$; + +} + end + + def down + execute %q{ + +CREATE or replace FUNCTION switch.writecdr(i_is_master boolean, i_node_id integer, i_pop_id integer, i_routing_attempt integer, i_is_last_cdr boolean, i_lega_transport_protocol_id smallint, i_lega_local_ip character varying, i_lega_local_port integer, i_lega_remote_ip character varying, i_lega_remote_port integer, i_legb_transport_protocol_id smallint, i_legb_local_ip character varying, i_legb_local_port integer, i_legb_remote_ip character varying, i_legb_remote_port integer, i_legb_ruri character varying, i_legb_outbound_proxy character varying, i_time_data json, i_early_media_present boolean, i_legb_disconnect_code integer, i_legb_disconnect_reason character varying, i_disconnect_initiator integer, i_internal_disconnect_code integer, i_internal_disconnect_reason character varying, i_lega_disconnect_code integer, i_lega_disconnect_reason character varying, i_orig_call_id character varying, i_term_call_id character varying, i_local_tag character varying, i_legb_local_tag character varying, i_msg_logger_path character varying, i_dump_level_id smallint, i_audio_recorded boolean, i_rtp_stats_data json, i_rtp_statistics json, i_global_tag character varying, i_resources character varying, i_active_resources json, i_failed_resource_type_id smallint, i_failed_resource_id bigint, i_dtmf_events json, i_versions json, i_is_redirected boolean, i_dynamic json, i_lega_headers json, i_legb_headers json, i_lega_identity json) RETURNS integer + LANGUAGE plpgsql SECURITY DEFINER COST 10 + AS $$ +DECLARE + v_cdr cdr.cdr%rowtype; + + v_time_data switch.time_data_ty; + v_version_data switch.versions_ty; + v_dynamic switch.dynamic_cdr_data_ty; + + v_nozerolen boolean; + v_config sys.config%rowtype; + + v_rtp_rx_stream_data rtp_statistics.rx_streams%rowtype; + v_rtp_tx_stream_data rtp_statistics.tx_streams%rowtype; + + v_lega_headers switch.lega_headers_ty; + v_legb_headers switch.legb_headers_ty; + v_lega_reason switch.reason_ty; + v_legb_reason switch.reason_ty; +BEGIN + -- raise warning 'type: % id: %', i_failed_resource_type_id, i_failed_resource_id; + -- RAISE warning 'DTMF: %', i_dtmf_events; + + v_time_data:=json_populate_record(null::switch.time_data_ty, i_time_data); + v_version_data:=json_populate_record(null::switch.versions_ty, i_versions); + v_dynamic:=json_populate_record(null::switch.dynamic_cdr_data_ty, i_dynamic); + + v_lega_headers:=json_populate_record(null::switch.lega_headers_ty, i_lega_headers); + v_legb_headers:=json_populate_record(null::switch.legb_headers_ty, i_legb_headers); + + v_cdr.p_charge_info_in = v_lega_headers.p_charge_info; + + v_lega_reason = v_lega_headers.reason; + v_cdr.lega_q850_cause = v_lega_reason.q850_cause; + v_cdr.lega_q850_text = v_lega_reason.q850_text; + v_cdr.lega_q850_params = v_lega_reason.q850_params; + + v_legb_reason = v_legb_headers.reason; + v_cdr.legb_q850_cause = v_legb_reason.q850_cause; + v_cdr.legb_q850_text = v_legb_reason.q850_text; + v_cdr.legb_q850_params = v_legb_reason.q850_params; + + v_cdr.lega_identity = i_lega_identity; + v_cdr.lega_ss_status_id = v_dynamic.lega_ss_status_id; + v_cdr.legb_ss_status_id = v_dynamic.legb_ss_status_id; + + v_cdr.metadata = v_dynamic.metadata::jsonb; + + v_cdr.core_version=v_version_data.core; + v_cdr.yeti_version=v_version_data.yeti; + v_cdr.lega_user_agent=v_version_data.aleg; + v_cdr.legb_user_agent=v_version_data.bleg; + + v_cdr.pop_id=i_pop_id; + v_cdr.node_id=i_node_id; + + v_cdr.src_name_in:=v_dynamic.src_name_in; + v_cdr.src_name_out:=v_dynamic.src_name_out; + + v_cdr.diversion_in:=v_dynamic.diversion_in; + v_cdr.diversion_out:=v_dynamic.diversion_out; + + v_cdr.customer_id:=v_dynamic.customer_id; + v_cdr.customer_external_id:=v_dynamic.customer_external_id; + + v_cdr.customer_acc_id:=v_dynamic.customer_acc_id; + v_cdr.customer_account_check_balance=v_dynamic.customer_acc_check_balance; + v_cdr.customer_acc_external_id=v_dynamic.customer_acc_external_id; + v_cdr.customer_acc_vat:=v_dynamic.customer_acc_vat; + + v_cdr.customer_auth_id:=v_dynamic.customer_auth_id; + v_cdr.customer_auth_external_id:=v_dynamic.customer_auth_external_id; + v_cdr.customer_auth_external_type:=v_dynamic.customer_auth_external_type; + v_cdr.customer_auth_name:=v_dynamic.customer_auth_name; + + v_cdr.vendor_id:=v_dynamic.vendor_id; + v_cdr.vendor_external_id:=v_dynamic.vendor_external_id; + v_cdr.vendor_acc_id:=v_dynamic.vendor_acc_id; + v_cdr.vendor_acc_external_id:=v_dynamic.vendor_acc_external_id; + + v_cdr.destination_id:=v_dynamic.destination_id; + v_cdr.destination_prefix:=v_dynamic.destination_prefix; + v_cdr.dialpeer_id:=v_dynamic.dialpeer_id; + v_cdr.dialpeer_prefix:=v_dynamic.dialpeer_prefix; + + v_cdr.orig_gw_id:=v_dynamic.orig_gw_id; + v_cdr.orig_gw_external_id:=v_dynamic.orig_gw_external_id; + v_cdr.term_gw_id:=v_dynamic.term_gw_id; + v_cdr.term_gw_external_id:=v_dynamic.term_gw_external_id; + + v_cdr.routing_group_id:=v_dynamic.routing_group_id; + v_cdr.rateplan_id:=v_dynamic.rateplan_id; + + v_cdr.routing_attempt=i_routing_attempt; + v_cdr.is_last_cdr=i_is_last_cdr; + + v_cdr.destination_initial_rate:=v_dynamic.destination_initial_rate::numeric; + v_cdr.destination_next_rate:=v_dynamic.destination_next_rate::numeric; + v_cdr.destination_initial_interval:=v_dynamic.destination_initial_interval; + v_cdr.destination_next_interval:=v_dynamic.destination_next_interval; + v_cdr.destination_fee:=v_dynamic.destination_fee; + v_cdr.destination_rate_policy_id:=v_dynamic.destination_rate_policy_id; + v_cdr.destination_reverse_billing=v_dynamic.destination_reverse_billing; + + v_cdr.dialpeer_initial_rate:=v_dynamic.dialpeer_initial_rate::numeric; + v_cdr.dialpeer_next_rate:=v_dynamic.dialpeer_next_rate::numeric; + v_cdr.dialpeer_initial_interval:=v_dynamic.dialpeer_initial_interval; + v_cdr.dialpeer_next_interval:=v_dynamic.dialpeer_next_interval; + v_cdr.dialpeer_fee:=v_dynamic.dialpeer_fee; + v_cdr.dialpeer_reverse_billing=v_dynamic.dialpeer_reverse_billing; + + /* sockets addresses */ + v_cdr.sign_orig_transport_protocol_id=i_lega_transport_protocol_id; + v_cdr.sign_orig_ip=i_lega_remote_ip; + v_cdr.sign_orig_port=NULLIF(i_lega_remote_port,0); + v_cdr.sign_orig_local_ip=i_lega_local_ip; + v_cdr.sign_orig_local_port=NULLIF(i_lega_local_port,0); + + v_cdr.sign_term_transport_protocol_id=i_legb_transport_protocol_id; + v_cdr.sign_term_ip=i_legb_remote_ip; + v_cdr.sign_term_port=NULLIF(i_legb_remote_port,0); + v_cdr.sign_term_local_ip=i_legb_local_ip; + v_cdr.sign_term_local_port=NULLIF(i_legb_local_port,0); + + v_cdr.local_tag=i_local_tag; + v_cdr.legb_local_tag=i_legb_local_tag; + v_cdr.legb_ruri=i_legb_ruri; + v_cdr.legb_outbound_proxy=i_legb_outbound_proxy; + + v_cdr.is_redirected=i_is_redirected; + + /* Call time data */ + v_cdr.time_start:=to_timestamp(v_time_data.time_start); + + select into strict v_config * from sys.config; + + if v_time_data.time_connect is not null then + v_cdr.time_connect:=to_timestamp(v_time_data.time_connect); + v_cdr.duration:=switch.duration_round(v_config, v_time_data.time_end-v_time_data.time_connect); -- rounding + v_nozerolen:=true; + v_cdr.success=true; + else + v_cdr.time_connect:=NULL; + v_cdr.duration:=0; + v_nozerolen:=false; + v_cdr.success=false; + end if; + v_cdr.routing_delay=(v_time_data.leg_b_time-v_time_data.time_start)::real; + v_cdr.pdd=(coalesce(v_time_data.time_18x,v_time_data.time_connect)-v_time_data.time_start)::real; + v_cdr.rtt=(coalesce(v_time_data.time_1xx,v_time_data.time_18x,v_time_data.time_connect)-v_time_data.leg_b_time)::real; + v_cdr.early_media_present=i_early_media_present; + + v_cdr.time_end:=to_timestamp(v_time_data.time_end); + + -- DC processing + v_cdr.legb_disconnect_code:=i_legb_disconnect_code; + v_cdr.legb_disconnect_reason:=i_legb_disconnect_reason; + v_cdr.disconnect_initiator_id:=i_disconnect_initiator; + v_cdr.internal_disconnect_code:=i_internal_disconnect_code; + v_cdr.internal_disconnect_reason:=i_internal_disconnect_reason; + v_cdr.lega_disconnect_code:=i_lega_disconnect_code; + v_cdr.lega_disconnect_reason:=i_lega_disconnect_reason; + + v_cdr.src_prefix_in:=v_dynamic.src_prefix_in; + v_cdr.src_prefix_out:=v_dynamic.src_prefix_out; + v_cdr.dst_prefix_in:=v_dynamic.dst_prefix_in; + v_cdr.dst_prefix_out:=v_dynamic.dst_prefix_out; + + v_cdr.orig_call_id=i_orig_call_id; + v_cdr.term_call_id=i_term_call_id; + + /* removed */ + --v_cdr.dump_file:=i_msg_logger_path; + + v_cdr.dump_level_id:=i_dump_level_id; + v_cdr.audio_recorded:=i_audio_recorded; + + v_cdr.auth_orig_transport_protocol_id=v_dynamic.auth_orig_protocol_id; + v_cdr.auth_orig_ip:=v_dynamic.auth_orig_ip; + v_cdr.auth_orig_ip:=v_dynamic.auth_orig_ip; + v_cdr.auth_orig_port:=v_dynamic.auth_orig_port; + + perform switch.write_rtp_statistics( + i_rtp_statistics, + i_pop_id, + i_node_id, + v_dynamic.orig_gw_id, + v_dynamic.orig_gw_external_id, + v_dynamic.term_gw_id, + v_dynamic.term_gw_external_id, + i_local_tag, + i_legb_local_tag + ); + + v_cdr.global_tag=i_global_tag; + + v_cdr.src_country_id=v_dynamic.src_country_id; + v_cdr.src_network_id=v_dynamic.src_network_id; + v_cdr.dst_country_id=v_dynamic.dst_country_id; + v_cdr.dst_network_id=v_dynamic.dst_network_id; + v_cdr.dst_prefix_routing=v_dynamic.dst_prefix_routing; + v_cdr.src_prefix_routing=v_dynamic.src_prefix_routing; + v_cdr.routing_plan_id=v_dynamic.routing_plan_id; + v_cdr.lrn=v_dynamic.lrn; + v_cdr.lnp_database_id=v_dynamic.lnp_database_id; + + v_cdr.ruri_domain=v_dynamic.ruri_domain; + v_cdr.to_domain=v_dynamic.to_domain; + v_cdr.from_domain=v_dynamic.from_domain; + + v_cdr.src_area_id=v_dynamic.src_area_id; + v_cdr.dst_area_id=v_dynamic.dst_area_id; + v_cdr.routing_tag_ids=v_dynamic.routing_tag_ids; + + + v_cdr.id:=nextval('cdr.cdr_id_seq'::regclass); + v_cdr.uuid:=public.uuid_generate_v1(); + + v_cdr.pai_in=v_dynamic.pai_in; + v_cdr.ppi_in=v_dynamic.ppi_in; + v_cdr.privacy_in=v_dynamic.privacy_in; + v_cdr.rpid_in=v_dynamic.rpid_in; + v_cdr.rpid_privacy_in=v_dynamic.rpid_privacy_in; + v_cdr.pai_out=v_dynamic.pai_out; + v_cdr.ppi_out=v_dynamic.ppi_out; + v_cdr.privacy_out=v_dynamic.privacy_out; + v_cdr.rpid_out=v_dynamic.rpid_out; + v_cdr.rpid_privacy_out=v_dynamic.rpid_privacy_out; + + v_cdr.failed_resource_type_id = i_failed_resource_type_id; + v_cdr.failed_resource_id = i_failed_resource_id; + + v_cdr:=billing.bill_cdr(v_cdr); + + if not v_config.disable_realtime_statistics then + perform stats.update_rt_stats(v_cdr); + end if; + + v_cdr.customer_price = switch.customer_price_round(v_config, v_cdr.customer_price); + v_cdr.customer_price_no_vat = switch.customer_price_round(v_config, v_cdr.customer_price_no_vat); + v_cdr.vendor_price = switch.vendor_price_round(v_config, v_cdr.vendor_price); + + -- generate event to billing engine + perform event.billing_insert_event('cdr_full',v_cdr); + perform event.streaming_insert_event(v_cdr); + INSERT INTO cdr.cdr VALUES( v_cdr.*); + RETURN 0; +END; +$$; + +CREATE or replace FUNCTION switch.writecdr(i_is_master boolean, i_node_id integer, i_pop_id integer, i_routing_attempt integer, i_is_last_cdr boolean, i_lega_transport_protocol_id smallint, i_lega_local_ip character varying, i_lega_local_port integer, i_lega_remote_ip character varying, i_lega_remote_port integer, i_legb_transport_protocol_id smallint, i_legb_local_ip character varying, i_legb_local_port integer, i_legb_remote_ip character varying, i_legb_remote_port integer, i_legb_ruri character varying, i_legb_outbound_proxy character varying, i_time_data json, i_early_media_present boolean, i_legb_disconnect_code integer, i_legb_disconnect_reason character varying, i_disconnect_initiator integer, i_internal_disconnect_code integer, i_internal_disconnect_reason character varying, i_lega_disconnect_code integer, i_lega_disconnect_reason character varying, i_internal_disconnect_code_id smallint, i_orig_call_id character varying, i_term_call_id character varying, i_local_tag character varying, i_legb_local_tag character varying, i_msg_logger_path character varying, i_dump_level_id smallint, i_audio_recorded boolean, i_rtp_stats_data json, i_rtp_statistics json, i_global_tag character varying, i_resources character varying, i_active_resources json, i_failed_resource_type_id smallint, i_failed_resource_id bigint, i_dtmf_events json, i_versions json, i_is_redirected boolean, i_dynamic json, i_lega_headers json, i_legb_headers json, i_lega_identity json) RETURNS integer + LANGUAGE plpgsql SECURITY DEFINER COST 10 + AS $$ +DECLARE + v_cdr cdr.cdr%rowtype; + + v_time_data switch.time_data_ty; + v_version_data switch.versions_ty; + v_dynamic switch.dynamic_cdr_data_ty; + + v_nozerolen boolean; + v_config sys.config%rowtype; + + v_rtp_rx_stream_data rtp_statistics.rx_streams%rowtype; + v_rtp_tx_stream_data rtp_statistics.tx_streams%rowtype; + + v_lega_headers switch.lega_headers_ty; + v_legb_headers switch.legb_headers_ty; + v_lega_reason switch.reason_ty; + v_legb_reason switch.reason_ty; +BEGIN + -- raise warning 'type: % id: %', i_failed_resource_type_id, i_failed_resource_id; + -- RAISE warning 'DTMF: %', i_dtmf_events; + + v_time_data:=json_populate_record(null::switch.time_data_ty, i_time_data); + v_version_data:=json_populate_record(null::switch.versions_ty, i_versions); + v_dynamic:=json_populate_record(null::switch.dynamic_cdr_data_ty, i_dynamic); + + v_lega_headers:=json_populate_record(null::switch.lega_headers_ty, i_lega_headers); + v_legb_headers:=json_populate_record(null::switch.legb_headers_ty, i_legb_headers); + + v_cdr.p_charge_info_in = v_lega_headers.p_charge_info; + + v_lega_reason = v_lega_headers.reason; + v_cdr.lega_q850_cause = v_lega_reason.q850_cause; + v_cdr.lega_q850_text = v_lega_reason.q850_text; + v_cdr.lega_q850_params = v_lega_reason.q850_params; + + v_legb_reason = v_legb_headers.reason; + v_cdr.legb_q850_cause = v_legb_reason.q850_cause; + v_cdr.legb_q850_text = v_legb_reason.q850_text; + v_cdr.legb_q850_params = v_legb_reason.q850_params; + + v_cdr.lega_identity = i_lega_identity; + v_cdr.lega_ss_status_id = v_dynamic.lega_ss_status_id; + v_cdr.legb_ss_status_id = v_dynamic.legb_ss_status_id; + + v_cdr.metadata = v_dynamic.metadata::jsonb; + + v_cdr.core_version=v_version_data.core; + v_cdr.yeti_version=v_version_data.yeti; + v_cdr.lega_user_agent=v_version_data.aleg; + v_cdr.legb_user_agent=v_version_data.bleg; + + v_cdr.pop_id=i_pop_id; + v_cdr.node_id=i_node_id; + + v_cdr.src_name_in:=v_dynamic.src_name_in; + v_cdr.src_name_out:=v_dynamic.src_name_out; + + v_cdr.diversion_in:=v_dynamic.diversion_in; + v_cdr.diversion_out:=v_dynamic.diversion_out; + + v_cdr.customer_id:=v_dynamic.customer_id; + v_cdr.customer_external_id:=v_dynamic.customer_external_id; + + v_cdr.customer_acc_id:=v_dynamic.customer_acc_id; + v_cdr.customer_account_check_balance=v_dynamic.customer_acc_check_balance; + v_cdr.customer_acc_external_id=v_dynamic.customer_acc_external_id; + v_cdr.customer_acc_vat:=v_dynamic.customer_acc_vat; + + v_cdr.customer_auth_id:=v_dynamic.customer_auth_id; + v_cdr.customer_auth_external_id:=v_dynamic.customer_auth_external_id; + v_cdr.customer_auth_external_type:=v_dynamic.customer_auth_external_type; + v_cdr.customer_auth_name:=v_dynamic.customer_auth_name; + + v_cdr.vendor_id:=v_dynamic.vendor_id; + v_cdr.vendor_external_id:=v_dynamic.vendor_external_id; + v_cdr.vendor_acc_id:=v_dynamic.vendor_acc_id; + v_cdr.vendor_acc_external_id:=v_dynamic.vendor_acc_external_id; + + v_cdr.destination_id:=v_dynamic.destination_id; + v_cdr.destination_prefix:=v_dynamic.destination_prefix; + v_cdr.dialpeer_id:=v_dynamic.dialpeer_id; + v_cdr.dialpeer_prefix:=v_dynamic.dialpeer_prefix; + + v_cdr.orig_gw_id:=v_dynamic.orig_gw_id; + v_cdr.orig_gw_external_id:=v_dynamic.orig_gw_external_id; + v_cdr.term_gw_id:=v_dynamic.term_gw_id; + v_cdr.term_gw_external_id:=v_dynamic.term_gw_external_id; + + v_cdr.routing_group_id:=v_dynamic.routing_group_id; + v_cdr.rateplan_id:=v_dynamic.rateplan_id; + + v_cdr.routing_attempt=i_routing_attempt; + v_cdr.is_last_cdr=i_is_last_cdr; + + v_cdr.destination_initial_rate:=v_dynamic.destination_initial_rate::numeric; + v_cdr.destination_next_rate:=v_dynamic.destination_next_rate::numeric; + v_cdr.destination_initial_interval:=v_dynamic.destination_initial_interval; + v_cdr.destination_next_interval:=v_dynamic.destination_next_interval; + v_cdr.destination_fee:=v_dynamic.destination_fee; + v_cdr.destination_rate_policy_id:=v_dynamic.destination_rate_policy_id; + v_cdr.destination_reverse_billing=v_dynamic.destination_reverse_billing; + + v_cdr.dialpeer_initial_rate:=v_dynamic.dialpeer_initial_rate::numeric; + v_cdr.dialpeer_next_rate:=v_dynamic.dialpeer_next_rate::numeric; + v_cdr.dialpeer_initial_interval:=v_dynamic.dialpeer_initial_interval; + v_cdr.dialpeer_next_interval:=v_dynamic.dialpeer_next_interval; + v_cdr.dialpeer_fee:=v_dynamic.dialpeer_fee; + v_cdr.dialpeer_reverse_billing=v_dynamic.dialpeer_reverse_billing; + + /* sockets addresses */ + v_cdr.sign_orig_transport_protocol_id=i_lega_transport_protocol_id; + v_cdr.sign_orig_ip=i_lega_remote_ip; + v_cdr.sign_orig_port=NULLIF(i_lega_remote_port,0); + v_cdr.sign_orig_local_ip=i_lega_local_ip; + v_cdr.sign_orig_local_port=NULLIF(i_lega_local_port,0); + + v_cdr.sign_term_transport_protocol_id=i_legb_transport_protocol_id; + v_cdr.sign_term_ip=i_legb_remote_ip; + v_cdr.sign_term_port=NULLIF(i_legb_remote_port,0); + v_cdr.sign_term_local_ip=i_legb_local_ip; + v_cdr.sign_term_local_port=NULLIF(i_legb_local_port,0); + + v_cdr.local_tag=i_local_tag; + v_cdr.legb_local_tag=i_legb_local_tag; + v_cdr.legb_ruri=i_legb_ruri; + v_cdr.legb_outbound_proxy=i_legb_outbound_proxy; + + v_cdr.is_redirected=i_is_redirected; + + /* Call time data */ + v_cdr.time_start:=to_timestamp(v_time_data.time_start); + + select into strict v_config * from sys.config; + + if v_time_data.time_connect is not null then + v_cdr.time_connect:=to_timestamp(v_time_data.time_connect); + v_cdr.duration:=switch.duration_round(v_config, v_time_data.time_end-v_time_data.time_connect); -- rounding + v_nozerolen:=true; + v_cdr.success=true; + else + v_cdr.time_connect:=NULL; + v_cdr.duration:=0; + v_nozerolen:=false; + v_cdr.success=false; + end if; + v_cdr.routing_delay=(v_time_data.leg_b_time-v_time_data.time_start)::real; + v_cdr.pdd=(coalesce(v_time_data.time_18x,v_time_data.time_connect)-v_time_data.time_start)::real; + v_cdr.rtt=(coalesce(v_time_data.time_1xx,v_time_data.time_18x,v_time_data.time_connect)-v_time_data.leg_b_time)::real; + v_cdr.early_media_present=i_early_media_present; + + v_cdr.time_end:=to_timestamp(v_time_data.time_end); + + -- DC processing + v_cdr.legb_disconnect_code=i_legb_disconnect_code; + v_cdr.legb_disconnect_reason=i_legb_disconnect_reason; + v_cdr.disconnect_initiator_id=i_disconnect_initiator; + v_cdr.internal_disconnect_code_id=i_internal_disconnect_code_id; + v_cdr.internal_disconnect_code=i_internal_disconnect_code; + v_cdr.internal_disconnect_reason=i_internal_disconnect_reason; + v_cdr.lega_disconnect_code=i_lega_disconnect_code; + v_cdr.lega_disconnect_reason=i_lega_disconnect_reason; + + v_cdr.src_prefix_in:=v_dynamic.src_prefix_in; + v_cdr.src_prefix_out:=v_dynamic.src_prefix_out; + v_cdr.dst_prefix_in:=v_dynamic.dst_prefix_in; + v_cdr.dst_prefix_out:=v_dynamic.dst_prefix_out; + + v_cdr.orig_call_id=i_orig_call_id; + v_cdr.term_call_id=i_term_call_id; + + /* removed */ + --v_cdr.dump_file:=i_msg_logger_path; + + v_cdr.dump_level_id:=i_dump_level_id; + v_cdr.audio_recorded:=i_audio_recorded; + + v_cdr.auth_orig_transport_protocol_id=v_dynamic.auth_orig_protocol_id; + v_cdr.auth_orig_ip:=v_dynamic.auth_orig_ip; + v_cdr.auth_orig_ip:=v_dynamic.auth_orig_ip; + v_cdr.auth_orig_port:=v_dynamic.auth_orig_port; + + perform switch.write_rtp_statistics( + i_rtp_statistics, + i_pop_id, + i_node_id, + v_dynamic.orig_gw_id, + v_dynamic.orig_gw_external_id, + v_dynamic.term_gw_id, + v_dynamic.term_gw_external_id, + i_local_tag, + i_legb_local_tag + ); + + v_cdr.global_tag=i_global_tag; + + v_cdr.src_country_id=v_dynamic.src_country_id; + v_cdr.src_network_id=v_dynamic.src_network_id; + v_cdr.dst_country_id=v_dynamic.dst_country_id; + v_cdr.dst_network_id=v_dynamic.dst_network_id; + v_cdr.dst_prefix_routing=v_dynamic.dst_prefix_routing; + v_cdr.src_prefix_routing=v_dynamic.src_prefix_routing; + v_cdr.routing_plan_id=v_dynamic.routing_plan_id; + v_cdr.lrn=v_dynamic.lrn; + v_cdr.lnp_database_id=v_dynamic.lnp_database_id; + + v_cdr.ruri_domain=v_dynamic.ruri_domain; + v_cdr.to_domain=v_dynamic.to_domain; + v_cdr.from_domain=v_dynamic.from_domain; + + v_cdr.src_area_id=v_dynamic.src_area_id; + v_cdr.dst_area_id=v_dynamic.dst_area_id; + v_cdr.routing_tag_ids=v_dynamic.routing_tag_ids; + + + v_cdr.id:=nextval('cdr.cdr_id_seq'::regclass); + v_cdr.uuid:=public.uuid_generate_v1(); + + v_cdr.pai_in=v_dynamic.pai_in; + v_cdr.ppi_in=v_dynamic.ppi_in; + v_cdr.privacy_in=v_dynamic.privacy_in; + v_cdr.rpid_in=v_dynamic.rpid_in; + v_cdr.rpid_privacy_in=v_dynamic.rpid_privacy_in; + v_cdr.pai_out=v_dynamic.pai_out; + v_cdr.ppi_out=v_dynamic.ppi_out; + v_cdr.privacy_out=v_dynamic.privacy_out; + v_cdr.rpid_out=v_dynamic.rpid_out; + v_cdr.rpid_privacy_out=v_dynamic.rpid_privacy_out; + + v_cdr.failed_resource_type_id = i_failed_resource_type_id; + v_cdr.failed_resource_id = i_failed_resource_id; + + v_cdr:=billing.bill_cdr(v_cdr); + + if not v_config.disable_realtime_statistics then + perform stats.update_rt_stats(v_cdr); + end if; + + v_cdr.customer_price = switch.customer_price_round(v_config, v_cdr.customer_price); + v_cdr.customer_price_no_vat = switch.customer_price_round(v_config, v_cdr.customer_price_no_vat); + v_cdr.vendor_price = switch.vendor_price_round(v_config, v_cdr.vendor_price); + + -- generate event to billing engine + perform event.billing_insert_event('cdr_full',v_cdr); + perform event.streaming_insert_event(v_cdr); + INSERT INTO cdr.cdr VALUES( v_cdr.*); + RETURN 0; +END; +$$; + +CREATE or replace FUNCTION billing.bill_cdr(i_cdr cdr.cdr) RETURNS cdr.cdr + LANGUAGE plpgsql COST 10 + AS $$ +DECLARE + _v billing.interval_billing_data%rowtype; +BEGIN + if i_cdr.duration>0 and i_cdr.success then -- run billing. + _v=billing.interval_billing( + i_cdr.duration, + i_cdr.destination_fee, + i_cdr.destination_initial_rate, + i_cdr.destination_next_rate, + i_cdr.destination_initial_interval, + i_cdr.destination_next_interval, + i_cdr.customer_acc_vat); + i_cdr.customer_price=_v.amount; + i_cdr.customer_price_no_vat=_v.amount_no_vat; + i_cdr.customer_duration=_v.duration; + + _v=billing.interval_billing( + i_cdr.duration, + i_cdr.dialpeer_fee, + i_cdr.dialpeer_initial_rate, + i_cdr.dialpeer_next_rate, + i_cdr.dialpeer_initial_interval, + i_cdr.dialpeer_next_interval, + 0); + i_cdr.vendor_price=_v.amount; + i_cdr.vendor_duration=_v.duration; + i_cdr.profit=i_cdr.customer_price-i_cdr.vendor_price; + else + i_cdr.customer_price=0; + i_cdr.customer_price_no_vat=0; + i_cdr.vendor_price=0; + i_cdr.profit=0; + end if; + RETURN i_cdr; +END; +$$; + + + alter type switch.dynamic_cdr_data_ty drop attribute package_counter_id; + alter table cdr.cdr drop package_counter_id; + +} + end +end diff --git a/db/cdr_structure.sql b/db/cdr_structure.sql index 30b77bec1..28606b080 100644 --- a/db/cdr_structure.sql +++ b/db/cdr_structure.sql @@ -285,7 +285,8 @@ CREATE TYPE switch.dynamic_cdr_data_ty AS ( lega_ss_status_id smallint, legb_ss_status_id smallint, metadata character varying, - customer_auth_external_type character varying + customer_auth_external_type character varying, + package_counter_id bigint ); @@ -515,7 +516,8 @@ CREATE TABLE cdr.cdr ( lega_q850_params character varying, legb_q850_cause smallint, legb_q850_params character varying, - internal_disconnect_code_id smallint + internal_disconnect_code_id smallint, + package_counter_id bigint ) PARTITION BY RANGE (time_start); @@ -531,7 +533,21 @@ DECLARE _v billing.interval_billing_data%rowtype; BEGIN if i_cdr.duration>0 and i_cdr.success then -- run billing. - _v=billing.interval_billing( + if i_cdr.package_counter_id is not null then + -- running billing with fake rates to calculate duration + _v=billing.interval_billing( + i_cdr.duration, + '1.0'::numeric, + '1.0'::numeric, + '1.0'::numeric, + i_cdr.destination_initial_interval, + i_cdr.destination_next_interval, + 0::integer); + i_cdr.customer_price=0; + i_cdr.customer_price_no_vat=0; + i_cdr.customer_duration=_v.duration; + else + _v=billing.interval_billing( i_cdr.duration, i_cdr.destination_fee, i_cdr.destination_initial_rate, @@ -542,6 +558,7 @@ BEGIN i_cdr.customer_price=_v.amount; i_cdr.customer_price_no_vat=_v.amount_no_vat; i_cdr.customer_duration=_v.duration; + end if; _v=billing.interval_billing( i_cdr.duration, @@ -1185,6 +1202,7 @@ BEGIN v_cdr.routing_attempt=i_routing_attempt; v_cdr.is_last_cdr=i_is_last_cdr; + v_cdr.package_counter_id = v_dynamic.package_counter_id; v_cdr.destination_initial_rate:=v_dynamic.destination_initial_rate::numeric; v_cdr.destination_next_rate:=v_dynamic.destination_next_rate::numeric; v_cdr.destination_initial_interval:=v_dynamic.destination_initial_interval; @@ -1425,6 +1443,7 @@ BEGIN v_cdr.vendor_acc_id:=v_dynamic.vendor_acc_id; v_cdr.vendor_acc_external_id:=v_dynamic.vendor_acc_external_id; + v_cdr.package_counter_id = v_dynamic.package_counter_id; v_cdr.destination_id:=v_dynamic.destination_id; v_cdr.destination_prefix:=v_dynamic.destination_prefix; v_cdr.dialpeer_id:=v_dynamic.dialpeer_id; @@ -4381,6 +4400,7 @@ INSERT INTO "public"."schema_migrations" (version) VALUES ('20240122201619'), ('20240405165010'), ('20240411092931'), -('20240609092136'); +('20240609092136'), +('20240617084103'); diff --git a/db/custom_seeds/full_cdrs.rb b/db/custom_seeds/full_cdrs.rb index e4e4e6dcf..608d7f3a3 100644 --- a/db/custom_seeds/full_cdrs.rb +++ b/db/custom_seeds/full_cdrs.rb @@ -9,9 +9,10 @@ rate_plan = Routing::Rateplan.find_or_create_by!(name: 'seed_routing_plan') customer = Contractor.find_or_create_by!(name: 'seed_customer', enabled: true, vendor: false, customer: true) customer_acc = Account.find_or_create_by!(name: 'seed_customer_acc', contractor: customer) -customer_gw = Gateway.find_or_create_by!(name: 'seed_customer_gw', contractor: customer, allow_origination: true, allow_termination: false, enabled: true, incoming_auth_password: 'pw', incoming_auth_username: 'us') +customer_gw = Gateway.find_or_create_by!(name: 'seed_customer_gw2', contractor: customer, allow_origination: true, allow_termination: false, enabled: true, incoming_auth_password: 'pw', incoming_auth_username: 'us') customer_auth = CustomersAuth.find_or_create_by!(name: 'seed_customer_acc', customer: customer, account: customer_acc, gateway: customer_gw, routing_plan: routing_plan, rateplan: rate_plan, require_incoming_auth: true) pop = Pop.find_or_create_by!(id: 100_500, name: 'seed-UA') +package_counter = Billing::PackageCounter.find_or_create_by!(account_id: customer_acc.id, exclude: false, duration: 1200) 100.times do Routing::Area.find_or_create_by!(name: "Area #{rand(200)}") @@ -53,6 +54,7 @@ routing_attempt: 1, is_last_cdr: true, success: success, + package_counter_id: package_counter.id, destination_prefix: 380, destination_initial_rate: rand(0..5), destination_next_rate: rand(0..5), diff --git a/db/migrate/20240615214442_package_billing.rb b/db/migrate/20240615214442_package_billing.rb new file mode 100644 index 000000000..973019dc4 --- /dev/null +++ b/db/migrate/20240615214442_package_billing.rb @@ -0,0 +1,4751 @@ +class PackageBilling < ActiveRecord::Migration[7.0] + + def up + execute %q{ + create table billing.package_counters( + id bigserial primary key, + account_id integer not null references billing.accounts(id), + service_id bigint, + prefix varchar not null, + exclude boolean not null default false, + duration integer not null default 0 + ); + create index package_counters_account_id_idx on billing.package_counters using btree(account_id); + create index package_counters_service_id_idx on billing.package_counters using btree(service_id); + create index package_counters_prefix_idx on billing.package_counters using gist (((prefix)::public.prefix_range)); + + alter table class4.destinations add allow_package_billing boolean not null default false; + + alter type switch21.callprofile_ty add attribute package_counter_id bigint; + INSERT INTO switch21.switch_interface_out (id, name, type, custom, rank, for_radius) VALUES (1047, 'package_counter_id', 'bigint', true, 1986, false); + +CREATE or replace FUNCTION switch21.route( +i_node_id integer, +i_pop_id integer, +i_protocol_id smallint, +i_remote_ip inet, +i_remote_port integer, +i_local_ip inet, +i_local_port integer, +i_from_dsp character varying, +i_from_name character varying, +i_from_domain character varying, +i_from_port integer, +i_to_name character varying, +i_to_domain character varying, +i_to_port integer, +i_contact_name character varying, +i_contact_domain character varying, +i_contact_port integer, +i_uri_name character varying, +i_uri_domain character varying, +i_auth_id integer, +i_identity_data json, +i_interface character varying, +i_x_yeti_auth character varying, +i_diversion character varying, +i_x_orig_ip inet, +i_x_orig_port integer, +i_x_orig_protocol_id smallint, +i_pai character varying, +i_ppi character varying, +i_privacy character varying, +i_rpid character varying, +i_rpid_privacy character varying +) RETURNS SETOF switch21.callprofile_ty + LANGUAGE plpgsql SECURITY DEFINER ROWS 10 + AS $_$ + DECLARE + v_ret switch21.callprofile_ty; + i integer; + v_ip inet; + v_remote_ip inet; + v_remote_port INTEGER; + v_transport_protocol_id smallint; + v_customer_auth_normalized class4.customers_auth_normalized; + v_destination class4.destinations%rowtype; + v_dialpeer record; + v_rateplan class4.rateplans%rowtype; + v_dst_gw class4.gateways%rowtype; + v_orig_gw class4.gateways%rowtype; + v_rp class4.routing_plans%rowtype; + v_customer_allowtime real; + v_vendor_allowtime real; + v_sorting_id integer; + v_customer_acc integer; + v_route_found boolean:=false; + v_c_acc billing.accounts%rowtype; + v_v_acc billing.accounts%rowtype; + v_network sys.network_prefixes%rowtype; + v_src_network sys.network_prefixes%rowtype; + routedata record; + /*dbg{*/ + v_start timestamp; + v_end timestamp; + /*}dbg*/ + v_rate NUMERIC; + v_now timestamp; + v_x_yeti_auth varchar; + -- v_uri_domain varchar; + v_rate_limit float:='Infinity'::float; + v_destination_rate_limit float:='Infinity'::float; + v_test_vendor_id integer; + v_random float; + v_max_call_length integer; + v_routing_key varchar; + v_lnp_key varchar; + v_lnp_rule class4.routing_plan_lnp_rules%rowtype; + v_numberlist record; + v_numberlist_item record; + v_call_tags smallint[]:='{}'::smallint[]; + v_area_direction class4.routing_tag_detection_rules%rowtype; + v_numberlist_size integer; + v_lua_context switch21.lua_call_context; + v_identity_data switch21.identity_data_ty[]; + v_pai varchar[]; + v_ppi varchar; + v_privacy varchar[]; + v_diversion varchar[] not null default ARRAY[]::varchar[]; + v_cnam_req_json json; + v_cnam_resp_json json; + v_cnam_lua_resp switch21.cnam_lua_resp; + v_cnam_database class4.cnam_databases%rowtype; + v_rewrite switch21.defered_rewrite; + v_defered_src_rewrites switch21.defered_rewrite[] not null default ARRAY[]::switch21.defered_rewrite[]; + v_defered_dst_rewrites switch21.defered_rewrite[] not null default ARRAY[]::switch21.defered_rewrite[]; + v_rate_groups integer[]; + v_routing_groups integer[]; + v_package billing.package_counters%rowtype; + BEGIN + /*dbg{*/ + v_start:=now(); + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> Execution start',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + + perform id from sys.load_balancers where signalling_ip=host(i_remote_ip)::varchar; + IF FOUND and i_x_orig_ip IS not NULL AND i_x_orig_port IS not NULL THEN + v_remote_ip:=i_x_orig_ip; + v_remote_port:=i_x_orig_port; + v_transport_protocol_id=i_x_orig_protocol_id; + /*dbg{*/RAISE NOTICE '% ms -> Got originator address "%:%, proto: %" from x-headers',EXTRACT(MILLISECOND from v_end-v_start), v_remote_ip,v_remote_port, v_transport_protocol_id;/*}dbg*/ + else + v_remote_ip:=i_remote_ip; + v_remote_port:=i_remote_port; + v_transport_protocol_id:=i_protocol_id; + /*dbg{*/RAISE NOTICE '% ms -> Got originator address "%:%, proto: %" from switch leg info',EXTRACT(MILLISECOND from v_end-v_start), v_remote_ip,v_remote_port, v_transport_protocol_id;/*}dbg*/ + end if; + + v_now:=now(); + v_ret:=switch21.new_profile(); + + v_ret.diversion_in:=i_diversion; + + v_ret.auth_orig_protocol_id =v_transport_protocol_id; + v_ret.auth_orig_ip = v_remote_ip; + v_ret.auth_orig_port = v_remote_port; + + v_ret.src_name_in:=i_from_dsp; + v_ret.src_name_out:=v_ret.src_name_in; + + v_ret.src_prefix_in:=i_from_name; + v_ret.src_prefix_out:=v_ret.src_prefix_in; + + v_ret.dst_prefix_in:=i_uri_name; + v_ret.dst_prefix_out:=v_ret.dst_prefix_in; + + + v_ret.ruri_domain=i_uri_domain; + v_ret.from_domain=i_from_domain; + v_ret.to_domain=i_to_domain; + + v_ret.pai_in=i_pai; + v_pai=string_to_array(COALESCE(i_pai,''),','); + v_ret.ppi_in=i_ppi; + v_ppi=i_ppi; + v_ret.privacy_in=i_privacy; + v_privacy = string_to_array(COALESCE(i_privacy,''),';'); + v_ret.rpid_in=i_rpid; + v_ret.rpid_privacy_in=i_rpid_privacy; + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> AUTH. lookup started',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_x_yeti_auth:=COALESCE(i_x_yeti_auth,''); + -- v_uri_domain:=COALESCE(i_uri_domain,''); + + if i_auth_id is null then + SELECT into v_customer_auth_normalized ca.* + from class4.customers_auth_normalized ca + JOIN public.contractors c ON c.id=ca.customer_id + WHERE ca.enabled AND + ca.ip>>=v_remote_ip AND + prefix_range(ca.dst_prefix)@>prefix_range(v_ret.dst_prefix_in) AND + prefix_range(ca.src_prefix)@>prefix_range(v_ret.src_prefix_in) AND + (ca.pop_id=i_pop_id or ca.pop_id is null) and + COALESCE(ca.x_yeti_auth,'')=v_x_yeti_auth AND + COALESCE(nullif(ca.uri_domain,'')=i_uri_domain,true) AND + COALESCE(nullif(ca.to_domain,'')=i_to_domain,true) AND + COALESCE(nullif(ca.from_domain,'')=i_from_domain,true) AND + (ca.interface is null or ca.interface = i_interface ) AND + (ca.transport_protocol_id is null or ca.transport_protocol_id=v_transport_protocol_id) AND + length(v_ret.dst_prefix_in) between ca.dst_number_min_length and ca.dst_number_max_length and + length(v_ret.src_prefix_in) between ca.src_number_min_length and ca.src_number_max_length and + c.enabled and c.customer + ORDER BY + masklen(ca.ip) DESC, + ca.transport_protocol_id is null, + length(prefix_range(ca.dst_prefix)) DESC, + length(prefix_range(ca.src_prefix)) DESC, + ca.pop_id is null, + ca.uri_domain is null, + ca.to_domain is null, + ca.from_domain is null, + ca.require_incoming_auth + LIMIT 1; + IF NOT FOUND THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> AUTH. disconnection with 110.Cant find customer or customer locked',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_ret.disconnect_code_id=110; --Cant find customer or customer locked + RETURN NEXT v_ret; + RETURN; + END IF; + if v_customer_auth_normalized.require_incoming_auth then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> AUTH. Incoming auth required. Respond 401',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_ret.aleg_auth_required=true; + RETURN NEXT v_ret; + RETURN; + end IF; + if v_customer_auth_normalized.reject_calls then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> AUTH. disconnection with 8004. Reject by customers auth',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_ret.disconnect_code_id=8004; -- call rejected by authorization + + v_ret.customer_auth_id:=v_customer_auth_normalized.customers_auth_id; + v_ret.customer_auth_external_id:=v_customer_auth_normalized.external_id; + v_ret.customer_auth_external_type:=v_customer_auth_normalized.external_type; + + v_ret.customer_id:=v_customer_auth_normalized.customer_id; + select into strict v_ret.customer_external_id external_id from public.contractors where id=v_ret.customer_id; + + v_ret.rateplan_id:=v_customer_auth_normalized.rateplan_id; + v_ret.routing_plan_id:=v_customer_auth_normalized.routing_plan_id; + + v_ret.customer_acc_id:=v_customer_auth_normalized.account_id; + SELECT INTO STRICT v_ret.customer_acc_external_id external_id FROM billing.accounts WHERE id=v_customer_auth_normalized.account_id; + + RETURN NEXT v_ret; + RETURN; + end if; + else + SELECT into v_customer_auth_normalized ca.* + from class4.customers_auth_normalized ca + JOIN public.contractors c ON c.id=ca.customer_id + WHERE ca.enabled AND + ca.ip>>=v_remote_ip AND + prefix_range(ca.dst_prefix)@>prefix_range(v_ret.dst_prefix_in) AND + prefix_range(ca.src_prefix)@>prefix_range(v_ret.src_prefix_in) AND + (ca.pop_id=i_pop_id or ca.pop_id is null) and + COALESCE(ca.x_yeti_auth,'')=v_x_yeti_auth AND + COALESCE(nullif(ca.uri_domain,'')=i_uri_domain,true) AND + COALESCE(nullif(ca.to_domain,'')=i_to_domain,true) AND + COALESCE(nullif(ca.from_domain,'')=i_from_domain,true) AND + (ca.interface is null or ca.interface = i_interface ) AND + (ca.transport_protocol_id is null or ca.transport_protocol_id=v_transport_protocol_id) AND + length(v_ret.dst_prefix_in) between ca.dst_number_min_length and ca.dst_number_max_length and + length(v_ret.src_prefix_in) between ca.src_number_min_length and ca.src_number_max_length and + c.enabled and c.customer and + ca.require_incoming_auth and gateway_id = i_auth_id + ORDER BY + masklen(ca.ip) DESC, + ca.transport_protocol_id is null, + length(prefix_range(ca.dst_prefix)) DESC, + length(prefix_range(ca.src_prefix)) DESC, + ca.pop_id is null, + ca.uri_domain is null, + ca.to_domain is null, + ca.from_domain is null + LIMIT 1; + IF NOT FOUND THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> AUTH. disconnection with 110.Cant find customer or customer locked',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_ret.disconnect_code_id=110; --Cant find customer or customer locked + RETURN NEXT v_ret; + RETURN; + END IF; + if v_customer_auth_normalized.reject_calls then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> AUTH. disconnection with 8004. Reject by customers auth',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_ret.disconnect_code_id=8004; -- call rejected by authorization + + v_ret.customer_auth_id:=v_customer_auth_normalized.customers_auth_id; + v_ret.customer_auth_external_id:=v_customer_auth_normalized.external_id; + v_ret.customer_auth_external_type:=v_customer_auth_normalized.external_type; + + v_ret.customer_id:=v_customer_auth_normalized.customer_id; + select into strict v_ret.customer_external_id external_id from public.contractors where id=v_ret.customer_id; + + v_ret.rateplan_id:=v_customer_auth_normalized.rateplan_id; + v_ret.routing_plan_id:=v_customer_auth_normalized.routing_plan_id; + + v_ret.customer_acc_id:=v_customer_auth_normalized.account_id; + SELECT INTO STRICT v_ret.customer_acc_external_id external_id FROM billing.accounts WHERE id=v_customer_auth_normalized.account_id; + + RETURN NEXT v_ret; + RETURN; + end if; + end IF; + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> AUTH. found: %',EXTRACT(MILLISECOND from v_end-v_start),row_to_json(v_customer_auth_normalized, true); + /*}dbg*/ + + -- redefine call SRC/DST numbers + + IF v_customer_auth_normalized.src_name_field_id=1 THEN /* default - from uri display name */ + v_ret.src_name_in:=i_from_dsp; + END IF; + v_ret.src_name_out:=v_ret.src_name_in; + + IF v_customer_auth_normalized.src_number_field_id=1 THEN /* default - from uri userpart */ + v_ret.src_prefix_in:=i_from_name; + ELSIF v_customer_auth_normalized.src_number_field_id=2 THEN /* From uri Display name */ + v_ret.src_prefix_in:=i_from_dsp; + END IF; + v_ret.src_prefix_out:=v_ret.src_prefix_in; + + IF v_customer_auth_normalized.dst_number_field_id=1 THEN /* default - RURI userpart*/ + v_ret.dst_prefix_in:=i_uri_name; + ELSIF v_customer_auth_normalized.dst_number_field_id=2 THEN /* TO URI userpart */ + v_ret.dst_prefix_in:=i_to_name; + ELSIF v_customer_auth_normalized.dst_number_field_id=3 THEN /* Top-Most Diversion header userpart */ + v_ret.dst_prefix_in:=COALESCE(i_diversion,''); + END IF; + v_ret.dst_prefix_out:=v_ret.dst_prefix_in; + + IF v_customer_auth_normalized.diversion_policy_id = 2 THEN /* accept diversion */ + v_diversion = string_to_array(COALESCE(i_diversion,''),','); + v_diversion = yeti_ext.regexp_replace_rand( + v_diversion, + v_customer_auth_normalized.diversion_rewrite_rule, + v_customer_auth_normalized.diversion_rewrite_result + ); + END IF; + + select into v_identity_data array_agg(d) from json_populate_recordset(null::switch21.identity_data_ty, i_identity_data) d; + IF v_customer_auth_normalized.rewrite_ss_status_id IS NOT NULL THEN + v_ret.ss_attest_id = v_customer_auth_normalized.rewrite_ss_status_id; + END IF; + + -- feel customer data ;-) + v_ret.dump_level_id:=v_customer_auth_normalized.dump_level_id; + v_ret.customer_auth_id:=v_customer_auth_normalized.customers_auth_id; + v_ret.customer_auth_external_id:=v_customer_auth_normalized.external_id; + v_ret.customer_auth_external_type:=v_customer_auth_normalized.external_type; + + v_ret.customer_id:=v_customer_auth_normalized.customer_id; + select into strict v_ret.customer_external_id external_id from public.contractors where id=v_customer_auth_normalized.customer_id; + + v_ret.rateplan_id:=v_customer_auth_normalized.rateplan_id; + v_ret.routing_plan_id:=v_customer_auth_normalized.routing_plan_id; + v_ret.customer_acc_id:=v_customer_auth_normalized.account_id; + + v_ret.orig_gw_id:=v_customer_auth_normalized.gateway_id; + SELECT into v_orig_gw * from class4.gateways WHERE id=v_customer_auth_normalized.gateway_id; + -- we have to set disconnect policy to allow rewrite internal reject when call rejected before gw processing + v_ret.aleg_policy_id = v_orig_gw.orig_disconnect_policy_id; + + if not v_orig_gw.enabled then + v_ret.disconnect_code_id=8005; -- Origination gateway is disabled + RETURN NEXT v_ret; + RETURN; + end if; + + CASE v_customer_auth_normalized.privacy_mode_id + WHEN 1 THEN + -- allow all + WHEN 2 THEN + IF cardinality(array_remove(v_privacy,'none')) > 0 THEN + v_ret.disconnect_code_id = 8013; + RETURN NEXT v_ret; + RETURN; + END IF; + WHEN 3 THEN + IF 'critical' = ANY(v_privacy) THEN + v_ret.disconnect_code_id = 8014; + RETURN NEXT v_ret; + RETURN; + END IF; + WHEN 4 THEN + IF lower(v_ret.src_prefix_in)='anonymous' AND COALESCE(cardinality(v_pai),0) = 0 AND ( v_ppi is null or v_ppi='') THEN + v_ret.disconnect_code_id = 8015; + RETURN NEXT v_ret; + RETURN; + END IF; + END CASE; + + v_ret.radius_auth_profile_id=v_customer_auth_normalized.radius_auth_profile_id; + v_ret.aleg_radius_acc_profile_id=v_customer_auth_normalized.radius_accounting_profile_id; + v_ret.record_audio=v_customer_auth_normalized.enable_audio_recording; + + v_ret.customer_acc_check_balance=v_customer_auth_normalized.check_account_balance; + + SELECT INTO STRICT v_c_acc * FROM billing.accounts WHERE id=v_customer_auth_normalized.account_id; + v_ret.customer_acc_external_id=v_c_acc.external_id; + v_ret.customer_acc_vat=v_c_acc.vat; + v_destination_rate_limit=coalesce(v_c_acc.destination_rate_limit::float,'Infinity'::float); + + select into v_max_call_length max_call_duration from sys.guiconfig limit 1; + + if NOT v_customer_auth_normalized.check_account_balance then + v_ret.time_limit = LEAST(v_max_call_length, v_c_acc.max_call_duration); + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> AUTH. No customer acc balance checking. customer time limit set to max value: % ',EXTRACT(MILLISECOND from v_end-v_start), v_ret.time_limit; + /*}dbg*/ + elsif v_customer_auth_normalized.check_account_balance AND v_c_acc.balance<=v_c_acc.min_balance then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> AUTH. Customer acc balance checking. Call blocked before routing',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_ret.disconnect_code_id=8000; --No enough customer balance + RETURN NEXT v_ret; + RETURN; + end if; + + v_ret.customer_acc_external_id=v_c_acc.external_id; + v_ret.customer_acc_vat=v_c_acc.vat; + + v_ret.lega_res=''; + if v_customer_auth_normalized.capacity is not null then + v_ret.lega_res='3:'||v_customer_auth_normalized.customers_auth_id||':'||v_customer_auth_normalized.capacity::varchar||':1;'; + end if; + + if v_c_acc.origination_capacity is not null then + v_ret.lega_res:=v_ret.lega_res||'1:'||v_c_acc.id::varchar||':'||v_c_acc.origination_capacity::varchar||':1;'; + end if; + + if v_c_acc.total_capacity is not null then + v_ret.lega_res:=v_ret.lega_res||'7:'||v_c_acc.id::varchar||':'||v_c_acc.total_capacity::varchar||':1;'; + end if; + + if v_orig_gw.origination_capacity is not null then + v_ret.lega_res:=v_ret.lega_res||'4:'||v_orig_gw.id::varchar||':'||v_orig_gw.origination_capacity::varchar||':1;'; + end if; + + if v_customer_auth_normalized.cps_limit is not null then + if not yeti_ext.tbf_rate_check(1::integer,v_customer_auth_normalized.customers_auth_id::bigint, v_customer_auth_normalized.cps_limit::real) then + v_ret.disconnect_code_id=8012; -- CPS limit on customer auth + RETURN NEXT v_ret; + RETURN; + end if; + end if; + + -- Tag processing CA + v_call_tags=yeti_ext.tag_action(v_customer_auth_normalized.tag_action_id, v_call_tags, v_customer_auth_normalized.tag_action_value); + + /* + number rewriting _Before_ routing + */ + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> AUTH. Before rewrite src_prefix: % , dst_prefix: %',EXTRACT(MILLISECOND from v_end-v_start),v_ret.src_prefix_out,v_ret.dst_prefix_out; + /*}dbg*/ + v_ret.dst_prefix_out=yeti_ext.regexp_replace_rand(v_ret.dst_prefix_out,v_customer_auth_normalized.dst_rewrite_rule,v_customer_auth_normalized.dst_rewrite_result); + v_ret.src_prefix_out=yeti_ext.regexp_replace_rand(v_ret.src_prefix_out,v_customer_auth_normalized.src_rewrite_rule,v_customer_auth_normalized.src_rewrite_result); + v_ret.src_name_out=yeti_ext.regexp_replace_rand(v_ret.src_name_out,v_customer_auth_normalized.src_name_rewrite_rule,v_customer_auth_normalized.src_name_rewrite_result, true); + + -- if v_ret.radius_auth_profile_id is not null then + v_ret.src_number_radius:=i_from_name; + v_ret.dst_number_radius:=i_uri_name; + v_ret.src_number_radius=yeti_ext.regexp_replace_rand( + v_ret.src_number_radius, + v_customer_auth_normalized.src_number_radius_rewrite_rule, + v_customer_auth_normalized.src_number_radius_rewrite_result + ); + + v_ret.dst_number_radius=yeti_ext.regexp_replace_rand( + v_ret.dst_number_radius, + v_customer_auth_normalized.dst_number_radius_rewrite_rule, + v_customer_auth_normalized.dst_number_radius_rewrite_result + ); + v_ret.customer_auth_name=v_customer_auth_normalized."name"; + v_ret.customer_name=(select "name" from public.contractors where id=v_customer_auth_normalized.customer_id limit 1); + -- end if; +/** + if v_customer_auth_normalized.lua_script_id is not null then + v_lua_context.src_name_in = v_ret.src_name_in; + v_lua_context.src_number_in = v_ret.src_prefix_in; + v_lua_context.dst_number_in = v_ret.dst_prefix_in; + v_lua_context.src_name_out = v_ret.src_name_out; + v_lua_context.src_number_out = v_ret.src_prefix_out; + v_lua_context.dst_number_out = v_ret.dst_prefix_out; + -- v_lua_context.src_name_routing + -- v_lua_context.src_number_routing + -- v_lua_context.dst_number_routing + -- #arrays + -- v_lua_context.diversion_in + -- v_lua_context.diversion_routing + -- v_lua_context.diversion_out + select into v_lua_context switch21.lua_exec(v_customer_auth_normalized.lua_script_id, v_lua_context); + v_ret.src_name_out = v_lua_context.src_name_out; + v_ret.src_prefix_out = v_lua_context.src_number_out; + v_ret.dst_prefix_out = v_lua_context.dst_number_out; + end if; +**/ + if v_customer_auth_normalized.cnam_database_id is not null then + select into v_cnam_database * from class4.cnam_databases where id=v_customer_auth_normalized.cnam_database_id; + + select into v_cnam_req_json * from switch21.cnam_lua_build_request(v_cnam_database.request_lua, row_to_json(v_ret)::text); + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> CNAM. Lua generated request: %',EXTRACT(MILLISECOND from v_end-v_start),v_cnam_req_json; + /*}dbg*/ + + select into v_cnam_resp_json yeti_ext.lnp_resolve_cnam(v_cnam_database.id, v_cnam_req_json); + + /*dbg{*/ + v_end=clock_timestamp(); + RAISE NOTICE '% ms -> CNAM. resolver response: %',EXTRACT(MILLISECOND from v_end-v_start),v_cnam_resp_json; + /*}dbg*/ + + if json_extract_path_text(v_cnam_resp_json,'error') is not null then + /*dbg{*/ + v_end=clock_timestamp(); + RAISE NOTICE '% ms -> CNAM. error',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + if v_cnam_database.drop_call_on_error then + v_ret.disconnect_code_id=8009; -- CNAM Error + RETURN NEXT v_ret; + RETURN; + end if; + else + select into v_cnam_lua_resp * from switch21.cnam_lua_response_exec(v_cnam_database.response_lua, json_extract_path_text(v_cnam_resp_json,'response')); + + /*dbg{*/ + v_end=clock_timestamp(); + RAISE NOTICE '% ms -> CNAM. Lua parsed response: %',EXTRACT(MILLISECOND from v_end-v_start),row_to_json(v_cnam_lua_resp); + /*}dbg*/ + if v_cnam_lua_resp.metadata is not null then + v_ret.metadata = json_build_object('cnam_resp', v_cnam_lua_resp.metadata::json)::varchar; + end if; + v_ret.src_name_out = coalesce(v_cnam_lua_resp.src_name,v_ret.src_name_out); + v_ret.src_prefix_out = coalesce(v_cnam_lua_resp.src_number,v_ret.src_prefix_out); + v_ret.dst_prefix_out = coalesce(v_cnam_lua_resp.dst_number,v_ret.dst_prefix_out); + v_ret.pai_out = coalesce(v_cnam_lua_resp.pai,v_ret.pai_out); + v_ret.ppi_out = coalesce(v_cnam_lua_resp.ppi,v_ret.ppi_out); + v_call_tags = coalesce(v_cnam_lua_resp.routing_tag_ids,v_call_tags); + end if; + + end if; + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> AUTH. After rewrite src_prefix: % , dst_prefix: %',EXTRACT(MILLISECOND from v_end-v_start),v_ret.src_prefix_out,v_ret.dst_prefix_out; + /*}dbg*/ + + ----- Numberlist processing------------------------------------------------------------------------------------------------------- + if v_customer_auth_normalized.dst_numberlist_id is not null then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DST Numberlist processing. Lookup by key: %',EXTRACT(MILLISECOND from v_end-v_start), v_ret.dst_prefix_out; + /*}dbg*/ + + v_numberlist_item=switch21.match_numberlist(v_customer_auth_normalized.dst_numberlist_id, v_ret.dst_prefix_out); + select into v_numberlist * from class4.numberlists where id=v_customer_auth_normalized.dst_numberlist_id; + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DST Numberlist. key found: %',EXTRACT(MILLISECOND from v_end-v_start), row_to_json(v_numberlist_item); + /*}dbg*/ + IF v_numberlist_item.action_id is not null and v_numberlist_item.action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DST Numberlist. Drop by key action. Key: %',EXTRACT(MILLISECOND from v_end-v_start), v_numberlist_item.key; + /*}dbg*/ + v_ret.disconnect_code_id=8001; --destination blacklisted + RETURN NEXT v_ret; + RETURN; + elsif v_numberlist_item.action_id is not null and v_numberlist_item.action_id=2 then + IF v_numberlist_item.defer_src_rewrite THEN + v_defered_src_rewrites = array_append( + v_defered_src_rewrites, + (v_numberlist_item.src_rewrite_rule, v_numberlist_item.src_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.src_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.src_prefix_out, + v_numberlist_item.src_rewrite_rule, + v_numberlist_item.src_rewrite_result + ); + END IF; + IF v_numberlist_item.defer_dst_rewrite THEN + v_defered_dst_rewrites = array_append( + v_defered_dst_rewrites, + (v_numberlist_item.dst_rewrite_rule, v_numberlist_item.dst_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.dst_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.dst_prefix_out, + v_numberlist_item.dst_rewrite_rule, + v_numberlist_item.dst_rewrite_result + ); + END IF; + v_call_tags=yeti_ext.tag_action(v_numberlist_item.tag_action_id, v_call_tags, v_numberlist_item.tag_action_value); + -- pass call NOP. + elsif v_numberlist_item.action_id is null and v_numberlist.default_action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DST Numberlist. Drop by default action',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + -- drop by default + v_ret.disconnect_code_id=8001; --destination blacklisted + RETURN NEXT v_ret; + RETURN; + elsif v_numberlist_item.action_id is null and v_numberlist.default_action_id=2 then + IF v_numberlist.defer_src_rewrite THEN + v_defered_src_rewrites = array_append( + v_defered_src_rewrites, + (v_numberlist.default_src_rewrite_rule, v_numberlist.default_src_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.src_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.src_prefix_out, + v_numberlist.default_src_rewrite_rule, + v_numberlist.default_src_rewrite_result + ); + END IF; + IF v_numberlist.defer_dst_rewrite THEN + v_defered_dst_rewrites = array_append( + v_defered_dst_rewrites, + (v_numberlist.default_dst_rewrite_rule, v_numberlist.default_dst_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.dst_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.dst_prefix_out, + v_numberlist.default_dst_rewrite_rule, + v_numberlist.default_dst_rewrite_result + ); + END IF; + v_call_tags=yeti_ext.tag_action(v_numberlist.tag_action_id, v_call_tags, v_numberlist.tag_action_value); + -- pass by default + end if; + end if; + + if v_customer_auth_normalized.src_numberlist_id is not null then + + if v_customer_auth_normalized.src_numberlist_use_diversion AND v_diversion[1] is not null then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> SRC Numberlist processing. Lookup by key %, fallback to %', EXTRACT(MILLISECOND from v_end-v_start), v_ret.src_prefix_out, v_diversion[1]; + /*}dbg*/ + v_numberlist_item=switch21.match_numberlist(v_customer_auth_normalized.src_numberlist_id, v_ret.src_prefix_out, v_diversion[1]); + else + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> SRC Numberlist processing. Lookup by key %, no fallback', EXTRACT(MILLISECOND from v_end-v_start), v_ret.src_prefix_out; + /*}dbg*/ + v_numberlist_item=switch21.match_numberlist(v_customer_auth_normalized.src_numberlist_id, v_ret.src_prefix_out); + end if; + + select into v_numberlist * from class4.numberlists where id=v_customer_auth_normalized.src_numberlist_id; + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> SRC Numberlist. key found: %',EXTRACT(MILLISECOND from v_end-v_start), row_to_json(v_numberlist_item); + /*}dbg*/ + IF v_numberlist_item.action_id is not null and v_numberlist_item.action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> SRC Numberlist. Drop by key action. Key: %',EXTRACT(MILLISECOND from v_end-v_start), v_numberlist_item.key; + /*}dbg*/ + v_ret.disconnect_code_id=8002; --source blacklisted + RETURN NEXT v_ret; + RETURN; + elsif v_numberlist_item.action_id is not null and v_numberlist_item.action_id=2 then + IF v_numberlist_item.defer_src_rewrite THEN + v_defered_src_rewrites = array_append( + v_defered_src_rewrites, + (v_numberlist_item.src_rewrite_rule, v_numberlist_item.src_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.src_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.src_prefix_out, + v_numberlist_item.src_rewrite_rule, + v_numberlist_item.src_rewrite_result + ); + END IF; + IF v_numberlist_item.defer_dst_rewrite THEN + v_defered_dst_rewrites = array_append( + v_defered_dst_rewrites, + (v_numberlist_item.dst_rewrite_rule, v_numberlist_item.dst_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.dst_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.dst_prefix_out, + v_numberlist_item.dst_rewrite_rule, + v_numberlist_item.dst_rewrite_result + ); + END IF; + v_call_tags=yeti_ext.tag_action(v_numberlist_item.tag_action_id, v_call_tags, v_numberlist_item.tag_action_value); + -- pass call NOP. + elsif v_numberlist_item.action_id is null and v_numberlist.default_action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> SRC Numberlist. Drop by default action',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_ret.disconnect_code_id=8002; --source blacklisted + RETURN NEXT v_ret; + RETURN; + elsif v_numberlist_item.action_id is null and v_numberlist.default_action_id=2 then + IF v_numberlist.defer_src_rewrite THEN + v_defered_src_rewrites = array_append( + v_defered_src_rewrites, + (v_numberlist.default_src_rewrite_rule, v_numberlist.default_src_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.src_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.src_prefix_out, + v_numberlist.default_src_rewrite_rule, + v_numberlist.default_src_rewrite_result + ); + END IF; + IF v_numberlist.defer_dst_rewrite THEN + v_defered_dst_rewrites = array_append( + v_defered_dst_rewrites, + (v_numberlist.default_dst_rewrite_rule, v_numberlist.default_dst_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.dst_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.dst_prefix_out, + v_numberlist.default_dst_rewrite_rule, + v_numberlist.default_dst_rewrite_result + ); + END IF; + v_call_tags=yeti_ext.tag_action(v_numberlist.tag_action_id, v_call_tags, v_numberlist.tag_action_value); + -- pass by default + end if; + end if; + + SELECT INTO v_rp * from class4.routing_plans WHERE id=v_customer_auth_normalized.routing_plan_id; + + ---- Routing Plan Numberlist processing ---- + if v_rp.dst_numberlist_id is not null then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> RP DST Numberlist processing. Lookup by key: %',EXTRACT(MILLISECOND from v_end-v_start), v_ret.dst_prefix_out; + /*}dbg*/ + + v_numberlist_item=switch21.match_numberlist(v_rp.dst_numberlist_id, v_ret.dst_prefix_out); + select into v_numberlist * from class4.numberlists where id=v_rp.dst_numberlist_id; + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> RP DST Numberlist. key found: %',EXTRACT(MILLISECOND from v_end-v_start), row_to_json(v_numberlist_item); + /*}dbg*/ + IF v_numberlist_item.action_id is not null and v_numberlist_item.action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> RP DST Numberlist. Drop by key action. Key: %',EXTRACT(MILLISECOND from v_end-v_start), v_numberlist_item.key; + /*}dbg*/ + v_ret.disconnect_code_id=8016; --destination blacklisted by routing plan + RETURN NEXT v_ret; + RETURN; + elsif v_numberlist_item.action_id is not null and v_numberlist_item.action_id=2 then + IF v_numberlist_item.defer_src_rewrite THEN + v_defered_src_rewrites = array_append( + v_defered_src_rewrites, + (v_numberlist_item.src_rewrite_rule, v_numberlist_item.src_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.src_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.src_prefix_out, + v_numberlist_item.src_rewrite_rule, + v_numberlist_item.src_rewrite_result + ); + END IF; + IF v_numberlist_item.defer_dst_rewrite THEN + v_defered_dst_rewrites = array_append( + v_defered_dst_rewrites, + (v_numberlist_item.dst_rewrite_rule, v_numberlist_item.dst_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.dst_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.dst_prefix_out, + v_numberlist_item.dst_rewrite_rule, + v_numberlist_item.dst_rewrite_result + ); + END IF; + v_call_tags=yeti_ext.tag_action(v_numberlist_item.tag_action_id, v_call_tags, v_numberlist_item.tag_action_value); + -- pass call NOP. + elsif v_numberlist_item.action_id is null and v_numberlist.default_action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> RP DST Numberlist. Drop by default action',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + -- drop by default + v_ret.disconnect_code_id=8016; --destination blacklisted by routing plan + RETURN NEXT v_ret; + RETURN; + elsif v_numberlist_item.action_id is null and v_numberlist.default_action_id=2 then + IF v_numberlist.defer_src_rewrite THEN + v_defered_src_rewrites = array_append( + v_defered_src_rewrites, + (v_numberlist.default_src_rewrite_rule, v_numberlist.default_src_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.src_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.src_prefix_out, + v_numberlist.default_src_rewrite_rule, + v_numberlist.default_src_rewrite_result + ); + END IF; + IF v_numberlist.defer_dst_rewrite THEN + v_defered_dst_rewrites = array_append( + v_defered_dst_rewrites, + (v_numberlist.default_dst_rewrite_rule, v_numberlist.default_dst_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.dst_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.dst_prefix_out, + v_numberlist.default_dst_rewrite_rule, + v_numberlist.default_dst_rewrite_result + ); + END IF; + v_call_tags=yeti_ext.tag_action(v_numberlist.tag_action_id, v_call_tags, v_numberlist.tag_action_value); + -- pass by default + end if; + end if; + + if v_rp.src_numberlist_id is not null then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> RP SRC Numberlist processing. Lookup by key %, no fallback', EXTRACT(MILLISECOND from v_end-v_start), v_ret.src_prefix_out; + /*}dbg*/ + v_numberlist_item=switch21.match_numberlist(v_rp.src_numberlist_id, v_ret.src_prefix_out); + + select into v_numberlist * from class4.numberlists where id=v_rp.src_numberlist_id; + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> RP SRC Numberlist. key found: %',EXTRACT(MILLISECOND from v_end-v_start), row_to_json(v_numberlist_item); + /*}dbg*/ + IF v_numberlist_item.action_id is not null and v_numberlist_item.action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> RP SRC Numberlist. Drop by key action. Key: %',EXTRACT(MILLISECOND from v_end-v_start), v_numberlist_item.key; + /*}dbg*/ + v_ret.disconnect_code_id=8017; --source blacklisted by routing plan + RETURN NEXT v_ret; + RETURN; + elsif v_numberlist_item.action_id is not null and v_numberlist_item.action_id=2 then + IF v_numberlist_item.defer_src_rewrite THEN + v_defered_src_rewrites = array_append( + v_defered_src_rewrites, + (v_numberlist_item.src_rewrite_rule, v_numberlist_item.src_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.src_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.src_prefix_out, + v_numberlist_item.src_rewrite_rule, + v_numberlist_item.src_rewrite_result + ); + END IF; + IF v_numberlist_item.defer_dst_rewrite THEN + v_defered_dst_rewrites = array_append( + v_defered_dst_rewrites, + (v_numberlist_item.dst_rewrite_rule, v_numberlist_item.dst_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.dst_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.dst_prefix_out, + v_numberlist_item.dst_rewrite_rule, + v_numberlist_item.dst_rewrite_result + ); + END IF; + v_call_tags=yeti_ext.tag_action(v_numberlist_item.tag_action_id, v_call_tags, v_numberlist_item.tag_action_value); + -- pass call NOP. + elsif v_numberlist_item.action_id is null and v_numberlist.default_action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> RP SRC Numberlist. Drop by default action',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_ret.disconnect_code_id=8017; --source blacklisted by routing plan + RETURN NEXT v_ret; + RETURN; + elsif v_numberlist_item.action_id is null and v_numberlist.default_action_id=2 then + IF v_numberlist.defer_src_rewrite THEN + v_defered_src_rewrites = array_append( + v_defered_src_rewrites, + (v_numberlist.default_src_rewrite_rule, v_numberlist.default_src_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.src_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.src_prefix_out, + v_numberlist.default_src_rewrite_rule, + v_numberlist.default_src_rewrite_result + ); + END IF; + IF v_numberlist.defer_dst_rewrite THEN + v_defered_dst_rewrites = array_append( + v_defered_dst_rewrites, + (v_numberlist.default_dst_rewrite_rule, v_numberlist.default_dst_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.dst_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.dst_prefix_out, + v_numberlist.default_dst_rewrite_rule, + v_numberlist.default_dst_rewrite_result + ); + END IF; + v_call_tags=yeti_ext.tag_action(v_numberlist.tag_action_id, v_call_tags, v_numberlist.tag_action_value); + -- pass by default + end if; + end if; + ---- END of routing plan Numberlist processing + + -- setting numbers used for routing & billing + v_ret.src_prefix_routing=v_ret.src_prefix_out; + v_ret.dst_prefix_routing=v_ret.dst_prefix_out; + v_routing_key=v_ret.dst_prefix_out; + + -- Areas and Tag detection------------------------------------------- + v_ret.src_area_id:=( + select area_id from class4.area_prefixes where prefix_range(prefix)@>prefix_range(v_ret.src_prefix_routing) + order by length(prefix_range(prefix)) desc limit 1 + ); + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> SRC Area found: %',EXTRACT(MILLISECOND from v_end-v_start), v_ret.src_area_id; + /*}dbg*/ + + v_ret.dst_area_id:=( + select area_id from class4.area_prefixes where prefix_range(prefix)@>prefix_range(v_ret.dst_prefix_routing) + order by length(prefix_range(prefix)) desc limit 1 + ); + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DST Area found: %',EXTRACT(MILLISECOND from v_end-v_start), v_ret.dst_area_id; + /*}dbg*/ + + + select into v_area_direction * from class4.routing_tag_detection_rules + where + (src_area_id is null OR src_area_id = v_ret.src_area_id) AND + (dst_area_id is null OR dst_area_id = v_ret.dst_area_id) AND + prefix_range(src_prefix) @> prefix_range(v_ret.src_prefix_routing) AND + prefix_range(dst_prefix) @> prefix_range(v_ret.dst_prefix_routing) AND + yeti_ext.tag_compare(routing_tag_ids, v_call_tags, routing_tag_mode_id ) > 0 + order by + yeti_ext.tag_compare(routing_tag_ids, v_call_tags, routing_tag_mode_id) desc, + length(prefix_range(src_prefix)) desc, + length(prefix_range(dst_prefix)) desc, + src_area_id is null, + dst_area_id is null + limit 1; + if found then + /*dbg{*/ + RAISE NOTICE '% ms -> Routing tag detection rule found: %',EXTRACT(MILLISECOND from clock_timestamp() - v_start), row_to_json(v_area_direction); + /*}dbg*/ + v_call_tags=yeti_ext.tag_action(v_area_direction.tag_action_id, v_call_tags, v_area_direction.tag_action_value); + end if; + + v_ret.routing_tag_ids:=v_call_tags; + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> Routing tags: %',EXTRACT(MILLISECOND from v_end-v_start), v_ret.routing_tag_ids; + /*}dbg*/ + ---------------------------------------------------------------------- + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> Routing plan processing',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + + v_routing_key=v_ret.dst_prefix_routing; + + if v_rp.sorting_id=5 then -- route testing + v_test_vendor_id=regexp_replace(v_routing_key,'(.*)\*(.*)','\1')::integer; + v_routing_key=regexp_replace(v_routing_key,'(.*)\*(.*)','\2'); + v_ret.dst_prefix_out=v_routing_key; + v_ret.dst_prefix_routing=v_routing_key; + end if; + + if v_rp.use_lnp then + select into v_lnp_rule rules.* + from class4.routing_plan_lnp_rules rules + WHERE prefix_range(rules.dst_prefix)@>prefix_range(v_ret.dst_prefix_routing) and rules.routing_plan_id=v_rp.id + order by length(prefix_range(rules.dst_prefix)) desc limit 1; + if found then + v_ret.lnp_database_id=v_lnp_rule.database_id; + v_lnp_key=v_ret.dst_prefix_routing; + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> LNP. Need LNP lookup, LNP key: %',EXTRACT(MILLISECOND from v_end-v_start),v_lnp_key; + /*}dbg*/ + v_lnp_key=yeti_ext.regexp_replace_rand(v_lnp_key,v_lnp_rule.req_dst_rewrite_rule,v_lnp_rule.req_dst_rewrite_result); + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> LNP key translation. LNP key: %',EXTRACT(MILLISECOND from v_end-v_start),v_lnp_key; + /*}dbg*/ + -- try cache + select into v_ret.lrn lrn from class4.lnp_cache where dst=v_lnp_key AND database_id=v_lnp_rule.database_id and expires_at>v_now; + if found then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> LNP. Data found in cache, lrn: %',EXTRACT(MILLISECOND from v_end-v_start),v_ret.lrn; + /*}dbg*/ + -- TRANSLATING response from cache + v_ret.lrn=yeti_ext.regexp_replace_rand(v_ret.lrn,v_lnp_rule.lrn_rewrite_rule,v_lnp_rule.lrn_rewrite_result); + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> LNP. Translation. lrn: %',EXTRACT(MILLISECOND from v_end-v_start),v_ret.lrn; + /*}dbg*/ + v_routing_key=v_ret.lrn; + if v_lnp_rule.rewrite_call_destination then + v_ret.dst_prefix_out=v_ret.lrn; + v_ret.dst_prefix_routing=v_ret.lrn; + -- TODO shouldn't we perform tag detection again there? Call destination changed. + end if; + else + v_ret.lrn=switch21.lnp_resolve(v_ret.lnp_database_id,v_lnp_key); + if v_ret.lrn is null then -- fail + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> LNP. Query failed',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + if v_lnp_rule.drop_call_on_error then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> LNP. Dropping call',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_ret.disconnect_code_id=8003; --No response from LNP DB + RETURN NEXT v_ret; + RETURN; + end if; + else + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> LNP. Success, lrn: %',EXTRACT(MILLISECOND from v_end-v_start),v_ret.lrn; + /*}dbg*/ + -- TRANSLATING response from LNP DB + v_ret.lrn=yeti_ext.regexp_replace_rand(v_ret.lrn,v_lnp_rule.lrn_rewrite_rule,v_lnp_rule.lrn_rewrite_result); + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> LNP. Translation. lrn: %',EXTRACT(MILLISECOND from v_end-v_start),v_ret.lrn; + /*}dbg*/ + v_routing_key=v_ret.lrn; + if v_lnp_rule.rewrite_call_destination then + v_ret.dst_prefix_out=v_ret.lrn; + v_ret.dst_prefix_routing=v_ret.lrn; + -- TODO shouldn't we perform tag detection again there? Call destination changed. + end if; + end if; + end if; + end if; + end if; + + + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DST. search start. Routing key: %. Routing tags: %, Rate limit: %',EXTRACT(MILLISECOND from v_end-v_start), v_routing_key, v_ret.routing_tag_ids, v_destination_rate_limit; + /*}dbg*/ + v_src_network:=switch21.detect_network(v_ret.src_prefix_routing); + v_ret.src_network_id=v_src_network.network_id; + v_ret.src_country_id=v_src_network.country_id; + + v_network:=switch21.detect_network(v_ret.dst_prefix_routing); + v_ret.dst_network_id=v_network.network_id; + v_ret.dst_country_id=v_network.country_id; + + IF v_rp.validate_dst_number_network AND v_ret.dst_network_id is null THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> Network detection. DST network validation enabled and DST network was not found. Rejecting call',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + + v_ret.disconnect_code_id=8007; --No network detected for DST number + RETURN NEXT v_ret; + RETURN; + END IF; + + IF v_rp.validate_dst_number_format AND NOT (v_routing_key ~ '^[0-9]+$') THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> Dst number format is not valid. DST number: %s',EXTRACT(MILLISECOND from v_end-v_start), v_routing_key; + /*}dbg*/ + + v_ret.disconnect_code_id=8008; --Invalid DST number format + RETURN NEXT v_ret; + RETURN; + END IF; + + IF v_rp.validate_src_number_network AND v_ret.src_network_id is null AND lower(v_ret.src_prefix_routing)!='anonymous' THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> Network detection. SRC network validation enabled and SRC network was not found. Rejecting call',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + + v_ret.disconnect_code_id=8010; --No network detected for SRC number + RETURN NEXT v_ret; + RETURN; + END IF; + + IF v_rp.validate_src_number_format AND lower(v_ret.src_prefix_routing)!='anonymous' AND NOT (v_ret.src_prefix_routing ~ '^[0-9]+$') THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> SRC number format is not valid. SRC number: %s',EXTRACT(MILLISECOND from v_end-v_start), v_ret.src_prefix_routing; + /*}dbg*/ + + v_ret.disconnect_code_id=8011; --Invalid SRC number format + RETURN NEXT v_ret; + RETURN; + END IF; + + --- rateplan lookup + SELECT INTO v_rate_groups array_agg(rate_group_id) from class4.rate_plan_groups where rateplan_id = v_customer_auth_normalized.rateplan_id; + + SELECT into v_destination d.*/*,switch.tracelog(d.*)*/ + FROM class4.destinations d + WHERE + prefix_range(prefix)@>prefix_range(v_routing_key) + AND length(v_routing_key) between d.dst_number_min_length and d.dst_number_max_length + AND d.rate_group_id = ANY(v_rate_groups) + AND enabled + AND valid_from <= v_now + AND valid_till >= v_now + AND yeti_ext.tag_compare(d.routing_tag_ids, v_call_tags, d.routing_tag_mode_id)>0 + ORDER BY length(prefix_range(prefix)) DESC, yeti_ext.tag_compare(d.routing_tag_ids, v_call_tags) desc + limit 1; + IF NOT FOUND THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DST. Destination not found',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_ret.disconnect_code_id=111; --Cant find destination prefix + RETURN NEXT v_ret; + RETURN; + END IF; + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DST. found: %',EXTRACT(MILLISECOND from v_end-v_start),row_to_json(v_destination, true); + /*}dbg*/ + + v_ret.destination_id = v_destination.id; + v_ret.destination_prefix = v_destination.prefix; + v_ret.destination_initial_interval = v_destination.initial_interval; + v_ret.destination_next_interval = v_destination.next_interval; + + IF v_destination.allow_package_billing THEN + SELECT INTO v_package * FROM billing.package_counters pc + WHERE pc.account_id = v_customer_auth_normalized.account_id AND + prefix_range(pc.prefix)@>prefix_range(v_routing_key) + ORDER BY length(prefix_range(pc.prefix)) DESC LIMIT 1; + END IF; + IF v_package.id is not null AND v_package.duration > 0 AND NOT v_package.exclude THEN + v_ret.package_counter_id = v_package.id; + v_ret.time_limit = v_package.duration; + ELSE + v_ret.destination_fee = v_destination.connect_fee::varchar; + v_ret.destination_rate_policy_id = v_destination.rate_policy_id; + v_ret.destination_reverse_billing = v_destination.reverse_billing; + if v_destination.next_rate::float > v_destination_rate_limit then + v_ret.disconnect_code_id=8006; -- No destination with appropriate rate found + RETURN NEXT v_ret; + RETURN; + end if; + END IF; + + IF v_destination.reject_calls THEN + v_ret.disconnect_code_id=112; --Rejected by destination + RETURN NEXT v_ret; + RETURN; + END IF; + + select into v_rateplan * from class4.rateplans where id=v_customer_auth_normalized.rateplan_id; + if COALESCE(v_destination.profit_control_mode_id,v_rateplan.profit_control_mode_id)=2 then -- per call + v_rate_limit=v_destination.next_rate::float; + end if; + + + /* + FIND dialpeers logic. Queries must use prefix index for best performance + */ + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DP. search start. Routing key: %. Rate limit: %. Routing tag: %',EXTRACT(MILLISECOND from v_end-v_start), v_routing_key, v_rate_limit, v_ret.routing_tag_ids; + /*}dbg*/ + + + /* apply defered rewrites there, not really after routing, but without affecting v_routing_key */ + + FOREACH v_rewrite IN ARRAY v_defered_src_rewrites LOOP + v_ret.src_prefix_out = yeti_ext.regexp_replace_rand( + v_ret.src_prefix_out, + v_rewrite.rule, + v_rewrite.result + ); + END LOOP; + + FOREACH v_rewrite IN ARRAY v_defered_dst_rewrites LOOP + v_ret.dst_prefix_out = yeti_ext.regexp_replace_rand( + v_ret.dst_prefix_out, + v_rewrite.rule, + v_rewrite.result + ); + END LOOP; + + SELECT INTO v_routing_groups array_agg(routing_group_id) from class4.routing_plan_groups where routing_plan_id = v_customer_auth_normalized.routing_plan_id; + + CASE v_rp.sorting_id + WHEN '1' THEN -- LCR,Prio, ACD&ASR control + FOR routedata IN ( + WITH step1 AS( + SELECT + (t_dp.*)::class4.dialpeers as s1_dialpeer, + t_dp.vendor_id as s1_vendor_id, + t_dp.account_id as s1_vendor_account_id, + t_dp.next_rate as dp_next_rate, + t_dp.lcr_rate_multiplier AS dp_lcr_rate_multiplier, + t_dp.priority AS dp_priority, + t_dp.locked as dp_locked, + t_dp.enabled as dp_enabled, + rank() OVER ( + PARTITION BY t_dp.vendor_id, t_dp.routeset_discriminator_id + ORDER BY + length(prefix_range(t_dp.prefix)) desc, + yeti_ext.tag_compare(t_dp.routing_tag_ids, v_call_tags, t_dp.routing_tag_mode_id) desc, + t_dp.exclusive_route desc -- in case when we have two identical prefixes with different exclusive flag value, we should lift up exclusive route, otherwise it will be filtered at WHERE r=1 and exclusive_rank=1 + ) as r, + rank() OVER ( + ORDER BY t_dp.exclusive_route desc -- force top rank for exclusive route + ) as exclusive_rank + FROM class4.dialpeers t_dp + WHERE + prefix_range(t_dp.prefix)@>prefix_range(v_routing_key) + AND length(v_routing_key) between t_dp.dst_number_min_length and t_dp.dst_number_max_length + AND t_dp.routing_group_id = ANY(v_routing_groups) + and t_dp.valid_from<=v_now + and t_dp.valid_till>=v_now + AND yeti_ext.tag_compare(t_dp.routing_tag_ids, v_call_tags, t_dp.routing_tag_mode_id) > 0 + ) + SELECT + s1_dialpeer as s2_dialpeer, + (t_vendor_account.*)::billing.accounts as s2_vendor_account + FROM step1 + JOIN public.contractors t_vendor ON step1.s1_vendor_id = t_vendor.id + JOIN billing.accounts t_vendor_account ON step1.s1_vendor_account_id = t_vendor_account.id + WHERE + r=1 + and exclusive_rank=1 + AND dp_next_rate<=v_rate_limit + AND dp_enabled + and not dp_locked --ACD&ASR control for DP + AND t_vendor_account.balance < t_vendor_account.max_balance + AND t_vendor.enabled AND t_vendor.vendor + ORDER BY dp_next_rate*dp_lcr_rate_multiplier, dp_priority DESC + LIMIT v_rp.max_rerouting_attempts + ) LOOP + RETURN QUERY + /*rel{*/SELECT * from process_dp_release(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}rel*/ + /*dbg{*/SELECT * from process_dp_debug(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}dbg*/ + end LOOP; + WHEN '2' THEN --LCR, no prio, No ACD&ASR control + FOR routedata IN ( + WITH step1 AS( -- filtering + SELECT + (t_dp.*)::class4.dialpeers as s1_dialpeer, + t_dp.vendor_id as s1_vendor_id, + t_dp.account_id as s1_vendor_account_id, + rank() OVER ( + PARTITION BY t_dp.vendor_id, t_dp.routeset_discriminator_id + ORDER BY + length(prefix_range(t_dp.prefix)) desc, + yeti_ext.tag_compare(t_dp.routing_tag_ids, v_call_tags, t_dp.routing_tag_mode_id) desc, + t_dp.exclusive_route desc -- in case when we have two identical prefixes with different exclusive flag value, we should lift up exclusive route, otherwise it will be filtered at WHERE r=1 and exclusive_rank=1 + ) as r, + rank() OVER ( + ORDER BY t_dp.exclusive_route desc -- force top rank for exclusive route + ) as exclusive_rank, + t_dp.next_rate*t_dp.lcr_rate_multiplier as dp_metric, + t_dp.next_rate as dp_next_rate, + t_dp.enabled as dp_enabled + FROM class4.dialpeers t_dp + WHERE + prefix_range(t_dp.prefix)@>prefix_range(v_routing_key) + AND length(v_routing_key) between t_dp.dst_number_min_length and t_dp.dst_number_max_length + AND t_dp.routing_group_id = ANY(v_routing_groups) + and t_dp.valid_from<=v_now + and t_dp.valid_till>=v_now + AND yeti_ext.tag_compare(t_dp.routing_tag_ids,v_call_tags, t_dp.routing_tag_mode_id)>0 + ) + SELECT + s1_dialpeer as s2_dialpeer, + (t_vendor_account.*)::billing.accounts as s2_vendor_account + FROM step1 + JOIN public.contractors t_vendor ON step1.s1_vendor_id = t_vendor.id + JOIN billing.accounts t_vendor_account ON step1.s1_vendor_account_id = t_vendor_account.id + WHERE + r=1 + and exclusive_rank=1 + AND dp_enabled + and dp_next_rate<=v_rate_limit + AND t_vendor_account.balance < t_vendor_account.max_balance + AND t_vendor.enabled AND t_vendor.vendor + ORDER BY dp_metric + LIMIT v_rp.max_rerouting_attempts + ) LOOP + RETURN QUERY + /*rel{*/SELECT * from process_dp_release(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}rel*/ + /*dbg{*/SELECT * from process_dp_debug(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}dbg*/ + END LOOP; + WHEN '3' THEN --Prio, LCR, ACD&ASR control + FOR routedata in( + WITH step1 AS( -- filtering + SELECT + (t_dp.*)::class4.dialpeers as s1_dialpeer, + t_dp.vendor_id as s1_vendor_id, + t_dp.account_id as s1_vendor_account_id, + rank() OVER ( + PARTITION BY t_dp.vendor_id, t_dp.routeset_discriminator_id + ORDER BY + length(prefix_range(t_dp.prefix)) desc, + yeti_ext.tag_compare(t_dp.routing_tag_ids, v_call_tags, t_dp.routing_tag_mode_id) desc, + t_dp.exclusive_route desc -- in case when we have two identical prefixes with different exclusive flag value, we should lift up exclusive route, otherwise it will be filtered at WHERE r=1 and exclusive_rank=1 + ) as r, + rank() OVER ( + ORDER BY t_dp.exclusive_route desc -- force top rank for exclusive route + ) as exclusive_rank, + t_dp.priority as dp_metric_priority, + t_dp.next_rate*t_dp.lcr_rate_multiplier as dp_metric, + t_dp.next_rate as dp_next_rate, + t_dp.locked as dp_locked, + t_dp.enabled as dp_enabled + FROM class4.dialpeers t_dp + WHERE + prefix_range(t_dp.prefix)@>prefix_range(v_routing_key) + AND length(v_routing_key) between t_dp.dst_number_min_length and t_dp.dst_number_max_length + AND t_dp.routing_group_id = ANY(v_routing_groups) + and t_dp.valid_from<=v_now + and t_dp.valid_till>=v_now + AND yeti_ext.tag_compare(t_dp.routing_tag_ids, v_call_tags, t_dp.routing_tag_mode_id) > 0 + ) + SELECT + s1_dialpeer as s2_dialpeer, + (t_vendor_account.*)::billing.accounts as s2_vendor_account + FROM step1 + JOIN public.contractors t_vendor ON step1.s1_vendor_id = t_vendor.id + JOIN billing.accounts t_vendor_account ON step1.s1_vendor_account_id = t_vendor_account.id + WHERE + r=1 + AND exclusive_rank=1 + AND dp_next_rate<=v_rate_limit + AND dp_enabled + AND NOT dp_locked + AND t_vendor_account.balance < t_vendor_account.max_balance + AND t_vendor.enabled AND t_vendor.vendor + ORDER BY dp_metric_priority DESC, dp_metric + LIMIT v_rp.max_rerouting_attempts + )LOOP + RETURN QUERY + /*rel{*/SELECT * from process_dp_release(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}rel*/ + /*dbg{*/SELECT * from process_dp_debug(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}dbg*/ + END LOOP; + WHEN'4' THEN -- LCRD, Prio, ACD&ACR control + FOR routedata IN ( + WITH step1 AS( + SELECT + (t_dp.*)::class4.dialpeers as s1_dialpeer, + t_dp.vendor_id as s1_vendor_id, + t_dp.account_id as s1_vendor_account_id, + rank() OVER ( + PARTITION BY t_dp.vendor_id, t_dp.routeset_discriminator_id + ORDER BY + length(prefix_range(t_dp.prefix)) desc, + yeti_ext.tag_compare(t_dp.routing_tag_ids, v_call_tags, t_dp.routing_tag_mode_id) desc, + t_dp.exclusive_route desc -- in case when we have two identical prefixes with different exclusive flag value, we should lift up exclusive route, otherwise it will be filtered at WHERE r=1 and exclusive_rank=1 + ) as r, + rank() OVER ( + ORDER BY t_dp.exclusive_route desc -- force top rank for exclusive route + ) as exclusive_rank, + ((t_dp.next_rate - first_value(t_dp.next_rate) OVER(ORDER BY t_dp.next_rate ASC)) > v_rp.rate_delta_max)::INTEGER *(t_dp.next_rate + t_dp.priority) - t_dp.priority AS r2, + t_dp.next_rate as dp_next_rate, + t_dp.locked as dp_locked, + t_dp.enabled as dp_enabled + FROM class4.dialpeers t_dp + WHERE + prefix_range(t_dp.prefix)@>prefix_range(v_routing_key) + AND length(v_routing_key) between t_dp.dst_number_min_length and t_dp.dst_number_max_length + AND t_dp.routing_group_id = ANY(v_routing_groups) + and t_dp.valid_from<=v_now + and t_dp.valid_till>=v_now + AND yeti_ext.tag_compare(t_dp.routing_tag_ids,v_call_tags, t_dp.routing_tag_mode_id)>0 + ) + SELECT + s1_dialpeer as s2_dialpeer, + (t_vendor_account.*)::billing.accounts as s2_vendor_account + FROM step1 + JOIN public.contractors t_vendor ON step1.s1_vendor_id = t_vendor.id + JOIN billing.accounts t_vendor_account ON step1.s1_vendor_account_id = t_vendor_account.id + WHERE + r=1 + and exclusive_rank=1 + and dp_next_rate <= v_rate_limit + and dp_enabled + and not dp_locked --ACD&ASR control for DP + AND t_vendor_account.balance < t_vendor_account.max_balance + AND t_vendor.enabled AND t_vendor.vendor + ORDER BY r2 ASC + LIMIT v_rp.max_rerouting_attempts + ) LOOP + RETURN QUERY + /*rel{*/SELECT * from process_dp_release(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}rel*/ + /*dbg{*/SELECT * from process_dp_debug(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}dbg*/ + end LOOP; + WHEN'5' THEN -- Route test + FOR routedata IN ( + WITH step1 AS( -- filtering + SELECT + (t_dp.*)::class4.dialpeers as s1_dialpeer, + t_dp.vendor_id as s1_vendor_id, + t_dp.account_id as s1_vendor_account_id, + rank() OVER ( + PARTITION BY t_dp.vendor_id, t_dp.routeset_discriminator_id + ORDER BY + length(prefix_range(t_dp.prefix)) desc, + yeti_ext.tag_compare(t_dp.routing_tag_ids, v_call_tags, t_dp.routing_tag_mode_id) desc, + t_dp.exclusive_route desc -- in case when we have two identical prefixes with different exclusive flag value, we should lift up exclusive route, otherwise it will be filtered at WHERE r=1 and exclusive_rank=1 + ) as r, + rank() OVER ( + ORDER BY t_dp.exclusive_route desc -- force top rank for exclusive route + ) as exclusive_rank, + t_dp.priority as dp_metric_priority, + t_dp.next_rate*t_dp.lcr_rate_multiplier as dp_metric, + t_dp.next_rate as dp_next_rate, + t_dp.enabled as dp_enabled + FROM class4.dialpeers t_dp + WHERE + prefix_range(t_dp.prefix)@>prefix_range(v_routing_key) + AND length(v_routing_key) between t_dp.dst_number_min_length and t_dp.dst_number_max_length + AND t_dp.routing_group_id = ANY(v_routing_groups) + and t_dp.valid_from<=v_now + and t_dp.valid_till>=v_now + and t_dp.vendor_id = v_test_vendor_id + AND yeti_ext.tag_compare(t_dp.routing_tag_ids,v_call_tags, t_dp.routing_tag_mode_id) > 0 + ) + SELECT + s1_dialpeer as s2_dialpeer, + (t_vendor_account.*)::billing.accounts as s2_vendor_account + FROM step1 + JOIN public.contractors t_vendor ON step1.s1_vendor_id = t_vendor.id + JOIN billing.accounts t_vendor_account ON step1.s1_vendor_account_id = t_vendor_account.id + WHERE + r=1 + and exclusive_rank=1 + and dp_enabled + and dp_next_rate<=v_rate_limit + AND t_vendor_account.balance < t_vendor_account.max_balance + AND t_vendor.enabled AND t_vendor.vendor + ORDER BY dp_metric_priority DESC, dp_metric + LIMIT v_rp.max_rerouting_attempts + )LOOP + RETURN QUERY + /*rel{*/SELECT * from process_dp_release(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}rel*/ + /*dbg{*/SELECT * from process_dp_debug(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}dbg*/ + END LOOP; + WHEN'6' THEN -- QD.Static,LCR,ACD&ACR control + v_random:=random(); + FOR routedata in( + WITH step1 AS( -- filtering + SELECT + (t_dp.*)::class4.dialpeers as s1_dialpeer, + (t_vendor_account.*)::billing.accounts as s1_vendor_account, + rank() OVER ( + PARTITION BY t_dp.vendor_id, t_dp.routeset_discriminator_id + ORDER BY + length(prefix_range(t_dp.prefix)) desc, + yeti_ext.tag_compare(t_dp.routing_tag_ids, v_call_tags, t_dp.routing_tag_mode_id) desc, + t_dp.exclusive_route desc -- in case when we have two identical prefixes with different exclusive flag value, we should lift up exclusive route, otherwise it will be filtered at WHERE r=1 and exclusive_rank=1 + ) as r, + rank() OVER ( + ORDER BY t_dp.exclusive_route desc -- force top rank for exclusive route + ) as exclusive_rank, + rank() OVER ( + PARTITION BY t_dp.vendor_id, t_dp.routeset_discriminator_id + ORDER BY length(prefix_range(coalesce(rpsr.prefix,''))) desc + ) as r2, + t_dp.priority as dp_metric_priority, + t_dp.next_rate*t_dp.lcr_rate_multiplier as dp_metric, + t_dp.next_rate as dp_next_rate, + t_dp.locked as dp_locked, + t_dp.enabled as dp_enabled, + t_dp.force_hit_rate as dp_force_hit_rate, + COALESCE(rpsr.priority, t_dp.priority) as rpsr_priority, + COALESCE(rpsr.weight, 100) as rpsr_weight + FROM class4.dialpeers t_dp + JOIN billing.accounts t_vendor_account ON t_dp.account_id=t_vendor_account.id + join public.contractors t_vendor on t_dp.vendor_id=t_vendor.id + left join class4.routing_plan_static_routes rpsr + ON rpsr.routing_plan_id=v_customer_auth_normalized.routing_plan_id + and rpsr.vendor_id=t_dp.vendor_id + AND prefix_range(rpsr.prefix)@>prefix_range(v_routing_key) + WHERE + prefix_range(t_dp.prefix)@>prefix_range(v_routing_key) + AND length(v_routing_key) between t_dp.dst_number_min_length and t_dp.dst_number_max_length + AND t_dp.routing_group_id = ANY(v_routing_groups) + and t_dp.valid_from<=v_now + and t_dp.valid_till>=v_now + AND t_vendor_account.balance 0 + ) + SELECT s1_dialpeer as s2_dialpeer, + s1_vendor_account as s2_vendor_account + FROM step1 + WHERE + r=1 + and exclusive_rank=1 + and r2=1 + and dp_next_rate<=v_rate_limit + and dp_enabled + and not dp_locked + ORDER BY + coalesce(v_random<=dp_force_hit_rate,false) desc, + rpsr_priority, + yeti_ext.rank_dns_srv(rpsr_weight) over ( partition by rpsr_priority order by rpsr_weight), + dp_metric + LIMIT v_rp.max_rerouting_attempts + )LOOP + RETURN QUERY + /*rel{*/SELECT * from process_dp_release(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}rel*/ + /*dbg{*/SELECT * from process_dp_debug(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}dbg*/ + END LOOP; + WHEN'7' THEN -- QD.Static, No ACD&ACR control + v_random:=random(); + FOR routedata in( + WITH step1 AS( -- filtering + SELECT + (t_dp.*)::class4.dialpeers as s1_dialpeer, + (t_vendor_account.*)::billing.accounts as s1_vendor_account, + rank() OVER ( + PARTITION BY t_dp.vendor_id, t_dp.routeset_discriminator_id + ORDER BY + length(prefix_range(t_dp.prefix)) desc, + yeti_ext.tag_compare(t_dp.routing_tag_ids, v_call_tags, t_dp.routing_tag_mode_id) desc, + t_dp.exclusive_route desc -- in case when we have two identical prefixes with different exclusive flag value, we should lift up exclusive route, otherwise it will be filtered at WHERE r=1 and exclusive_rank=1 + ) as r, + rank() OVER ( + ORDER BY t_dp.exclusive_route desc -- force top rank for exclusive route + ) as exclusive_rank, + rank() OVER ( + PARTITION BY t_dp.vendor_id, t_dp.routeset_discriminator_id + ORDER BY length(prefix_range(coalesce(rpsr.prefix,''))) desc + ) as r2, + t_dp.priority as dp_metric_priority, + t_dp.next_rate*t_dp.lcr_rate_multiplier as dp_metric, + t_dp.next_rate as dp_next_rate, + t_dp.enabled as dp_enabled, + t_dp.force_hit_rate as dp_force_hit_rate, + rpsr.priority as rpsr_priority, + rpsr.weight as rpsr_weight + FROM class4.dialpeers t_dp + JOIN billing.accounts t_vendor_account ON t_dp.account_id=t_vendor_account.id + join public.contractors t_vendor on t_dp.vendor_id=t_vendor.id + join class4.routing_plan_static_routes rpsr + ON rpsr.routing_plan_id=v_customer_auth_normalized.routing_plan_id + and rpsr.vendor_id=t_dp.vendor_id + AND prefix_range(rpsr.prefix)@>prefix_range(v_routing_key) + WHERE + prefix_range(t_dp.prefix)@>prefix_range(v_routing_key) + AND length(v_routing_key) between t_dp.dst_number_min_length and t_dp.dst_number_max_length + AND t_dp.routing_group_id = ANY(v_routing_groups) + and t_dp.valid_from<=v_now + and t_dp.valid_till>=v_now + AND t_vendor_account.balance 0 + ) + SELECT s1_dialpeer as s2_dialpeer, + s1_vendor_account as s2_vendor_account + FROM step1 + WHERE + r=1 + and exclusive_rank=1 + and r2=1 + and dp_next_rate<=v_rate_limit + and dp_enabled + ORDER BY + coalesce(v_random<=dp_force_hit_rate,false) desc, + rpsr_priority, + yeti_ext.rank_dns_srv(rpsr_weight) over ( partition by rpsr_priority order by rpsr_weight), + dp_metric + LIMIT v_rp.max_rerouting_attempts + )LOOP + RETURN QUERY + /*rel{*/SELECT * from process_dp_release(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}rel*/ + /*dbg{*/SELECT * from process_dp_debug(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}dbg*/ + END LOOP; + + ELSE + RAISE NOTICE 'BUG: unknown sorting_id'; + END CASE; + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> Dialpeer search done',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_ret.disconnect_code_id=113; --No routes + RETURN NEXT v_ret; + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DONE.',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + RETURN; + END; + $_$; + +CREATE or replace FUNCTION switch21.process_gw(i_profile switch21.callprofile_ty, i_destination class4.destinations, i_dp class4.dialpeers, i_customer_acc billing.accounts, i_customer_gw class4.gateways, i_vendor_acc billing.accounts, i_vendor_gw class4.gateways, i_send_billing_information boolean, i_max_call_length integer, i_diversion character varying[], i_privacy character varying[], i_pai character varying[], i_ppi character varying) RETURNS switch21.callprofile_ty + LANGUAGE plpgsql STABLE SECURITY DEFINER COST 100000 + AS $_$ +DECLARE + i integer; + v_vendor_allowtime real; + v_route_found boolean:=false; + v_from_user varchar; + v_from_domain varchar; + v_schema varchar; + v_termination_numberlist class4.numberlists%rowtype; + v_termination_numberlist_item class4.numberlist_items%rowtype; + v_termination_numberlist_size integer; + v_aleg_append_headers_reply varchar[] not null default ARRAY[]::varchar[]; + v_bleg_append_headers_req varchar[] not null default ARRAY[]::varchar[]; + v_diversion varchar[] not null default ARRAY[]::varchar[]; + v_diversion_header varchar; + v_diversion_out varchar[] not null default ARRAY[]::varchar[]; + v_pai_out varchar[] not null default ARRAY[]::varchar[]; + v_pai varchar; + v_allow_pai boolean:=true; + v_to_uri_params varchar[] not null default ARRAY[]::varchar[]; + v_from_uri_params varchar[] not null default ARRAY[]::varchar[]; + v_ruri_host varchar; + v_ruri_params varchar[] not null default ARRAY[]::varchar[]; + v_ruri_user_params varchar[] not null default ARRAY[]::varchar[]; + v_to_username varchar; + /*dbg{*/ + v_start timestamp; + v_end timestamp; + /*}dbg*/ +BEGIN + /*dbg{*/ + v_start:=now(); + --RAISE NOTICE 'process_dp in: %',i_profile; + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DP. Found dialpeer: %',EXTRACT(MILLISECOND from v_end-v_start),row_to_json(i_dp,true); + /*}dbg*/ + + --RAISE NOTICE 'process_dp dst: %',i_destination; + + i_profile.destination_id:=i_destination.id; + i_profile.destination_fee:=i_destination.connect_fee::varchar; + i_profile.destination_rate_policy_id:=i_destination.rate_policy_id; + + --vendor account capacity limit; + i_profile.legb_res= ''; + if i_vendor_acc.termination_capacity is not null then + i_profile.legb_res = '2:'||i_dp.account_id::varchar||':'||i_vendor_acc.termination_capacity::varchar||':1;'; + end if; + + if i_vendor_acc.total_capacity is not null then + i_profile.legb_res = i_profile.legb_res||'7:'||i_dp.account_id::varchar||':'||i_vendor_acc.total_capacity::varchar||':1;'; + end if; + + -- dialpeer account capacity limit; + if i_dp.capacity is not null then + i_profile.legb_res = i_profile.legb_res||'6:'||i_dp.id::varchar||':'||i_dp.capacity::varchar||':1;'; + end if; + + /* */ + i_profile.dialpeer_id=i_dp.id; + i_profile.dialpeer_prefix=i_dp.prefix; + i_profile.dialpeer_next_rate=i_dp.next_rate::varchar; + i_profile.dialpeer_initial_rate=i_dp.initial_rate::varchar; + i_profile.dialpeer_initial_interval=i_dp.initial_interval; + i_profile.dialpeer_next_interval=i_dp.next_interval; + i_profile.dialpeer_fee=i_dp.connect_fee::varchar; + i_profile.dialpeer_reverse_billing=i_dp.reverse_billing; + i_profile.vendor_id=i_dp.vendor_id; + i_profile.vendor_acc_id=i_dp.account_id; + i_profile.term_gw_id=i_vendor_gw.id; + + i_profile.orig_gw_name=i_customer_gw."name"; + i_profile.orig_gw_external_id=i_customer_gw.external_id; + + i_profile.term_gw_name=i_vendor_gw."name"; + i_profile.term_gw_external_id=i_vendor_gw.external_id; + + i_profile.customer_account_name=i_customer_acc."name"; + + i_profile.routing_group_id:=i_dp.routing_group_id; + + if i_send_billing_information then + v_aleg_append_headers_reply=array_append(v_aleg_append_headers_reply, (E'X-VND-INIT-INT:'||i_profile.dialpeer_initial_interval)::varchar); + v_aleg_append_headers_reply=array_append(v_aleg_append_headers_reply, (E'X-VND-NEXT-INT:'||i_profile.dialpeer_next_interval)::varchar); + v_aleg_append_headers_reply=array_append(v_aleg_append_headers_reply, (E'X-VND-INIT-RATE:'||i_profile.dialpeer_initial_rate)::varchar); + v_aleg_append_headers_reply=array_append(v_aleg_append_headers_reply, (E'X-VND-NEXT-RATE:'||i_profile.dialpeer_next_rate)::varchar); + v_aleg_append_headers_reply=array_append(v_aleg_append_headers_reply, (E'X-VND-CF:'||i_profile.dialpeer_fee)::varchar); + end if; + v_aleg_append_headers_reply = array_cat(v_aleg_append_headers_reply,i_customer_gw.orig_append_headers_reply); + i_profile.aleg_append_headers_reply=ARRAY_TO_STRING(v_aleg_append_headers_reply,'\r\n'); + + if i_destination.use_dp_intervals THEN + i_profile.destination_initial_interval:=i_dp.initial_interval; + i_profile.destination_next_interval:=i_dp.next_interval; + ELSE + i_profile.destination_initial_interval:=i_destination.initial_interval; + i_profile.destination_next_interval:=i_destination.next_interval; + end if; + + IF i_profile.package_counter_id IS NULL THEN + CASE i_profile.destination_rate_policy_id + WHEN 1 THEN -- fixed + i_profile.destination_next_rate:=i_destination.next_rate::varchar; + i_profile.destination_initial_rate:=i_destination.initial_rate::varchar; + WHEN 2 THEN -- based on dialpeer + i_profile.destination_next_rate:=(COALESCE(i_destination.dp_margin_fixed,0)+i_dp.next_rate*(1+COALESCE(i_destination.dp_margin_percent,0)))::varchar; + i_profile.destination_initial_rate:=(COALESCE(i_destination.dp_margin_fixed,0)+i_dp.initial_rate*(1+COALESCE(i_destination.dp_margin_percent,0)))::varchar; + WHEN 3 THEN -- min + IF i_dp.next_rate >= i_destination.next_rate THEN + i_profile.destination_next_rate:=i_destination.next_rate::varchar; -- FIXED least + i_profile.destination_initial_rate:=i_destination.initial_rate::varchar; + ELSE + i_profile.destination_next_rate:=(COALESCE(i_destination.dp_margin_fixed,0)+i_dp.next_rate*(1+COALESCE(i_destination.dp_margin_percent,0)))::varchar; -- DYNAMIC + i_profile.destination_initial_rate:=(COALESCE(i_destination.dp_margin_fixed,0)+i_dp.initial_rate*(1+COALESCE(i_destination.dp_margin_percent,0)))::varchar; + END IF; + WHEN 4 THEN -- max + IF i_dp.next_rate < i_destination.next_rate THEN + i_profile.destination_next_rate:=i_destination.next_rate::varchar; --FIXED + i_profile.destination_initial_rate:=i_destination.initial_rate::varchar; + ELSE + i_profile.destination_next_rate:=(COALESCE(i_destination.dp_margin_fixed,0)+i_dp.next_rate*(1+COALESCE(i_destination.dp_margin_percent,0)))::varchar; -- DYNAMIC + i_profile.destination_initial_rate:=(COALESCE(i_destination.dp_margin_fixed,0)+i_dp.initial_rate*(1+COALESCE(i_destination.dp_margin_percent,0)))::varchar; + END IF; + ELSE + -- + end case; + END IF; + + + /* time limiting START */ + --SELECT INTO STRICT v_c_acc * FROM billing.accounts WHERE id=v_customer_auth.account_id; + --SELECT INTO STRICT v_v_acc * FROM billing.accounts WHERE id=v_dialpeer.account_id; + + + if i_profile.time_limit is null then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> process_gw: customer time limit is not set, calculating',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + IF (i_customer_acc.balance-i_customer_acc.min_balance)-i_destination.connect_fee-i_destination.initial_rate/60*i_destination.initial_interval<0 THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> process_gw: No enough customer balance even for first billing interval. rejecting',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + i_profile.disconnect_code_id=8000; --Not enough customer balance + RETURN i_profile; + ELSIF i_destination.next_rate!=0 AND i_destination.next_interval!=0 THEN + i_profile.time_limit = (i_destination.initial_interval+ + LEAST(FLOOR(((i_customer_acc.balance-i_customer_acc.min_balance)-i_destination.connect_fee-i_destination.initial_rate/60*i_destination.initial_interval)/ + (i_destination.next_rate/60*i_destination.next_interval)),24e6)::integer*i_destination.next_interval)::integer; + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> process_gw: customer time limit: %',EXTRACT(MILLISECOND from v_end-v_start), i_profile.time_limit; + /*}dbg*/ + ELSE /* DST rates is 0, allowing maximum call length */ + i_profile.time_limit = COALESCE(i_customer_acc.max_call_duration, i_max_call_length)::integer; + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> process_gw: DST rate is 0. customer time limit set to max value: %',EXTRACT(MILLISECOND from v_end-v_start), i_profile.time_limit; + /*}dbg*/ + end IF; + end if; + + IF (i_vendor_acc.max_balance-i_vendor_acc.balance)-i_dp.connect_fee <0 THEN /* No enough balance, skipping this profile */ + v_vendor_allowtime:=0; + return null; + ELSIF (i_vendor_acc.max_balance-i_vendor_acc.balance)-i_dp.connect_fee-i_dp.initial_rate/60*i_dp.initial_interval<0 THEN /* No enough balance even for first billing interval - skipping this profile */ + return null; + ELSIF i_dp.next_rate!=0 AND i_dp.next_interval!=0 THEN /* DP rates is not zero, calculating limit */ + v_vendor_allowtime:=i_dp.initial_interval+ + LEAST(FLOOR(((i_vendor_acc.max_balance-i_vendor_acc.balance)-i_dp.connect_fee-i_dp.initial_rate/60*i_dp.initial_interval)/ + (i_dp.next_rate/60*i_dp.next_interval)),24e6)::integer*i_dp.next_interval; + ELSE /* DP rates is 0, allowing maximum call length */ + v_vendor_allowtime:=COALESCE(i_vendor_acc.max_call_duration, i_max_call_length); + end IF; + + i_profile.time_limit=LEAST( + COALESCE(i_customer_acc.max_call_duration, i_max_call_length)::integer, + COALESCE(i_vendor_acc.max_call_duration, i_max_call_length)::integer, + v_vendor_allowtime, + i_profile.time_limit + )::integer; + + + /* number rewriting _After_ routing */ + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DP. Before rewrite src_prefix: % , dst_prefix: %',EXTRACT(MILLISECOND from v_end-v_start),i_profile.src_prefix_out,i_profile.dst_prefix_out; + /*}dbg*/ + i_profile.dst_prefix_out=yeti_ext.regexp_replace_rand(i_profile.dst_prefix_out,i_dp.dst_rewrite_rule,i_dp.dst_rewrite_result); + i_profile.src_prefix_out=yeti_ext.regexp_replace_rand(i_profile.src_prefix_out,i_dp.src_rewrite_rule,i_dp.src_rewrite_result); + i_profile.src_name_out=yeti_ext.regexp_replace_rand(i_profile.src_name_out,i_dp.src_name_rewrite_rule,i_dp.src_name_rewrite_result, true); + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DP. After rewrite src_prefix: % , dst_prefix: %',EXTRACT(MILLISECOND from v_end-v_start),i_profile.src_prefix_out,i_profile.dst_prefix_out; + /*}dbg*/ + + /* + get termination gw data + */ + --SELECT into v_dst_gw * from class4.gateways WHERE id=v_dialpeer.gateway_id; + --SELECT into v_orig_gw * from class4.gateways WHERE id=v_customer_auth.gateway_id; + --vendor gw + if i_vendor_gw.termination_capacity is not null then + i_profile.legb_res:=i_profile.legb_res||'5:'||i_vendor_gw.id::varchar||':'||i_vendor_gw.termination_capacity::varchar||':1;'; + end if; + + + /* + numberlist processing _After_ routing _IN_ termination GW + */ + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW. Before numberlist processing src_prefix: % , dst_prefix: %',EXTRACT(MILLISECOND from v_end-v_start),i_profile.src_prefix_out,i_profile.dst_prefix_out; + /*}dbg*/ + + + ----- DST Numberlist processing------------------------------------------------------------------------------------------------------- + IF i_vendor_gw.termination_dst_numberlist_id is not null then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW DST Numberlist processing. Lookup by key: %',EXTRACT(MILLISECOND from v_end-v_start), i_profile.dst_prefix_out; + /*}dbg*/ + + select into v_termination_numberlist * from class4.numberlists where id=i_vendor_gw.termination_dst_numberlist_id; + CASE v_termination_numberlist.mode_id + when 1 then -- strict match + select into v_termination_numberlist_item * from class4.numberlist_items ni + where + ni.numberlist_id=i_vendor_gw.termination_dst_numberlist_id and + ni.key=i_profile.dst_prefix_out + limit 1; + when 2 then -- prefix match + select into v_termination_numberlist_item * from class4.numberlist_items ni + where + ni.numberlist_id=i_vendor_gw.termination_dst_numberlist_id and + prefix_range(ni.key)@>prefix_range(i_profile.dst_prefix_out) and + length(i_profile.dst_prefix_out) between ni.number_min_length and ni.number_max_length + order by length(prefix_range(ni.key)) desc + limit 1; + when 3 then -- random + select into v_termination_numberlist_size count(*) from class4.numberlist_items where numberlist_id=i_vendor_gw.termination_dst_numberlist_id; + select into v_termination_numberlist_item * from class4.numberlist_items ni + where ni.numberlist_id=i_vendor_gw.termination_dst_numberlist_id order by ni.id OFFSET floor(random()*v_termination_numberlist_size) limit 1; + END CASE; + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW DST Numberlist. key found: %',EXTRACT(MILLISECOND from v_end-v_start), row_to_json(v_termination_numberlist_item); + /*}dbg*/ + + IF v_termination_numberlist_item.action_id is not null and v_termination_numberlist_item.action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW DST Numberlist. Drop by key action. Skipping route. Key: %',EXTRACT(MILLISECOND from v_end-v_start), v_termination_numberlist_item.key; + /*}dbg*/ + RETURN null; + ELSIF v_termination_numberlist_item.action_id is not null and v_termination_numberlist_item.action_id=2 then + i_profile.src_prefix_out=yeti_ext.regexp_replace_rand( + i_profile.src_prefix_out, + v_termination_numberlist_item.src_rewrite_rule, + v_termination_numberlist_item.src_rewrite_result + ); + + i_profile.dst_prefix_out=yeti_ext.regexp_replace_rand( + i_profile.dst_prefix_out, + v_termination_numberlist_item.dst_rewrite_rule, + v_termination_numberlist_item.dst_rewrite_result + ); + ELSIF v_termination_numberlist_item.action_id is null and v_termination_numberlist.default_action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW DST Numberlist. Drop by default action. Skipping route',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + RETURN null; + ELSIF v_termination_numberlist_item.action_id is null and v_termination_numberlist.default_action_id=2 then + i_profile.src_prefix_out=yeti_ext.regexp_replace_rand( + i_profile.src_prefix_out, + v_termination_numberlist.default_src_rewrite_rule, + v_termination_numberlist.default_src_rewrite_result + ); + + i_profile.dst_prefix_out=yeti_ext.regexp_replace_rand( + i_profile.dst_prefix_out, + v_termination_numberlist.default_dst_rewrite_rule, + v_termination_numberlist.default_dst_rewrite_result + ); + END IF; + END IF; + + + + ----- SRC Numberlist processing------------------------------------------------------------------------------------------------------- + IF i_vendor_gw.termination_src_numberlist_id is not null then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW SRC Numberlist processing. Lookup by key: %',EXTRACT(MILLISECOND from v_end-v_start), i_profile.src_prefix_out; + /*}dbg*/ + + select into v_termination_numberlist * from class4.numberlists where id=i_vendor_gw.termination_src_numberlist_id; + CASE v_termination_numberlist.mode_id + when 1 then -- strict match + select into v_termination_numberlist_item * from class4.numberlist_items ni + where + ni.numberlist_id=i_vendor_gw.termination_src_numberlist_id and + ni.key=i_profile.src_prefix_out + limit 1; + when 2 then -- prefix match + select into v_termination_numberlist_item * from class4.numberlist_items ni + where + ni.numberlist_id=i_vendor_gw.termination_src_numberlist_id and + prefix_range(ni.key)@>prefix_range(i_profile.src_prefix_out) and + length(i_profile.src_prefix_out) between ni.number_min_length and ni.number_max_length + order by length(prefix_range(ni.key)) desc + limit 1; + when 3 then -- random + select into v_termination_numberlist_size count(*) from class4.numberlist_items where numberlist_id=i_vendor_gw.termination_src_numberlist_id; + select into v_termination_numberlist_item * from class4.numberlist_items ni + where ni.numberlist_id=i_vendor_gw.termination_src_numberlist_id order by ni.id OFFSET floor(random()*v_termination_numberlist_size) limit 1; + END CASE; + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW SRC Numberlist. key found: %',EXTRACT(MILLISECOND from v_end-v_start), row_to_json(v_termination_numberlist_item); + /*}dbg*/ + + IF v_termination_numberlist_item.action_id is not null and v_termination_numberlist_item.action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW SRC Numberlist. Drop by key action. Skipping route. Key: %',EXTRACT(MILLISECOND from v_end-v_start), v_termination_numberlist_item.key; + /*}dbg*/ + RETURN null; + ELSIF v_termination_numberlist_item.action_id is not null and v_termination_numberlist_item.action_id=2 then + i_profile.src_prefix_out=yeti_ext.regexp_replace_rand( + i_profile.src_prefix_out, + v_termination_numberlist_item.src_rewrite_rule, + v_termination_numberlist_item.src_rewrite_result + ); + + i_profile.dst_prefix_out=yeti_ext.regexp_replace_rand( + i_profile.dst_prefix_out, + v_termination_numberlist_item.dst_rewrite_rule, + v_termination_numberlist_item.dst_rewrite_result + ); + ELSIF v_termination_numberlist_item.action_id is null and v_termination_numberlist.default_action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW DST Numberlist. Drop by default action. Skipping route.',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + -- drop by default + RETURN null; + ELSIF v_termination_numberlist_item.action_id is null and v_termination_numberlist.default_action_id=2 then + i_profile.src_prefix_out=yeti_ext.regexp_replace_rand( + i_profile.src_prefix_out, + v_termination_numberlist.default_src_rewrite_rule, + v_termination_numberlist.default_src_rewrite_result + ); + + i_profile.dst_prefix_out=yeti_ext.regexp_replace_rand( + i_profile.dst_prefix_out, + v_termination_numberlist.default_dst_rewrite_rule, + v_termination_numberlist.default_dst_rewrite_result + ); + END IF; + END IF; + + + + /* + number rewriting _After_ routing _IN_ termination GW + */ + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW. Before rewrite src_prefix: % , dst_prefix: %',EXTRACT(MILLISECOND from v_end-v_start),i_profile.src_prefix_out,i_profile.dst_prefix_out; + /*}dbg*/ + i_profile.dst_prefix_out=yeti_ext.regexp_replace_rand(i_profile.dst_prefix_out,i_vendor_gw.dst_rewrite_rule,i_vendor_gw.dst_rewrite_result); + i_profile.src_prefix_out=yeti_ext.regexp_replace_rand(i_profile.src_prefix_out,i_vendor_gw.src_rewrite_rule,i_vendor_gw.src_rewrite_result); + i_profile.src_name_out=yeti_ext.regexp_replace_rand(i_profile.src_name_out,i_vendor_gw.src_name_rewrite_rule,i_vendor_gw.src_name_rewrite_result, true); + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW. After rewrite src_prefix: % , dst_prefix: %',EXTRACT(MILLISECOND from v_end-v_start),i_profile.src_prefix_out,i_profile.dst_prefix_out; + /*}dbg*/ + + + IF cardinality(i_diversion) > 0 AND i_vendor_gw.diversion_send_mode_id > 1 THEN + v_diversion = yeti_ext.regexp_replace_rand(i_diversion, i_vendor_gw.diversion_rewrite_rule, i_vendor_gw.diversion_rewrite_result); + + IF i_vendor_gw.diversion_send_mode_id = 2 AND i_vendor_gw.diversion_domain is not null AND i_vendor_gw.diversion_domain!='' THEN + /* Diversion as SIP URI */ + FOREACH v_diversion_header IN ARRAY v_diversion LOOP + v_bleg_append_headers_req = array_append( + v_bleg_append_headers_req, + format('Diversion: ', v_diversion_header, i_vendor_gw.diversion_domain)::varchar + ); + v_diversion_out = array_append( + v_diversion_out, + format('', v_diversion_header, i_vendor_gw.diversion_domain)::varchar + ); + END LOOP; + ELSIF i_vendor_gw.diversion_send_mode_id = 3 THEN + /* Diversion as TEL URI */ + FOREACH v_diversion_header IN ARRAY v_diversion LOOP + v_bleg_append_headers_req=array_append( + v_bleg_append_headers_req, + format('Diversion: ', v_diversion_header)::varchar + ); + v_diversion_out = array_append( + v_diversion_out, + format('', v_diversion_header)::varchar + ); + END LOOP; + END IF; + + /* Field for CDR */ + i_profile.diversion_out = array_to_string(v_diversion_out, ','); + END IF; + + CASE i_vendor_gw.privacy_mode_id + WHEN 0 THEN + -- do nothing + WHEN 1 THEN + IF cardinality(array_remove(i_privacy,'none')) > 0 THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW Privacy % requested but privacy_mode_is %. Skipping gw.',EXTRACT(MILLISECOND from v_end-v_start), i_privacy, i_vendor_gw.privacy_mode_id; + /*}dbg*/ + return null; + END IF; + WHEN 2 THEN + IF 'critical' = ANY(i_privacy) THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW Privacy % requested but privacy_mode_is %. Skipping gw.',EXTRACT(MILLISECOND from v_end-v_start), i_privacy, i_vendor_gw.privacy_mode_id; + /*}dbg*/ + return null; + END IF; + WHEN 3 THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW Privacy % requested, privacy_mode_is %. Applying privacy.',EXTRACT(MILLISECOND from v_end-v_start), i_privacy, i_vendor_gw.privacy_mode_id; + /*}dbg*/ + IF 'id' = ANY(i_privacy) OR 'user' = ANY(i_privacy) THEN + i_profile.src_prefix_out='anonymous'; + i_profile.src_name_out='Anonymous'; + v_from_domain = 'anonymous.invalid'; + END IF; + IF 'id' = ANY(i_privacy) OR 'header' = ANY(i_privacy) THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW Privacy % requested, privacy_mode_is %. removing PAI/PPI headers.',EXTRACT(MILLISECOND from v_end-v_start), i_privacy, i_vendor_gw.privacy_mode_id; + /*}dbg*/ + v_allow_pai = false; + END IF; + i_profile.privacy_out = array_to_string(i_privacy,';'); + v_bleg_append_headers_req = array_append(v_bleg_append_headers_req, format('Privacy: %s', array_to_string(i_privacy,';')::varchar)); + WHEN 4 THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW Privacy % requested, privacy_mode_is %. forwarding.',EXTRACT(MILLISECOND from v_end-v_start), i_privacy, i_vendor_gw.privacy_mode_id; + /*}dbg*/ + IF cardinality(i_privacy)>0 THEN + i_profile.privacy_out = array_to_string(i_privacy,';'); + v_bleg_append_headers_req = array_append(v_bleg_append_headers_req, format('Privacy: %s', array_to_string(i_privacy,';')::varchar)); + END IF; + WHEN 5 THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW Privacy % requested, privacy_mode_is %. forwarding with anonymous From.',EXTRACT(MILLISECOND from v_end-v_start), i_privacy, i_vendor_gw.privacy_mode_id; + /*}dbg*/ + IF 'id' = ANY(i_privacy) or 'user' = ANY(i_privacy) THEN + i_profile.src_prefix_out='anonymous'; + i_profile.src_name_out='Anonymous'; + v_from_domain = 'anonymous.invalid'; + i_profile.privacy_out = array_to_string(i_privacy,';'); + v_bleg_append_headers_req = array_append(v_bleg_append_headers_req, format('Privacy: %s', array_to_string(i_privacy,';')::varchar)); + END IF; + END CASE; + + IF v_allow_pai THEN + -- only if privacy mode allows to send PAI + IF i_vendor_gw.pai_send_mode_id = 1 THEN + -- TEL URI + v_pai_out = array_append(v_pai_out, format('', i_profile.src_prefix_out)::varchar); + v_bleg_append_headers_req = array_append(v_bleg_append_headers_req, format('P-Asserted-Identity: ', i_profile.src_prefix_out)::varchar); + ELSIF i_vendor_gw.pai_send_mode_id = 2 and i_vendor_gw.pai_domain is not null and i_vendor_gw.pai_domain!='' THEN + -- SIP URL + v_pai_out = array_append(v_pai_out, format('', i_profile.src_prefix_out, i_vendor_gw.pai_domain)::varchar); + v_bleg_append_headers_req = array_append(v_bleg_append_headers_req, format('P-Asserted-Identity: ', i_profile.src_prefix_out, i_vendor_gw.pai_domain)::varchar); + ELSIF i_vendor_gw.pai_send_mode_id = 3 and i_vendor_gw.pai_domain is not null and i_vendor_gw.pai_domain!='' THEN + v_pai_out = array_append(v_pai_out, format('', i_profile.src_prefix_out, i_vendor_gw.pai_domain)::varchar); + v_bleg_append_headers_req = array_append(v_bleg_append_headers_req, format('P-Asserted-Identity: ', i_profile.src_prefix_out, i_vendor_gw.pai_domain)::varchar); + ELSIF i_vendor_gw.pai_send_mode_id = 4 THEN + -- relay + FOREACH v_pai IN ARRAY i_pai LOOP + v_bleg_append_headers_req = array_append(v_bleg_append_headers_req, format('P-Asserted-Identity: %s', v_pai)::varchar); + END LOOP; + v_pai_out = i_pai; + IF i_ppi is not null and i_ppi!='' THEN + v_bleg_append_headers_req = array_append(v_bleg_append_headers_req, format('P-Preferred-Identity: %s', i_ppi)::varchar); + i_profile.ppi_out = i_ppi; + END IF; + END IF; + i_profile.pai_out = NULLIF(array_to_string(v_pai_out, ','),''); + END IF; + + IF i_vendor_gw.stir_shaken_mode_id = 1 AND COALESCE(i_profile.ss_attest_id,0) > 0 THEN + -- insert signature + i_profile.ss_crt_id = i_vendor_gw.stir_shaken_crt_id; + i_profile.ss_otn = i_profile.src_prefix_routing; + i_profile.ss_dtn = i_profile.dst_prefix_routing; + i_profile.legb_ss_status_id = i_profile.ss_attest_id; + END IF; + + v_bleg_append_headers_req = array_cat(v_bleg_append_headers_req, string_to_array(i_vendor_gw.term_append_headers_req,'\r\n')::varchar[]); + i_profile.append_headers_req = array_to_string(v_bleg_append_headers_req,'\r\n'); + + i_profile.aleg_append_headers_req=i_customer_gw.orig_append_headers_req; + + i_profile.next_hop_1st_req=i_vendor_gw.auth_enabled; -- use low delay dns srv if auth enabled + i_profile.next_hop:=i_vendor_gw.term_next_hop; + i_profile.aleg_next_hop:=i_customer_gw.orig_next_hop; + -- i_profile.next_hop_for_replies:=v_dst_gw.term_next_hop_for_replies; + + i_profile.dlg_nat_handling=i_customer_gw.dialog_nat_handling; + + i_profile.call_id:=''; -- Generation by sems + + i_profile.enable_auth:=i_vendor_gw.auth_enabled; + i_profile.auth_pwd:=i_vendor_gw.auth_password; + i_profile.auth_user:=i_vendor_gw.auth_user; + i_profile.enable_aleg_auth:=false; + i_profile.auth_aleg_pwd:=''; + i_profile.auth_aleg_user:=''; + + if i_profile.enable_auth then + v_from_user=COALESCE(i_vendor_gw.auth_from_user,i_profile.src_prefix_out,''); + -- may be it already defined by privacy logic + v_from_domain=COALESCE(v_from_domain, i_vendor_gw.auth_from_domain, '$Oi'); + else + v_from_user=COALESCE(i_profile.src_prefix_out,''); + if i_vendor_gw.preserve_anonymous_from_domain and i_profile.from_domain='anonymous.invalid' then + v_from_domain='anonymous.invalid'; + else + v_from_domain=COALESCE(v_from_domain, '$Oi'); + end if; + end if; + + v_to_username = yeti_ext.regexp_replace_rand(i_profile.dst_prefix_out, i_vendor_gw.to_rewrite_rule, i_vendor_gw.to_rewrite_result); + + if i_vendor_gw.sip_schema_id = 1 then + v_schema='sip'; + elsif i_vendor_gw.sip_schema_id = 2 then + v_schema='sips'; + elsif i_vendor_gw.sip_schema_id = 3 then + v_schema='sip'; + -- user=phone param require e.164 with + in username, but we are not forcing it + v_from_uri_params = array_append(v_from_uri_params,'user=phone'); + v_to_uri_params = array_append(v_to_uri_params,'user=phone'); + v_ruri_params = array_append(v_ruri_params,'user=phone'); + else + RAISE exception 'Unknown termination gateway % SIP schema %', i_vendor_gw.id, i_vendor_gw.sip_schema_id; + end if; + + if i_vendor_gw.send_lnp_information and i_profile.lrn is not null then + if i_profile.lrn=i_profile.dst_prefix_routing then -- number not ported, but request was successf we musr add ;npdi=yes; + v_ruri_user_params = array_append(v_ruri_user_params, 'npdi=yes'); + i_profile.lrn=nullif(i_profile.dst_prefix_routing,i_profile.lrn); -- clear lnr field if number not ported; + else -- if number ported + v_ruri_user_params = array_append(v_ruri_user_params, 'rn='||i_profile.lrn); + v_ruri_user_params = array_append(v_ruri_user_params, 'npdi=yes'); + end if; + end if; + + i_profile.registered_aor_mode_id = i_vendor_gw.registered_aor_mode_id; + if i_vendor_gw.registered_aor_mode_id > 0 then + i_profile.registered_aor_id=i_vendor_gw.id; + v_ruri_host = 'unknown.invalid'; + else + v_ruri_host = i_vendor_gw.host; + end if; + + i_profile."from" = switch21.build_uri(false, v_schema, i_profile.src_name_out, v_from_user, null, v_from_domain, null, v_from_uri_params); + + i_profile."to" = switch21.build_uri(false, v_schema, null, v_to_username, null, v_ruri_host, i_vendor_gw.port, v_to_uri_params); + i_profile.ruri = switch21.build_uri(true, v_schema, null, i_profile.dst_prefix_out, v_ruri_user_params, v_ruri_host, i_vendor_gw.port, v_ruri_params); + + i_profile.bleg_transport_protocol_id:=i_vendor_gw.transport_protocol_id; + i_profile.bleg_protocol_priority_id:=i_vendor_gw.network_protocol_priority_id; + + i_profile.aleg_media_encryption_mode_id:=i_customer_gw.media_encryption_mode_id; + i_profile.bleg_media_encryption_mode_id:=i_vendor_gw.media_encryption_mode_id; + + IF (i_vendor_gw.term_use_outbound_proxy ) THEN + i_profile.outbound_proxy:=v_schema||':'||i_vendor_gw.term_outbound_proxy; + i_profile.force_outbound_proxy:=i_vendor_gw.term_force_outbound_proxy; + i_profile.bleg_outbound_proxy_transport_protocol_id:=i_vendor_gw.term_proxy_transport_protocol_id; + ELSE + i_profile.outbound_proxy:=NULL; + i_profile.force_outbound_proxy:=false; + END IF; + + IF (i_customer_gw.orig_use_outbound_proxy ) THEN + i_profile.aleg_force_outbound_proxy:=i_customer_gw.orig_force_outbound_proxy; + i_profile.aleg_outbound_proxy=v_schema||':'||i_customer_gw.orig_outbound_proxy; + i_profile.aleg_outbound_proxy_transport_protocol_id:=i_customer_gw.orig_proxy_transport_protocol_id; + else + i_profile.aleg_force_outbound_proxy:=FALSE; + i_profile.aleg_outbound_proxy=NULL; + end if; + + i_profile.aleg_policy_id=i_customer_gw.orig_disconnect_policy_id; + i_profile.bleg_policy_id=i_vendor_gw.term_disconnect_policy_id; + + i_profile.transit_headers_a2b:=i_customer_gw.transit_headers_from_origination||';'||i_vendor_gw.transit_headers_from_origination; + i_profile.transit_headers_b2a:=i_vendor_gw.transit_headers_from_termination||';'||i_customer_gw.transit_headers_from_termination; + + i_profile.sdp_filter_type_id:=0; + i_profile.sdp_filter_list:=''; + + i_profile.sdp_alines_filter_type_id:=i_vendor_gw.sdp_alines_filter_type_id; + i_profile.sdp_alines_filter_list:=i_vendor_gw.sdp_alines_filter_list; + + i_profile.enable_session_timer=i_vendor_gw.sst_enabled; + i_profile.session_expires =i_vendor_gw.sst_session_expires; + i_profile.minimum_timer:=i_vendor_gw.sst_minimum_timer; + i_profile.maximum_timer:=i_vendor_gw.sst_maximum_timer; + i_profile.session_refresh_method_id:=i_vendor_gw.session_refresh_method_id; + i_profile.accept_501_reply:=i_vendor_gw.sst_accept501; + + i_profile.enable_aleg_session_timer=i_customer_gw.sst_enabled; + i_profile.aleg_session_expires:=i_customer_gw.sst_session_expires; + i_profile.aleg_minimum_timer:=i_customer_gw.sst_minimum_timer; + i_profile.aleg_maximum_timer:=i_customer_gw.sst_maximum_timer; + i_profile.aleg_session_refresh_method_id:=i_customer_gw.session_refresh_method_id; + i_profile.aleg_accept_501_reply:=i_customer_gw.sst_accept501; + + i_profile.reply_translations:=''; + i_profile.disconnect_code_id:=NULL; + i_profile.enable_rtprelay:=i_vendor_gw.proxy_media OR i_customer_gw.proxy_media; + + i_profile.rtprelay_interface:=i_vendor_gw.rtp_interface_name; + i_profile.aleg_rtprelay_interface:=i_customer_gw.rtp_interface_name; + + i_profile.outbound_interface:=i_vendor_gw.sip_interface_name; + i_profile.aleg_outbound_interface:=i_customer_gw.sip_interface_name; + + i_profile.bleg_force_symmetric_rtp:=i_vendor_gw.force_symmetric_rtp; + i_profile.bleg_symmetric_rtp_nonstop=i_vendor_gw.symmetric_rtp_nonstop; + + i_profile.aleg_force_symmetric_rtp:=i_customer_gw.force_symmetric_rtp; + i_profile.aleg_symmetric_rtp_nonstop=i_customer_gw.symmetric_rtp_nonstop; + + i_profile.bleg_rtp_ping=i_vendor_gw.rtp_ping; + i_profile.aleg_rtp_ping=i_customer_gw.rtp_ping; + + i_profile.bleg_relay_options = i_vendor_gw.relay_options; + i_profile.aleg_relay_options = i_customer_gw.relay_options; + + + i_profile.filter_noaudio_streams = i_vendor_gw.filter_noaudio_streams OR i_customer_gw.filter_noaudio_streams; + i_profile.force_one_way_early_media = i_vendor_gw.force_one_way_early_media OR i_customer_gw.force_one_way_early_media; + i_profile.aleg_relay_reinvite = i_vendor_gw.relay_reinvite; + i_profile.bleg_relay_reinvite = i_customer_gw.relay_reinvite; + + i_profile.aleg_relay_hold = i_vendor_gw.relay_hold; + i_profile.bleg_relay_hold = i_customer_gw.relay_hold; + + i_profile.aleg_relay_prack = i_vendor_gw.relay_prack; + i_profile.bleg_relay_prack = i_customer_gw.relay_prack; + i_profile.aleg_rel100_mode_id = i_customer_gw.rel100_mode_id; + i_profile.bleg_rel100_mode_id = i_vendor_gw.rel100_mode_id; + + i_profile.rtp_relay_timestamp_aligning=i_vendor_gw.rtp_relay_timestamp_aligning OR i_customer_gw.rtp_relay_timestamp_aligning; + i_profile.allow_1xx_wo2tag=i_vendor_gw.allow_1xx_without_to_tag OR i_customer_gw.allow_1xx_without_to_tag; + + i_profile.aleg_sdp_c_location_id=i_customer_gw.sdp_c_location_id; + i_profile.bleg_sdp_c_location_id=i_vendor_gw.sdp_c_location_id; + i_profile.trusted_hdrs_gw=false; + + + + i_profile.aleg_codecs_group_id:=i_customer_gw.codec_group_id; + i_profile.bleg_codecs_group_id:=i_vendor_gw.codec_group_id; + i_profile.aleg_single_codec_in_200ok:=i_customer_gw.single_codec_in_200ok; + i_profile.bleg_single_codec_in_200ok:=i_vendor_gw.single_codec_in_200ok; + i_profile.try_avoid_transcoding = i_customer_gw.try_avoid_transcoding; + i_profile.ringing_timeout=i_vendor_gw.ringing_timeout; + i_profile.dead_rtp_time=GREATEST(i_vendor_gw.rtp_timeout,i_customer_gw.rtp_timeout); + i_profile.invite_timeout=i_vendor_gw.sip_timer_b; + i_profile.srv_failover_timeout=i_vendor_gw.dns_srv_failover_timer; + i_profile.fake_180_timer=i_vendor_gw.fake_180_timer; + i_profile.rtp_force_relay_cn=i_vendor_gw.rtp_force_relay_cn OR i_customer_gw.rtp_force_relay_cn; + i_profile.patch_ruri_next_hop=i_vendor_gw.resolve_ruri; + + i_profile.aleg_sensor_id=i_customer_gw.sensor_id; + i_profile.aleg_sensor_level_id=i_customer_gw.sensor_level_id; + i_profile.bleg_sensor_id=i_vendor_gw.sensor_id; + i_profile.bleg_sensor_level_id=i_vendor_gw.sensor_level_id; + + i_profile.aleg_dtmf_send_mode_id=i_customer_gw.dtmf_send_mode_id; + i_profile.aleg_dtmf_recv_modes=i_customer_gw.dtmf_receive_mode_id; + i_profile.bleg_dtmf_send_mode_id=i_vendor_gw.dtmf_send_mode_id; + i_profile.bleg_dtmf_recv_modes=i_vendor_gw.dtmf_receive_mode_id; + + + i_profile.aleg_rtp_filter_inband_dtmf=false; + i_profile.bleg_rtp_filter_inband_dtmf=false; + + if i_customer_gw.rx_inband_dtmf_filtering_mode_id=3 then -- enable filtering + i_profile.aleg_rtp_filter_inband_dtmf=true; + elsif i_customer_gw.rx_inband_dtmf_filtering_mode_id=1 then -- inherit + if i_vendor_gw.tx_inband_dtmf_filtering_mode_id in (1,2) then -- inherit or disable filtering + i_profile.aleg_rtp_filter_inband_dtmf=false; + elsif i_vendor_gw.tx_inband_dtmf_filtering_mode_id = 3 then -- enable filtering + i_profile.aleg_rtp_filter_inband_dtmf=true; + end if; + end if; + + + if i_vendor_gw.rx_inband_dtmf_filtering_mode_id=3 then -- enable filtering + i_profile.bleg_rtp_filter_inband_dtmf=true; + elsif i_vendor_gw.rx_inband_dtmf_filtering_mode_id=1 then -- inherit + if i_customer_gw.tx_inband_dtmf_filtering_mode_id in (1,2) then -- inherit or disable filtering + i_profile.bleg_rtp_filter_inband_dtmf=false; + elsif i_customer_gw.tx_inband_dtmf_filtering_mode_id = 3 then -- enable filtering + i_profile.bleg_rtp_filter_inband_dtmf=true; + end if; + end if; + + i_profile.aleg_rtp_acl = i_customer_gw.rtp_acl; + i_profile.bleg_rtp_acl = i_vendor_gw.rtp_acl; + + i_profile.rtprelay_force_dtmf_relay=i_vendor_gw.force_dtmf_relay; + i_profile.rtprelay_dtmf_detection=NOT i_vendor_gw.force_dtmf_relay; + i_profile.rtprelay_dtmf_filtering=NOT i_vendor_gw.force_dtmf_relay; + i_profile.bleg_max_30x_redirects = i_vendor_gw.max_30x_redirects; + i_profile.bleg_max_transfers = i_vendor_gw.max_transfers; + + + i_profile.aleg_relay_update=i_customer_gw.relay_update; + i_profile.bleg_relay_update=i_vendor_gw.relay_update; + i_profile.suppress_early_media=i_customer_gw.suppress_early_media OR i_vendor_gw.suppress_early_media; + + i_profile.bleg_radius_acc_profile_id=i_vendor_gw.radius_accounting_profile_id; + i_profile.bleg_force_cancel_routeset=i_vendor_gw.force_cancel_routeset; + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DP. Finished: % ',EXTRACT(MILLISECOND from v_end-v_start),row_to_json(i_profile,true); + /*}dbg*/ + RETURN i_profile; +END; +$_$; + + set search_path TO switch21; + SELECT * from switch21.preprocess_all(); + set search_path TO gui, public, switch, billing, class4, runtime_stats, sys, logs, data_import; + + } + end + + def down + execute %q{ + drop table billing.package_counters; + alter table class4.destinations drop column allow_package_billing; + + alter type switch21.callprofile_ty drop attribute package_counter_id; + delete from switch21.switch_interface_out where id = 1047; + +CREATE or replace FUNCTION switch21.route( +i_node_id integer, +i_pop_id integer, +i_protocol_id smallint, +i_remote_ip inet, +i_remote_port integer, +i_local_ip inet, +i_local_port integer, +i_from_dsp character varying, +i_from_name character varying, +i_from_domain character varying, +i_from_port integer, +i_to_name character varying, +i_to_domain character varying, +i_to_port integer, +i_contact_name character varying, +i_contact_domain character varying, +i_contact_port integer, +i_uri_name character varying, +i_uri_domain character varying, +i_auth_id integer, +i_identity_data json, +i_interface character varying, +i_x_yeti_auth character varying, +i_diversion character varying, +i_x_orig_ip inet, +i_x_orig_port integer, +i_x_orig_protocol_id smallint, +i_pai character varying, +i_ppi character varying, +i_privacy character varying, +i_rpid character varying, +i_rpid_privacy character varying +) RETURNS SETOF switch21.callprofile_ty + LANGUAGE plpgsql SECURITY DEFINER ROWS 10 + AS $_$ + DECLARE + v_ret switch21.callprofile_ty; + i integer; + v_ip inet; + v_remote_ip inet; + v_remote_port INTEGER; + v_transport_protocol_id smallint; + v_customer_auth_normalized class4.customers_auth_normalized; + v_destination class4.destinations%rowtype; + v_dialpeer record; + v_rateplan class4.rateplans%rowtype; + v_dst_gw class4.gateways%rowtype; + v_orig_gw class4.gateways%rowtype; + v_rp class4.routing_plans%rowtype; + v_customer_allowtime real; + v_vendor_allowtime real; + v_sorting_id integer; + v_customer_acc integer; + v_route_found boolean:=false; + v_c_acc billing.accounts%rowtype; + v_v_acc billing.accounts%rowtype; + v_network sys.network_prefixes%rowtype; + v_src_network sys.network_prefixes%rowtype; + routedata record; + /*dbg{*/ + v_start timestamp; + v_end timestamp; + /*}dbg*/ + v_rate NUMERIC; + v_now timestamp; + v_x_yeti_auth varchar; + -- v_uri_domain varchar; + v_rate_limit float:='Infinity'::float; + v_destination_rate_limit float:='Infinity'::float; + v_test_vendor_id integer; + v_random float; + v_max_call_length integer; + v_routing_key varchar; + v_lnp_key varchar; + v_lnp_rule class4.routing_plan_lnp_rules%rowtype; + v_numberlist record; + v_numberlist_item record; + v_call_tags smallint[]:='{}'::smallint[]; + v_area_direction class4.routing_tag_detection_rules%rowtype; + v_numberlist_size integer; + v_lua_context switch21.lua_call_context; + v_identity_data switch21.identity_data_ty[]; + v_pai varchar[]; + v_ppi varchar; + v_privacy varchar[]; + v_diversion varchar[] not null default ARRAY[]::varchar[]; + v_cnam_req_json json; + v_cnam_resp_json json; + v_cnam_lua_resp switch21.cnam_lua_resp; + v_cnam_database class4.cnam_databases%rowtype; + v_rewrite switch21.defered_rewrite; + v_defered_src_rewrites switch21.defered_rewrite[] not null default ARRAY[]::switch21.defered_rewrite[]; + v_defered_dst_rewrites switch21.defered_rewrite[] not null default ARRAY[]::switch21.defered_rewrite[]; + v_rate_groups integer[]; + v_routing_groups integer[]; + BEGIN + /*dbg{*/ + v_start:=now(); + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> Execution start',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + + perform id from sys.load_balancers where signalling_ip=host(i_remote_ip)::varchar; + IF FOUND and i_x_orig_ip IS not NULL AND i_x_orig_port IS not NULL THEN + v_remote_ip:=i_x_orig_ip; + v_remote_port:=i_x_orig_port; + v_transport_protocol_id=i_x_orig_protocol_id; + /*dbg{*/RAISE NOTICE '% ms -> Got originator address "%:%, proto: %" from x-headers',EXTRACT(MILLISECOND from v_end-v_start), v_remote_ip,v_remote_port, v_transport_protocol_id;/*}dbg*/ + else + v_remote_ip:=i_remote_ip; + v_remote_port:=i_remote_port; + v_transport_protocol_id:=i_protocol_id; + /*dbg{*/RAISE NOTICE '% ms -> Got originator address "%:%, proto: %" from switch leg info',EXTRACT(MILLISECOND from v_end-v_start), v_remote_ip,v_remote_port, v_transport_protocol_id;/*}dbg*/ + end if; + + v_now:=now(); + v_ret:=switch21.new_profile(); + + v_ret.diversion_in:=i_diversion; + + v_ret.auth_orig_protocol_id =v_transport_protocol_id; + v_ret.auth_orig_ip = v_remote_ip; + v_ret.auth_orig_port = v_remote_port; + + v_ret.src_name_in:=i_from_dsp; + v_ret.src_name_out:=v_ret.src_name_in; + + v_ret.src_prefix_in:=i_from_name; + v_ret.src_prefix_out:=v_ret.src_prefix_in; + + v_ret.dst_prefix_in:=i_uri_name; + v_ret.dst_prefix_out:=v_ret.dst_prefix_in; + + + v_ret.ruri_domain=i_uri_domain; + v_ret.from_domain=i_from_domain; + v_ret.to_domain=i_to_domain; + + v_ret.pai_in=i_pai; + v_pai=string_to_array(COALESCE(i_pai,''),','); + v_ret.ppi_in=i_ppi; + v_ppi=i_ppi; + v_ret.privacy_in=i_privacy; + v_privacy = string_to_array(COALESCE(i_privacy,''),';'); + v_ret.rpid_in=i_rpid; + v_ret.rpid_privacy_in=i_rpid_privacy; + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> AUTH. lookup started',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_x_yeti_auth:=COALESCE(i_x_yeti_auth,''); + -- v_uri_domain:=COALESCE(i_uri_domain,''); + + if i_auth_id is null then + SELECT into v_customer_auth_normalized ca.* + from class4.customers_auth_normalized ca + JOIN public.contractors c ON c.id=ca.customer_id + WHERE ca.enabled AND + ca.ip>>=v_remote_ip AND + prefix_range(ca.dst_prefix)@>prefix_range(v_ret.dst_prefix_in) AND + prefix_range(ca.src_prefix)@>prefix_range(v_ret.src_prefix_in) AND + (ca.pop_id=i_pop_id or ca.pop_id is null) and + COALESCE(ca.x_yeti_auth,'')=v_x_yeti_auth AND + COALESCE(nullif(ca.uri_domain,'')=i_uri_domain,true) AND + COALESCE(nullif(ca.to_domain,'')=i_to_domain,true) AND + COALESCE(nullif(ca.from_domain,'')=i_from_domain,true) AND + (ca.interface is null or ca.interface = i_interface ) AND + (ca.transport_protocol_id is null or ca.transport_protocol_id=v_transport_protocol_id) AND + length(v_ret.dst_prefix_in) between ca.dst_number_min_length and ca.dst_number_max_length and + length(v_ret.src_prefix_in) between ca.src_number_min_length and ca.src_number_max_length and + c.enabled and c.customer + ORDER BY + masklen(ca.ip) DESC, + ca.transport_protocol_id is null, + length(prefix_range(ca.dst_prefix)) DESC, + length(prefix_range(ca.src_prefix)) DESC, + ca.pop_id is null, + ca.uri_domain is null, + ca.to_domain is null, + ca.from_domain is null, + ca.require_incoming_auth + LIMIT 1; + IF NOT FOUND THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> AUTH. disconnection with 110.Cant find customer or customer locked',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_ret.disconnect_code_id=110; --Cant find customer or customer locked + RETURN NEXT v_ret; + RETURN; + END IF; + if v_customer_auth_normalized.require_incoming_auth then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> AUTH. Incoming auth required. Respond 401',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_ret.aleg_auth_required=true; + RETURN NEXT v_ret; + RETURN; + end IF; + if v_customer_auth_normalized.reject_calls then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> AUTH. disconnection with 8004. Reject by customers auth',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_ret.disconnect_code_id=8004; -- call rejected by authorization + + v_ret.customer_auth_id:=v_customer_auth_normalized.customers_auth_id; + v_ret.customer_auth_external_id:=v_customer_auth_normalized.external_id; + v_ret.customer_auth_external_type:=v_customer_auth_normalized.external_type; + + v_ret.customer_id:=v_customer_auth_normalized.customer_id; + select into strict v_ret.customer_external_id external_id from public.contractors where id=v_ret.customer_id; + + v_ret.rateplan_id:=v_customer_auth_normalized.rateplan_id; + v_ret.routing_plan_id:=v_customer_auth_normalized.routing_plan_id; + + v_ret.customer_acc_id:=v_customer_auth_normalized.account_id; + SELECT INTO STRICT v_ret.customer_acc_external_id external_id FROM billing.accounts WHERE id=v_customer_auth_normalized.account_id; + + RETURN NEXT v_ret; + RETURN; + end if; + else + SELECT into v_customer_auth_normalized ca.* + from class4.customers_auth_normalized ca + JOIN public.contractors c ON c.id=ca.customer_id + WHERE ca.enabled AND + ca.ip>>=v_remote_ip AND + prefix_range(ca.dst_prefix)@>prefix_range(v_ret.dst_prefix_in) AND + prefix_range(ca.src_prefix)@>prefix_range(v_ret.src_prefix_in) AND + (ca.pop_id=i_pop_id or ca.pop_id is null) and + COALESCE(ca.x_yeti_auth,'')=v_x_yeti_auth AND + COALESCE(nullif(ca.uri_domain,'')=i_uri_domain,true) AND + COALESCE(nullif(ca.to_domain,'')=i_to_domain,true) AND + COALESCE(nullif(ca.from_domain,'')=i_from_domain,true) AND + (ca.interface is null or ca.interface = i_interface ) AND + (ca.transport_protocol_id is null or ca.transport_protocol_id=v_transport_protocol_id) AND + length(v_ret.dst_prefix_in) between ca.dst_number_min_length and ca.dst_number_max_length and + length(v_ret.src_prefix_in) between ca.src_number_min_length and ca.src_number_max_length and + c.enabled and c.customer and + ca.require_incoming_auth and gateway_id = i_auth_id + ORDER BY + masklen(ca.ip) DESC, + ca.transport_protocol_id is null, + length(prefix_range(ca.dst_prefix)) DESC, + length(prefix_range(ca.src_prefix)) DESC, + ca.pop_id is null, + ca.uri_domain is null, + ca.to_domain is null, + ca.from_domain is null + LIMIT 1; + IF NOT FOUND THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> AUTH. disconnection with 110.Cant find customer or customer locked',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_ret.disconnect_code_id=110; --Cant find customer or customer locked + RETURN NEXT v_ret; + RETURN; + END IF; + if v_customer_auth_normalized.reject_calls then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> AUTH. disconnection with 8004. Reject by customers auth',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_ret.disconnect_code_id=8004; -- call rejected by authorization + + v_ret.customer_auth_id:=v_customer_auth_normalized.customers_auth_id; + v_ret.customer_auth_external_id:=v_customer_auth_normalized.external_id; + v_ret.customer_auth_external_type:=v_customer_auth_normalized.external_type; + + v_ret.customer_id:=v_customer_auth_normalized.customer_id; + select into strict v_ret.customer_external_id external_id from public.contractors where id=v_ret.customer_id; + + v_ret.rateplan_id:=v_customer_auth_normalized.rateplan_id; + v_ret.routing_plan_id:=v_customer_auth_normalized.routing_plan_id; + + v_ret.customer_acc_id:=v_customer_auth_normalized.account_id; + SELECT INTO STRICT v_ret.customer_acc_external_id external_id FROM billing.accounts WHERE id=v_customer_auth_normalized.account_id; + + RETURN NEXT v_ret; + RETURN; + end if; + end IF; + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> AUTH. found: %',EXTRACT(MILLISECOND from v_end-v_start),row_to_json(v_customer_auth_normalized, true); + /*}dbg*/ + + -- redefine call SRC/DST numbers + + IF v_customer_auth_normalized.src_name_field_id=1 THEN /* default - from uri display name */ + v_ret.src_name_in:=i_from_dsp; + END IF; + v_ret.src_name_out:=v_ret.src_name_in; + + IF v_customer_auth_normalized.src_number_field_id=1 THEN /* default - from uri userpart */ + v_ret.src_prefix_in:=i_from_name; + ELSIF v_customer_auth_normalized.src_number_field_id=2 THEN /* From uri Display name */ + v_ret.src_prefix_in:=i_from_dsp; + END IF; + v_ret.src_prefix_out:=v_ret.src_prefix_in; + + IF v_customer_auth_normalized.dst_number_field_id=1 THEN /* default - RURI userpart*/ + v_ret.dst_prefix_in:=i_uri_name; + ELSIF v_customer_auth_normalized.dst_number_field_id=2 THEN /* TO URI userpart */ + v_ret.dst_prefix_in:=i_to_name; + ELSIF v_customer_auth_normalized.dst_number_field_id=3 THEN /* Top-Most Diversion header userpart */ + v_ret.dst_prefix_in:=COALESCE(i_diversion,''); + END IF; + v_ret.dst_prefix_out:=v_ret.dst_prefix_in; + + IF v_customer_auth_normalized.diversion_policy_id = 2 THEN /* accept diversion */ + v_diversion = string_to_array(COALESCE(i_diversion,''),','); + v_diversion = yeti_ext.regexp_replace_rand( + v_diversion, + v_customer_auth_normalized.diversion_rewrite_rule, + v_customer_auth_normalized.diversion_rewrite_result + ); + END IF; + + select into v_identity_data array_agg(d) from json_populate_recordset(null::switch21.identity_data_ty, i_identity_data) d; + IF v_customer_auth_normalized.rewrite_ss_status_id IS NOT NULL THEN + v_ret.ss_attest_id = v_customer_auth_normalized.rewrite_ss_status_id; + END IF; + + -- feel customer data ;-) + v_ret.dump_level_id:=v_customer_auth_normalized.dump_level_id; + v_ret.customer_auth_id:=v_customer_auth_normalized.customers_auth_id; + v_ret.customer_auth_external_id:=v_customer_auth_normalized.external_id; + v_ret.customer_auth_external_type:=v_customer_auth_normalized.external_type; + + v_ret.customer_id:=v_customer_auth_normalized.customer_id; + select into strict v_ret.customer_external_id external_id from public.contractors where id=v_customer_auth_normalized.customer_id; + + v_ret.rateplan_id:=v_customer_auth_normalized.rateplan_id; + v_ret.routing_plan_id:=v_customer_auth_normalized.routing_plan_id; + v_ret.customer_acc_id:=v_customer_auth_normalized.account_id; + + v_ret.orig_gw_id:=v_customer_auth_normalized.gateway_id; + SELECT into v_orig_gw * from class4.gateways WHERE id=v_customer_auth_normalized.gateway_id; + -- we have to set disconnect policy to allow rewrite internal reject when call rejected before gw processing + v_ret.aleg_policy_id = v_orig_gw.orig_disconnect_policy_id; + + if not v_orig_gw.enabled then + v_ret.disconnect_code_id=8005; -- Origination gateway is disabled + RETURN NEXT v_ret; + RETURN; + end if; + + CASE v_customer_auth_normalized.privacy_mode_id + WHEN 1 THEN + -- allow all + WHEN 2 THEN + IF cardinality(array_remove(v_privacy,'none')) > 0 THEN + v_ret.disconnect_code_id = 8013; + RETURN NEXT v_ret; + RETURN; + END IF; + WHEN 3 THEN + IF 'critical' = ANY(v_privacy) THEN + v_ret.disconnect_code_id = 8014; + RETURN NEXT v_ret; + RETURN; + END IF; + WHEN 4 THEN + IF lower(v_ret.src_prefix_in)='anonymous' AND COALESCE(cardinality(v_pai),0) = 0 AND ( v_ppi is null or v_ppi='') THEN + v_ret.disconnect_code_id = 8015; + RETURN NEXT v_ret; + RETURN; + END IF; + END CASE; + + v_ret.radius_auth_profile_id=v_customer_auth_normalized.radius_auth_profile_id; + v_ret.aleg_radius_acc_profile_id=v_customer_auth_normalized.radius_accounting_profile_id; + v_ret.record_audio=v_customer_auth_normalized.enable_audio_recording; + + v_ret.customer_acc_check_balance=v_customer_auth_normalized.check_account_balance; + + SELECT INTO STRICT v_c_acc * FROM billing.accounts WHERE id=v_customer_auth_normalized.account_id; + v_ret.customer_acc_external_id=v_c_acc.external_id; + v_ret.customer_acc_vat=v_c_acc.vat; + v_destination_rate_limit=coalesce(v_c_acc.destination_rate_limit::float,'Infinity'::float); + + select into v_max_call_length max_call_duration from sys.guiconfig limit 1; + + if NOT v_customer_auth_normalized.check_account_balance then + v_ret.time_limit = LEAST(v_max_call_length, v_c_acc.max_call_duration); + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> AUTH. No customer acc balance checking. customer time limit set to max value: % ',EXTRACT(MILLISECOND from v_end-v_start), v_ret.time_limit; + /*}dbg*/ + elsif v_customer_auth_normalized.check_account_balance AND v_c_acc.balance<=v_c_acc.min_balance then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> AUTH. Customer acc balance checking. Call blocked before routing',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_ret.disconnect_code_id=8000; --No enough customer balance + RETURN NEXT v_ret; + RETURN; + end if; + + v_ret.customer_acc_external_id=v_c_acc.external_id; + v_ret.customer_acc_vat=v_c_acc.vat; + + v_ret.lega_res=''; + if v_customer_auth_normalized.capacity is not null then + v_ret.lega_res='3:'||v_customer_auth_normalized.customers_auth_id||':'||v_customer_auth_normalized.capacity::varchar||':1;'; + end if; + + if v_c_acc.origination_capacity is not null then + v_ret.lega_res:=v_ret.lega_res||'1:'||v_c_acc.id::varchar||':'||v_c_acc.origination_capacity::varchar||':1;'; + end if; + + if v_c_acc.total_capacity is not null then + v_ret.lega_res:=v_ret.lega_res||'7:'||v_c_acc.id::varchar||':'||v_c_acc.total_capacity::varchar||':1;'; + end if; + + if v_orig_gw.origination_capacity is not null then + v_ret.lega_res:=v_ret.lega_res||'4:'||v_orig_gw.id::varchar||':'||v_orig_gw.origination_capacity::varchar||':1;'; + end if; + + if v_customer_auth_normalized.cps_limit is not null then + if not yeti_ext.tbf_rate_check(1::integer,v_customer_auth_normalized.customers_auth_id::bigint, v_customer_auth_normalized.cps_limit::real) then + v_ret.disconnect_code_id=8012; -- CPS limit on customer auth + RETURN NEXT v_ret; + RETURN; + end if; + end if; + + -- Tag processing CA + v_call_tags=yeti_ext.tag_action(v_customer_auth_normalized.tag_action_id, v_call_tags, v_customer_auth_normalized.tag_action_value); + + /* + number rewriting _Before_ routing + */ + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> AUTH. Before rewrite src_prefix: % , dst_prefix: %',EXTRACT(MILLISECOND from v_end-v_start),v_ret.src_prefix_out,v_ret.dst_prefix_out; + /*}dbg*/ + v_ret.dst_prefix_out=yeti_ext.regexp_replace_rand(v_ret.dst_prefix_out,v_customer_auth_normalized.dst_rewrite_rule,v_customer_auth_normalized.dst_rewrite_result); + v_ret.src_prefix_out=yeti_ext.regexp_replace_rand(v_ret.src_prefix_out,v_customer_auth_normalized.src_rewrite_rule,v_customer_auth_normalized.src_rewrite_result); + v_ret.src_name_out=yeti_ext.regexp_replace_rand(v_ret.src_name_out,v_customer_auth_normalized.src_name_rewrite_rule,v_customer_auth_normalized.src_name_rewrite_result, true); + + -- if v_ret.radius_auth_profile_id is not null then + v_ret.src_number_radius:=i_from_name; + v_ret.dst_number_radius:=i_uri_name; + v_ret.src_number_radius=yeti_ext.regexp_replace_rand( + v_ret.src_number_radius, + v_customer_auth_normalized.src_number_radius_rewrite_rule, + v_customer_auth_normalized.src_number_radius_rewrite_result + ); + + v_ret.dst_number_radius=yeti_ext.regexp_replace_rand( + v_ret.dst_number_radius, + v_customer_auth_normalized.dst_number_radius_rewrite_rule, + v_customer_auth_normalized.dst_number_radius_rewrite_result + ); + v_ret.customer_auth_name=v_customer_auth_normalized."name"; + v_ret.customer_name=(select "name" from public.contractors where id=v_customer_auth_normalized.customer_id limit 1); + -- end if; +/** + if v_customer_auth_normalized.lua_script_id is not null then + v_lua_context.src_name_in = v_ret.src_name_in; + v_lua_context.src_number_in = v_ret.src_prefix_in; + v_lua_context.dst_number_in = v_ret.dst_prefix_in; + v_lua_context.src_name_out = v_ret.src_name_out; + v_lua_context.src_number_out = v_ret.src_prefix_out; + v_lua_context.dst_number_out = v_ret.dst_prefix_out; + -- v_lua_context.src_name_routing + -- v_lua_context.src_number_routing + -- v_lua_context.dst_number_routing + -- #arrays + -- v_lua_context.diversion_in + -- v_lua_context.diversion_routing + -- v_lua_context.diversion_out + select into v_lua_context switch21.lua_exec(v_customer_auth_normalized.lua_script_id, v_lua_context); + v_ret.src_name_out = v_lua_context.src_name_out; + v_ret.src_prefix_out = v_lua_context.src_number_out; + v_ret.dst_prefix_out = v_lua_context.dst_number_out; + end if; +**/ + if v_customer_auth_normalized.cnam_database_id is not null then + select into v_cnam_database * from class4.cnam_databases where id=v_customer_auth_normalized.cnam_database_id; + + select into v_cnam_req_json * from switch21.cnam_lua_build_request(v_cnam_database.request_lua, row_to_json(v_ret)::text); + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> CNAM. Lua generated request: %',EXTRACT(MILLISECOND from v_end-v_start),v_cnam_req_json; + /*}dbg*/ + + select into v_cnam_resp_json yeti_ext.lnp_resolve_cnam(v_cnam_database.id, v_cnam_req_json); + + /*dbg{*/ + v_end=clock_timestamp(); + RAISE NOTICE '% ms -> CNAM. resolver response: %',EXTRACT(MILLISECOND from v_end-v_start),v_cnam_resp_json; + /*}dbg*/ + + if json_extract_path_text(v_cnam_resp_json,'error') is not null then + /*dbg{*/ + v_end=clock_timestamp(); + RAISE NOTICE '% ms -> CNAM. error',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + if v_cnam_database.drop_call_on_error then + v_ret.disconnect_code_id=8009; -- CNAM Error + RETURN NEXT v_ret; + RETURN; + end if; + else + select into v_cnam_lua_resp * from switch21.cnam_lua_response_exec(v_cnam_database.response_lua, json_extract_path_text(v_cnam_resp_json,'response')); + + /*dbg{*/ + v_end=clock_timestamp(); + RAISE NOTICE '% ms -> CNAM. Lua parsed response: %',EXTRACT(MILLISECOND from v_end-v_start),row_to_json(v_cnam_lua_resp); + /*}dbg*/ + if v_cnam_lua_resp.metadata is not null then + v_ret.metadata = json_build_object('cnam_resp', v_cnam_lua_resp.metadata::json)::varchar; + end if; + v_ret.src_name_out = coalesce(v_cnam_lua_resp.src_name,v_ret.src_name_out); + v_ret.src_prefix_out = coalesce(v_cnam_lua_resp.src_number,v_ret.src_prefix_out); + v_ret.dst_prefix_out = coalesce(v_cnam_lua_resp.dst_number,v_ret.dst_prefix_out); + v_ret.pai_out = coalesce(v_cnam_lua_resp.pai,v_ret.pai_out); + v_ret.ppi_out = coalesce(v_cnam_lua_resp.ppi,v_ret.ppi_out); + v_call_tags = coalesce(v_cnam_lua_resp.routing_tag_ids,v_call_tags); + end if; + + end if; + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> AUTH. After rewrite src_prefix: % , dst_prefix: %',EXTRACT(MILLISECOND from v_end-v_start),v_ret.src_prefix_out,v_ret.dst_prefix_out; + /*}dbg*/ + + ----- Numberlist processing------------------------------------------------------------------------------------------------------- + if v_customer_auth_normalized.dst_numberlist_id is not null then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DST Numberlist processing. Lookup by key: %',EXTRACT(MILLISECOND from v_end-v_start), v_ret.dst_prefix_out; + /*}dbg*/ + + v_numberlist_item=switch21.match_numberlist(v_customer_auth_normalized.dst_numberlist_id, v_ret.dst_prefix_out); + select into v_numberlist * from class4.numberlists where id=v_customer_auth_normalized.dst_numberlist_id; + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DST Numberlist. key found: %',EXTRACT(MILLISECOND from v_end-v_start), row_to_json(v_numberlist_item); + /*}dbg*/ + IF v_numberlist_item.action_id is not null and v_numberlist_item.action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DST Numberlist. Drop by key action. Key: %',EXTRACT(MILLISECOND from v_end-v_start), v_numberlist_item.key; + /*}dbg*/ + v_ret.disconnect_code_id=8001; --destination blacklisted + RETURN NEXT v_ret; + RETURN; + elsif v_numberlist_item.action_id is not null and v_numberlist_item.action_id=2 then + IF v_numberlist_item.defer_src_rewrite THEN + v_defered_src_rewrites = array_append( + v_defered_src_rewrites, + (v_numberlist_item.src_rewrite_rule, v_numberlist_item.src_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.src_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.src_prefix_out, + v_numberlist_item.src_rewrite_rule, + v_numberlist_item.src_rewrite_result + ); + END IF; + IF v_numberlist_item.defer_dst_rewrite THEN + v_defered_dst_rewrites = array_append( + v_defered_dst_rewrites, + (v_numberlist_item.dst_rewrite_rule, v_numberlist_item.dst_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.dst_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.dst_prefix_out, + v_numberlist_item.dst_rewrite_rule, + v_numberlist_item.dst_rewrite_result + ); + END IF; + v_call_tags=yeti_ext.tag_action(v_numberlist_item.tag_action_id, v_call_tags, v_numberlist_item.tag_action_value); + -- pass call NOP. + elsif v_numberlist_item.action_id is null and v_numberlist.default_action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DST Numberlist. Drop by default action',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + -- drop by default + v_ret.disconnect_code_id=8001; --destination blacklisted + RETURN NEXT v_ret; + RETURN; + elsif v_numberlist_item.action_id is null and v_numberlist.default_action_id=2 then + IF v_numberlist.defer_src_rewrite THEN + v_defered_src_rewrites = array_append( + v_defered_src_rewrites, + (v_numberlist.default_src_rewrite_rule, v_numberlist.default_src_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.src_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.src_prefix_out, + v_numberlist.default_src_rewrite_rule, + v_numberlist.default_src_rewrite_result + ); + END IF; + IF v_numberlist.defer_dst_rewrite THEN + v_defered_dst_rewrites = array_append( + v_defered_dst_rewrites, + (v_numberlist.default_dst_rewrite_rule, v_numberlist.default_dst_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.dst_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.dst_prefix_out, + v_numberlist.default_dst_rewrite_rule, + v_numberlist.default_dst_rewrite_result + ); + END IF; + v_call_tags=yeti_ext.tag_action(v_numberlist.tag_action_id, v_call_tags, v_numberlist.tag_action_value); + -- pass by default + end if; + end if; + + if v_customer_auth_normalized.src_numberlist_id is not null then + + if v_customer_auth_normalized.src_numberlist_use_diversion AND v_diversion[1] is not null then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> SRC Numberlist processing. Lookup by key %, fallback to %', EXTRACT(MILLISECOND from v_end-v_start), v_ret.src_prefix_out, v_diversion[1]; + /*}dbg*/ + v_numberlist_item=switch21.match_numberlist(v_customer_auth_normalized.src_numberlist_id, v_ret.src_prefix_out, v_diversion[1]); + else + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> SRC Numberlist processing. Lookup by key %, no fallback', EXTRACT(MILLISECOND from v_end-v_start), v_ret.src_prefix_out; + /*}dbg*/ + v_numberlist_item=switch21.match_numberlist(v_customer_auth_normalized.src_numberlist_id, v_ret.src_prefix_out); + end if; + + select into v_numberlist * from class4.numberlists where id=v_customer_auth_normalized.src_numberlist_id; + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> SRC Numberlist. key found: %',EXTRACT(MILLISECOND from v_end-v_start), row_to_json(v_numberlist_item); + /*}dbg*/ + IF v_numberlist_item.action_id is not null and v_numberlist_item.action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> SRC Numberlist. Drop by key action. Key: %',EXTRACT(MILLISECOND from v_end-v_start), v_numberlist_item.key; + /*}dbg*/ + v_ret.disconnect_code_id=8002; --source blacklisted + RETURN NEXT v_ret; + RETURN; + elsif v_numberlist_item.action_id is not null and v_numberlist_item.action_id=2 then + IF v_numberlist_item.defer_src_rewrite THEN + v_defered_src_rewrites = array_append( + v_defered_src_rewrites, + (v_numberlist_item.src_rewrite_rule, v_numberlist_item.src_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.src_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.src_prefix_out, + v_numberlist_item.src_rewrite_rule, + v_numberlist_item.src_rewrite_result + ); + END IF; + IF v_numberlist_item.defer_dst_rewrite THEN + v_defered_dst_rewrites = array_append( + v_defered_dst_rewrites, + (v_numberlist_item.dst_rewrite_rule, v_numberlist_item.dst_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.dst_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.dst_prefix_out, + v_numberlist_item.dst_rewrite_rule, + v_numberlist_item.dst_rewrite_result + ); + END IF; + v_call_tags=yeti_ext.tag_action(v_numberlist_item.tag_action_id, v_call_tags, v_numberlist_item.tag_action_value); + -- pass call NOP. + elsif v_numberlist_item.action_id is null and v_numberlist.default_action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> SRC Numberlist. Drop by default action',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_ret.disconnect_code_id=8002; --source blacklisted + RETURN NEXT v_ret; + RETURN; + elsif v_numberlist_item.action_id is null and v_numberlist.default_action_id=2 then + IF v_numberlist.defer_src_rewrite THEN + v_defered_src_rewrites = array_append( + v_defered_src_rewrites, + (v_numberlist.default_src_rewrite_rule, v_numberlist.default_src_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.src_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.src_prefix_out, + v_numberlist.default_src_rewrite_rule, + v_numberlist.default_src_rewrite_result + ); + END IF; + IF v_numberlist.defer_dst_rewrite THEN + v_defered_dst_rewrites = array_append( + v_defered_dst_rewrites, + (v_numberlist.default_dst_rewrite_rule, v_numberlist.default_dst_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.dst_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.dst_prefix_out, + v_numberlist.default_dst_rewrite_rule, + v_numberlist.default_dst_rewrite_result + ); + END IF; + v_call_tags=yeti_ext.tag_action(v_numberlist.tag_action_id, v_call_tags, v_numberlist.tag_action_value); + -- pass by default + end if; + end if; + + SELECT INTO v_rp * from class4.routing_plans WHERE id=v_customer_auth_normalized.routing_plan_id; + + ---- Routing Plan Numberlist processing ---- + if v_rp.dst_numberlist_id is not null then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> RP DST Numberlist processing. Lookup by key: %',EXTRACT(MILLISECOND from v_end-v_start), v_ret.dst_prefix_out; + /*}dbg*/ + + v_numberlist_item=switch21.match_numberlist(v_rp.dst_numberlist_id, v_ret.dst_prefix_out); + select into v_numberlist * from class4.numberlists where id=v_rp.dst_numberlist_id; + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> RP DST Numberlist. key found: %',EXTRACT(MILLISECOND from v_end-v_start), row_to_json(v_numberlist_item); + /*}dbg*/ + IF v_numberlist_item.action_id is not null and v_numberlist_item.action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> RP DST Numberlist. Drop by key action. Key: %',EXTRACT(MILLISECOND from v_end-v_start), v_numberlist_item.key; + /*}dbg*/ + v_ret.disconnect_code_id=8016; --destination blacklisted by routing plan + RETURN NEXT v_ret; + RETURN; + elsif v_numberlist_item.action_id is not null and v_numberlist_item.action_id=2 then + IF v_numberlist_item.defer_src_rewrite THEN + v_defered_src_rewrites = array_append( + v_defered_src_rewrites, + (v_numberlist_item.src_rewrite_rule, v_numberlist_item.src_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.src_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.src_prefix_out, + v_numberlist_item.src_rewrite_rule, + v_numberlist_item.src_rewrite_result + ); + END IF; + IF v_numberlist_item.defer_dst_rewrite THEN + v_defered_dst_rewrites = array_append( + v_defered_dst_rewrites, + (v_numberlist_item.dst_rewrite_rule, v_numberlist_item.dst_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.dst_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.dst_prefix_out, + v_numberlist_item.dst_rewrite_rule, + v_numberlist_item.dst_rewrite_result + ); + END IF; + v_call_tags=yeti_ext.tag_action(v_numberlist_item.tag_action_id, v_call_tags, v_numberlist_item.tag_action_value); + -- pass call NOP. + elsif v_numberlist_item.action_id is null and v_numberlist.default_action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> RP DST Numberlist. Drop by default action',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + -- drop by default + v_ret.disconnect_code_id=8016; --destination blacklisted by routing plan + RETURN NEXT v_ret; + RETURN; + elsif v_numberlist_item.action_id is null and v_numberlist.default_action_id=2 then + IF v_numberlist.defer_src_rewrite THEN + v_defered_src_rewrites = array_append( + v_defered_src_rewrites, + (v_numberlist.default_src_rewrite_rule, v_numberlist.default_src_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.src_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.src_prefix_out, + v_numberlist.default_src_rewrite_rule, + v_numberlist.default_src_rewrite_result + ); + END IF; + IF v_numberlist.defer_dst_rewrite THEN + v_defered_dst_rewrites = array_append( + v_defered_dst_rewrites, + (v_numberlist.default_dst_rewrite_rule, v_numberlist.default_dst_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.dst_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.dst_prefix_out, + v_numberlist.default_dst_rewrite_rule, + v_numberlist.default_dst_rewrite_result + ); + END IF; + v_call_tags=yeti_ext.tag_action(v_numberlist.tag_action_id, v_call_tags, v_numberlist.tag_action_value); + -- pass by default + end if; + end if; + + if v_rp.src_numberlist_id is not null then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> RP SRC Numberlist processing. Lookup by key %, no fallback', EXTRACT(MILLISECOND from v_end-v_start), v_ret.src_prefix_out; + /*}dbg*/ + v_numberlist_item=switch21.match_numberlist(v_rp.src_numberlist_id, v_ret.src_prefix_out); + + select into v_numberlist * from class4.numberlists where id=v_rp.src_numberlist_id; + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> RP SRC Numberlist. key found: %',EXTRACT(MILLISECOND from v_end-v_start), row_to_json(v_numberlist_item); + /*}dbg*/ + IF v_numberlist_item.action_id is not null and v_numberlist_item.action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> RP SRC Numberlist. Drop by key action. Key: %',EXTRACT(MILLISECOND from v_end-v_start), v_numberlist_item.key; + /*}dbg*/ + v_ret.disconnect_code_id=8017; --source blacklisted by routing plan + RETURN NEXT v_ret; + RETURN; + elsif v_numberlist_item.action_id is not null and v_numberlist_item.action_id=2 then + IF v_numberlist_item.defer_src_rewrite THEN + v_defered_src_rewrites = array_append( + v_defered_src_rewrites, + (v_numberlist_item.src_rewrite_rule, v_numberlist_item.src_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.src_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.src_prefix_out, + v_numberlist_item.src_rewrite_rule, + v_numberlist_item.src_rewrite_result + ); + END IF; + IF v_numberlist_item.defer_dst_rewrite THEN + v_defered_dst_rewrites = array_append( + v_defered_dst_rewrites, + (v_numberlist_item.dst_rewrite_rule, v_numberlist_item.dst_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.dst_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.dst_prefix_out, + v_numberlist_item.dst_rewrite_rule, + v_numberlist_item.dst_rewrite_result + ); + END IF; + v_call_tags=yeti_ext.tag_action(v_numberlist_item.tag_action_id, v_call_tags, v_numberlist_item.tag_action_value); + -- pass call NOP. + elsif v_numberlist_item.action_id is null and v_numberlist.default_action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> RP SRC Numberlist. Drop by default action',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_ret.disconnect_code_id=8017; --source blacklisted by routing plan + RETURN NEXT v_ret; + RETURN; + elsif v_numberlist_item.action_id is null and v_numberlist.default_action_id=2 then + IF v_numberlist.defer_src_rewrite THEN + v_defered_src_rewrites = array_append( + v_defered_src_rewrites, + (v_numberlist.default_src_rewrite_rule, v_numberlist.default_src_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.src_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.src_prefix_out, + v_numberlist.default_src_rewrite_rule, + v_numberlist.default_src_rewrite_result + ); + END IF; + IF v_numberlist.defer_dst_rewrite THEN + v_defered_dst_rewrites = array_append( + v_defered_dst_rewrites, + (v_numberlist.default_dst_rewrite_rule, v_numberlist.default_dst_rewrite_result)::switch21.defered_rewrite + ); + ELSE + v_ret.dst_prefix_out=yeti_ext.regexp_replace_rand( + v_ret.dst_prefix_out, + v_numberlist.default_dst_rewrite_rule, + v_numberlist.default_dst_rewrite_result + ); + END IF; + v_call_tags=yeti_ext.tag_action(v_numberlist.tag_action_id, v_call_tags, v_numberlist.tag_action_value); + -- pass by default + end if; + end if; + ---- END of routing plan Numberlist processing + + -- setting numbers used for routing & billing + v_ret.src_prefix_routing=v_ret.src_prefix_out; + v_ret.dst_prefix_routing=v_ret.dst_prefix_out; + v_routing_key=v_ret.dst_prefix_out; + + -- Areas and Tag detection------------------------------------------- + v_ret.src_area_id:=( + select area_id from class4.area_prefixes where prefix_range(prefix)@>prefix_range(v_ret.src_prefix_routing) + order by length(prefix_range(prefix)) desc limit 1 + ); + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> SRC Area found: %',EXTRACT(MILLISECOND from v_end-v_start), v_ret.src_area_id; + /*}dbg*/ + + v_ret.dst_area_id:=( + select area_id from class4.area_prefixes where prefix_range(prefix)@>prefix_range(v_ret.dst_prefix_routing) + order by length(prefix_range(prefix)) desc limit 1 + ); + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DST Area found: %',EXTRACT(MILLISECOND from v_end-v_start), v_ret.dst_area_id; + /*}dbg*/ + + + select into v_area_direction * from class4.routing_tag_detection_rules + where + (src_area_id is null OR src_area_id = v_ret.src_area_id) AND + (dst_area_id is null OR dst_area_id = v_ret.dst_area_id) AND + prefix_range(src_prefix) @> prefix_range(v_ret.src_prefix_routing) AND + prefix_range(dst_prefix) @> prefix_range(v_ret.dst_prefix_routing) AND + yeti_ext.tag_compare(routing_tag_ids, v_call_tags, routing_tag_mode_id ) > 0 + order by + yeti_ext.tag_compare(routing_tag_ids, v_call_tags, routing_tag_mode_id) desc, + length(prefix_range(src_prefix)) desc, + length(prefix_range(dst_prefix)) desc, + src_area_id is null, + dst_area_id is null + limit 1; + if found then + /*dbg{*/ + RAISE NOTICE '% ms -> Routing tag detection rule found: %',EXTRACT(MILLISECOND from clock_timestamp() - v_start), row_to_json(v_area_direction); + /*}dbg*/ + v_call_tags=yeti_ext.tag_action(v_area_direction.tag_action_id, v_call_tags, v_area_direction.tag_action_value); + end if; + + v_ret.routing_tag_ids:=v_call_tags; + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> Routing tags: %',EXTRACT(MILLISECOND from v_end-v_start), v_ret.routing_tag_ids; + /*}dbg*/ + ---------------------------------------------------------------------- + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> Routing plan processing',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + + v_routing_key=v_ret.dst_prefix_routing; + + if v_rp.sorting_id=5 then -- route testing + v_test_vendor_id=regexp_replace(v_routing_key,'(.*)\*(.*)','\1')::integer; + v_routing_key=regexp_replace(v_routing_key,'(.*)\*(.*)','\2'); + v_ret.dst_prefix_out=v_routing_key; + v_ret.dst_prefix_routing=v_routing_key; + end if; + + if v_rp.use_lnp then + select into v_lnp_rule rules.* + from class4.routing_plan_lnp_rules rules + WHERE prefix_range(rules.dst_prefix)@>prefix_range(v_ret.dst_prefix_routing) and rules.routing_plan_id=v_rp.id + order by length(prefix_range(rules.dst_prefix)) desc limit 1; + if found then + v_ret.lnp_database_id=v_lnp_rule.database_id; + v_lnp_key=v_ret.dst_prefix_routing; + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> LNP. Need LNP lookup, LNP key: %',EXTRACT(MILLISECOND from v_end-v_start),v_lnp_key; + /*}dbg*/ + v_lnp_key=yeti_ext.regexp_replace_rand(v_lnp_key,v_lnp_rule.req_dst_rewrite_rule,v_lnp_rule.req_dst_rewrite_result); + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> LNP key translation. LNP key: %',EXTRACT(MILLISECOND from v_end-v_start),v_lnp_key; + /*}dbg*/ + -- try cache + select into v_ret.lrn lrn from class4.lnp_cache where dst=v_lnp_key AND database_id=v_lnp_rule.database_id and expires_at>v_now; + if found then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> LNP. Data found in cache, lrn: %',EXTRACT(MILLISECOND from v_end-v_start),v_ret.lrn; + /*}dbg*/ + -- TRANSLATING response from cache + v_ret.lrn=yeti_ext.regexp_replace_rand(v_ret.lrn,v_lnp_rule.lrn_rewrite_rule,v_lnp_rule.lrn_rewrite_result); + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> LNP. Translation. lrn: %',EXTRACT(MILLISECOND from v_end-v_start),v_ret.lrn; + /*}dbg*/ + v_routing_key=v_ret.lrn; + if v_lnp_rule.rewrite_call_destination then + v_ret.dst_prefix_out=v_ret.lrn; + v_ret.dst_prefix_routing=v_ret.lrn; + -- TODO shouldn't we perform tag detection again there? Call destination changed. + end if; + else + v_ret.lrn=switch21.lnp_resolve(v_ret.lnp_database_id,v_lnp_key); + if v_ret.lrn is null then -- fail + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> LNP. Query failed',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + if v_lnp_rule.drop_call_on_error then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> LNP. Dropping call',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_ret.disconnect_code_id=8003; --No response from LNP DB + RETURN NEXT v_ret; + RETURN; + end if; + else + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> LNP. Success, lrn: %',EXTRACT(MILLISECOND from v_end-v_start),v_ret.lrn; + /*}dbg*/ + -- TRANSLATING response from LNP DB + v_ret.lrn=yeti_ext.regexp_replace_rand(v_ret.lrn,v_lnp_rule.lrn_rewrite_rule,v_lnp_rule.lrn_rewrite_result); + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> LNP. Translation. lrn: %',EXTRACT(MILLISECOND from v_end-v_start),v_ret.lrn; + /*}dbg*/ + v_routing_key=v_ret.lrn; + if v_lnp_rule.rewrite_call_destination then + v_ret.dst_prefix_out=v_ret.lrn; + v_ret.dst_prefix_routing=v_ret.lrn; + -- TODO shouldn't we perform tag detection again there? Call destination changed. + end if; + end if; + end if; + end if; + end if; + + + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DST. search start. Routing key: %. Routing tags: %, Rate limit: %',EXTRACT(MILLISECOND from v_end-v_start), v_routing_key, v_ret.routing_tag_ids, v_destination_rate_limit; + /*}dbg*/ + v_src_network:=switch21.detect_network(v_ret.src_prefix_routing); + v_ret.src_network_id=v_src_network.network_id; + v_ret.src_country_id=v_src_network.country_id; + + v_network:=switch21.detect_network(v_ret.dst_prefix_routing); + v_ret.dst_network_id=v_network.network_id; + v_ret.dst_country_id=v_network.country_id; + + IF v_rp.validate_dst_number_network AND v_ret.dst_network_id is null THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> Network detection. DST network validation enabled and DST network was not found. Rejecting call',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + + v_ret.disconnect_code_id=8007; --No network detected for DST number + RETURN NEXT v_ret; + RETURN; + END IF; + + + IF v_rp.validate_dst_number_format AND NOT (v_routing_key ~ '^[0-9]+$') THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> Dst number format is not valid. DST number: %s',EXTRACT(MILLISECOND from v_end-v_start), v_routing_key; + /*}dbg*/ + + v_ret.disconnect_code_id=8008; --Invalid DST number format + RETURN NEXT v_ret; + RETURN; + END IF; + + IF v_rp.validate_src_number_network AND v_ret.src_network_id is null AND lower(v_ret.src_prefix_routing)!='anonymous' THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> Network detection. SRC network validation enabled and SRC network was not found. Rejecting call',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + + v_ret.disconnect_code_id=8010; --No network detected for SRC number + RETURN NEXT v_ret; + RETURN; + END IF; + + IF v_rp.validate_src_number_format AND lower(v_ret.src_prefix_routing)!='anonymous' AND NOT (v_ret.src_prefix_routing ~ '^[0-9]+$') THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> SRC number format is not valid. SRC number: %s',EXTRACT(MILLISECOND from v_end-v_start), v_ret.src_prefix_routing; + /*}dbg*/ + + v_ret.disconnect_code_id=8011; --Invalid SRC number format + RETURN NEXT v_ret; + RETURN; + END IF; + + --- rateplan lookup + SELECT INTO v_rate_groups array_agg(rate_group_id) from class4.rate_plan_groups where rateplan_id = v_customer_auth_normalized.rateplan_id; + + SELECT into v_destination d.*/*,switch.tracelog(d.*)*/ + FROM class4.destinations d + WHERE + prefix_range(prefix)@>prefix_range(v_routing_key) + AND length(v_routing_key) between d.dst_number_min_length and d.dst_number_max_length + AND d.rate_group_id = ANY(v_rate_groups) + AND enabled + AND valid_from <= v_now + AND valid_till >= v_now + AND yeti_ext.tag_compare(d.routing_tag_ids, v_call_tags, d.routing_tag_mode_id)>0 + ORDER BY length(prefix_range(prefix)) DESC, yeti_ext.tag_compare(d.routing_tag_ids, v_call_tags) desc + limit 1; + IF NOT FOUND THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DST. Destination not found',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_ret.disconnect_code_id=111; --Cant find destination prefix + RETURN NEXT v_ret; + RETURN; + END IF; + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DST. found: %',EXTRACT(MILLISECOND from v_end-v_start),row_to_json(v_destination, true); + /*}dbg*/ + + v_ret.destination_id:=v_destination.id; + v_ret.destination_prefix=v_destination.prefix; + v_ret.destination_initial_interval:=v_destination.initial_interval; + v_ret.destination_fee:=v_destination.connect_fee::varchar; + v_ret.destination_next_interval:=v_destination.next_interval; + v_ret.destination_rate_policy_id:=v_destination.rate_policy_id; + v_ret.destination_reverse_billing:=v_destination.reverse_billing; + IF v_destination.reject_calls THEN + v_ret.disconnect_code_id=112; --Rejected by destination + RETURN NEXT v_ret; + RETURN; + END IF; + + if v_destination.next_rate::float>v_destination_rate_limit then + v_ret.disconnect_code_id=8006; -- No destination with appropriate rate found + RETURN NEXT v_ret; + RETURN; + end if; + + select into v_rateplan * from class4.rateplans where id=v_customer_auth_normalized.rateplan_id; + if COALESCE(v_destination.profit_control_mode_id,v_rateplan.profit_control_mode_id)=2 then -- per call + v_rate_limit=v_destination.next_rate::float; + end if; + + + /* + FIND dialpeers logic. Queries must use prefix index for best performance + */ + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DP. search start. Routing key: %. Rate limit: %. Routing tag: %',EXTRACT(MILLISECOND from v_end-v_start), v_routing_key, v_rate_limit, v_ret.routing_tag_ids; + /*}dbg*/ + + + /* apply defered rewrites there, not really after routing, but without affecting v_routing_key */ + + FOREACH v_rewrite IN ARRAY v_defered_src_rewrites LOOP + v_ret.src_prefix_out = yeti_ext.regexp_replace_rand( + v_ret.src_prefix_out, + v_rewrite.rule, + v_rewrite.result + ); + END LOOP; + + FOREACH v_rewrite IN ARRAY v_defered_dst_rewrites LOOP + v_ret.dst_prefix_out = yeti_ext.regexp_replace_rand( + v_ret.dst_prefix_out, + v_rewrite.rule, + v_rewrite.result + ); + END LOOP; + + SELECT INTO v_routing_groups array_agg(routing_group_id) from class4.routing_plan_groups where routing_plan_id = v_customer_auth_normalized.routing_plan_id; + + CASE v_rp.sorting_id + WHEN '1' THEN -- LCR,Prio, ACD&ASR control + FOR routedata IN ( + WITH step1 AS( + SELECT + (t_dp.*)::class4.dialpeers as s1_dialpeer, + t_dp.vendor_id as s1_vendor_id, + t_dp.account_id as s1_vendor_account_id, + t_dp.next_rate as dp_next_rate, + t_dp.lcr_rate_multiplier AS dp_lcr_rate_multiplier, + t_dp.priority AS dp_priority, + t_dp.locked as dp_locked, + t_dp.enabled as dp_enabled, + rank() OVER ( + PARTITION BY t_dp.vendor_id, t_dp.routeset_discriminator_id + ORDER BY + length(prefix_range(t_dp.prefix)) desc, + yeti_ext.tag_compare(t_dp.routing_tag_ids, v_call_tags, t_dp.routing_tag_mode_id) desc, + t_dp.exclusive_route desc -- in case when we have two identical prefixes with different exclusive flag value, we should lift up exclusive route, otherwise it will be filtered at WHERE r=1 and exclusive_rank=1 + ) as r, + rank() OVER ( + ORDER BY t_dp.exclusive_route desc -- force top rank for exclusive route + ) as exclusive_rank + FROM class4.dialpeers t_dp + WHERE + prefix_range(t_dp.prefix)@>prefix_range(v_routing_key) + AND length(v_routing_key) between t_dp.dst_number_min_length and t_dp.dst_number_max_length + AND t_dp.routing_group_id = ANY(v_routing_groups) + and t_dp.valid_from<=v_now + and t_dp.valid_till>=v_now + AND yeti_ext.tag_compare(t_dp.routing_tag_ids, v_call_tags, t_dp.routing_tag_mode_id) > 0 + ) + SELECT + s1_dialpeer as s2_dialpeer, + (t_vendor_account.*)::billing.accounts as s2_vendor_account + FROM step1 + JOIN public.contractors t_vendor ON step1.s1_vendor_id = t_vendor.id + JOIN billing.accounts t_vendor_account ON step1.s1_vendor_account_id = t_vendor_account.id + WHERE + r=1 + and exclusive_rank=1 + AND dp_next_rate<=v_rate_limit + AND dp_enabled + and not dp_locked --ACD&ASR control for DP + AND t_vendor_account.balance < t_vendor_account.max_balance + AND t_vendor.enabled AND t_vendor.vendor + ORDER BY dp_next_rate*dp_lcr_rate_multiplier, dp_priority DESC + LIMIT v_rp.max_rerouting_attempts + ) LOOP + RETURN QUERY + /*rel{*/SELECT * from process_dp_release(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}rel*/ + /*dbg{*/SELECT * from process_dp_debug(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}dbg*/ + end LOOP; + WHEN '2' THEN --LCR, no prio, No ACD&ASR control + FOR routedata IN ( + WITH step1 AS( -- filtering + SELECT + (t_dp.*)::class4.dialpeers as s1_dialpeer, + t_dp.vendor_id as s1_vendor_id, + t_dp.account_id as s1_vendor_account_id, + rank() OVER ( + PARTITION BY t_dp.vendor_id, t_dp.routeset_discriminator_id + ORDER BY + length(prefix_range(t_dp.prefix)) desc, + yeti_ext.tag_compare(t_dp.routing_tag_ids, v_call_tags, t_dp.routing_tag_mode_id) desc, + t_dp.exclusive_route desc -- in case when we have two identical prefixes with different exclusive flag value, we should lift up exclusive route, otherwise it will be filtered at WHERE r=1 and exclusive_rank=1 + ) as r, + rank() OVER ( + ORDER BY t_dp.exclusive_route desc -- force top rank for exclusive route + ) as exclusive_rank, + t_dp.next_rate*t_dp.lcr_rate_multiplier as dp_metric, + t_dp.next_rate as dp_next_rate, + t_dp.enabled as dp_enabled + FROM class4.dialpeers t_dp + WHERE + prefix_range(t_dp.prefix)@>prefix_range(v_routing_key) + AND length(v_routing_key) between t_dp.dst_number_min_length and t_dp.dst_number_max_length + AND t_dp.routing_group_id = ANY(v_routing_groups) + and t_dp.valid_from<=v_now + and t_dp.valid_till>=v_now + AND yeti_ext.tag_compare(t_dp.routing_tag_ids,v_call_tags, t_dp.routing_tag_mode_id)>0 + ) + SELECT + s1_dialpeer as s2_dialpeer, + (t_vendor_account.*)::billing.accounts as s2_vendor_account + FROM step1 + JOIN public.contractors t_vendor ON step1.s1_vendor_id = t_vendor.id + JOIN billing.accounts t_vendor_account ON step1.s1_vendor_account_id = t_vendor_account.id + WHERE + r=1 + and exclusive_rank=1 + AND dp_enabled + and dp_next_rate<=v_rate_limit + AND t_vendor_account.balance < t_vendor_account.max_balance + AND t_vendor.enabled AND t_vendor.vendor + ORDER BY dp_metric + LIMIT v_rp.max_rerouting_attempts + ) LOOP + RETURN QUERY + /*rel{*/SELECT * from process_dp_release(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}rel*/ + /*dbg{*/SELECT * from process_dp_debug(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}dbg*/ + END LOOP; + WHEN '3' THEN --Prio, LCR, ACD&ASR control + FOR routedata in( + WITH step1 AS( -- filtering + SELECT + (t_dp.*)::class4.dialpeers as s1_dialpeer, + t_dp.vendor_id as s1_vendor_id, + t_dp.account_id as s1_vendor_account_id, + rank() OVER ( + PARTITION BY t_dp.vendor_id, t_dp.routeset_discriminator_id + ORDER BY + length(prefix_range(t_dp.prefix)) desc, + yeti_ext.tag_compare(t_dp.routing_tag_ids, v_call_tags, t_dp.routing_tag_mode_id) desc, + t_dp.exclusive_route desc -- in case when we have two identical prefixes with different exclusive flag value, we should lift up exclusive route, otherwise it will be filtered at WHERE r=1 and exclusive_rank=1 + ) as r, + rank() OVER ( + ORDER BY t_dp.exclusive_route desc -- force top rank for exclusive route + ) as exclusive_rank, + t_dp.priority as dp_metric_priority, + t_dp.next_rate*t_dp.lcr_rate_multiplier as dp_metric, + t_dp.next_rate as dp_next_rate, + t_dp.locked as dp_locked, + t_dp.enabled as dp_enabled + FROM class4.dialpeers t_dp + WHERE + prefix_range(t_dp.prefix)@>prefix_range(v_routing_key) + AND length(v_routing_key) between t_dp.dst_number_min_length and t_dp.dst_number_max_length + AND t_dp.routing_group_id = ANY(v_routing_groups) + and t_dp.valid_from<=v_now + and t_dp.valid_till>=v_now + AND yeti_ext.tag_compare(t_dp.routing_tag_ids, v_call_tags, t_dp.routing_tag_mode_id) > 0 + ) + SELECT + s1_dialpeer as s2_dialpeer, + (t_vendor_account.*)::billing.accounts as s2_vendor_account + FROM step1 + JOIN public.contractors t_vendor ON step1.s1_vendor_id = t_vendor.id + JOIN billing.accounts t_vendor_account ON step1.s1_vendor_account_id = t_vendor_account.id + WHERE + r=1 + AND exclusive_rank=1 + AND dp_next_rate<=v_rate_limit + AND dp_enabled + AND NOT dp_locked + AND t_vendor_account.balance < t_vendor_account.max_balance + AND t_vendor.enabled AND t_vendor.vendor + ORDER BY dp_metric_priority DESC, dp_metric + LIMIT v_rp.max_rerouting_attempts + )LOOP + RETURN QUERY + /*rel{*/SELECT * from process_dp_release(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}rel*/ + /*dbg{*/SELECT * from process_dp_debug(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}dbg*/ + END LOOP; + WHEN'4' THEN -- LCRD, Prio, ACD&ACR control + FOR routedata IN ( + WITH step1 AS( + SELECT + (t_dp.*)::class4.dialpeers as s1_dialpeer, + t_dp.vendor_id as s1_vendor_id, + t_dp.account_id as s1_vendor_account_id, + rank() OVER ( + PARTITION BY t_dp.vendor_id, t_dp.routeset_discriminator_id + ORDER BY + length(prefix_range(t_dp.prefix)) desc, + yeti_ext.tag_compare(t_dp.routing_tag_ids, v_call_tags, t_dp.routing_tag_mode_id) desc, + t_dp.exclusive_route desc -- in case when we have two identical prefixes with different exclusive flag value, we should lift up exclusive route, otherwise it will be filtered at WHERE r=1 and exclusive_rank=1 + ) as r, + rank() OVER ( + ORDER BY t_dp.exclusive_route desc -- force top rank for exclusive route + ) as exclusive_rank, + ((t_dp.next_rate - first_value(t_dp.next_rate) OVER(ORDER BY t_dp.next_rate ASC)) > v_rp.rate_delta_max)::INTEGER *(t_dp.next_rate + t_dp.priority) - t_dp.priority AS r2, + t_dp.next_rate as dp_next_rate, + t_dp.locked as dp_locked, + t_dp.enabled as dp_enabled + FROM class4.dialpeers t_dp + WHERE + prefix_range(t_dp.prefix)@>prefix_range(v_routing_key) + AND length(v_routing_key) between t_dp.dst_number_min_length and t_dp.dst_number_max_length + AND t_dp.routing_group_id = ANY(v_routing_groups) + and t_dp.valid_from<=v_now + and t_dp.valid_till>=v_now + AND yeti_ext.tag_compare(t_dp.routing_tag_ids,v_call_tags, t_dp.routing_tag_mode_id)>0 + ) + SELECT + s1_dialpeer as s2_dialpeer, + (t_vendor_account.*)::billing.accounts as s2_vendor_account + FROM step1 + JOIN public.contractors t_vendor ON step1.s1_vendor_id = t_vendor.id + JOIN billing.accounts t_vendor_account ON step1.s1_vendor_account_id = t_vendor_account.id + WHERE + r=1 + and exclusive_rank=1 + and dp_next_rate <= v_rate_limit + and dp_enabled + and not dp_locked --ACD&ASR control for DP + AND t_vendor_account.balance < t_vendor_account.max_balance + AND t_vendor.enabled AND t_vendor.vendor + ORDER BY r2 ASC + LIMIT v_rp.max_rerouting_attempts + ) LOOP + RETURN QUERY + /*rel{*/SELECT * from process_dp_release(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}rel*/ + /*dbg{*/SELECT * from process_dp_debug(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}dbg*/ + end LOOP; + WHEN'5' THEN -- Route test + FOR routedata IN ( + WITH step1 AS( -- filtering + SELECT + (t_dp.*)::class4.dialpeers as s1_dialpeer, + t_dp.vendor_id as s1_vendor_id, + t_dp.account_id as s1_vendor_account_id, + rank() OVER ( + PARTITION BY t_dp.vendor_id, t_dp.routeset_discriminator_id + ORDER BY + length(prefix_range(t_dp.prefix)) desc, + yeti_ext.tag_compare(t_dp.routing_tag_ids, v_call_tags, t_dp.routing_tag_mode_id) desc, + t_dp.exclusive_route desc -- in case when we have two identical prefixes with different exclusive flag value, we should lift up exclusive route, otherwise it will be filtered at WHERE r=1 and exclusive_rank=1 + ) as r, + rank() OVER ( + ORDER BY t_dp.exclusive_route desc -- force top rank for exclusive route + ) as exclusive_rank, + t_dp.priority as dp_metric_priority, + t_dp.next_rate*t_dp.lcr_rate_multiplier as dp_metric, + t_dp.next_rate as dp_next_rate, + t_dp.enabled as dp_enabled + FROM class4.dialpeers t_dp + WHERE + prefix_range(t_dp.prefix)@>prefix_range(v_routing_key) + AND length(v_routing_key) between t_dp.dst_number_min_length and t_dp.dst_number_max_length + AND t_dp.routing_group_id = ANY(v_routing_groups) + and t_dp.valid_from<=v_now + and t_dp.valid_till>=v_now + and t_dp.vendor_id = v_test_vendor_id + AND yeti_ext.tag_compare(t_dp.routing_tag_ids,v_call_tags, t_dp.routing_tag_mode_id) > 0 + ) + SELECT + s1_dialpeer as s2_dialpeer, + (t_vendor_account.*)::billing.accounts as s2_vendor_account + FROM step1 + JOIN public.contractors t_vendor ON step1.s1_vendor_id = t_vendor.id + JOIN billing.accounts t_vendor_account ON step1.s1_vendor_account_id = t_vendor_account.id + WHERE + r=1 + and exclusive_rank=1 + and dp_enabled + and dp_next_rate<=v_rate_limit + AND t_vendor_account.balance < t_vendor_account.max_balance + AND t_vendor.enabled AND t_vendor.vendor + ORDER BY dp_metric_priority DESC, dp_metric + LIMIT v_rp.max_rerouting_attempts + )LOOP + RETURN QUERY + /*rel{*/SELECT * from process_dp_release(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}rel*/ + /*dbg{*/SELECT * from process_dp_debug(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}dbg*/ + END LOOP; + WHEN'6' THEN -- QD.Static,LCR,ACD&ACR control + v_random:=random(); + FOR routedata in( + WITH step1 AS( -- filtering + SELECT + (t_dp.*)::class4.dialpeers as s1_dialpeer, + (t_vendor_account.*)::billing.accounts as s1_vendor_account, + rank() OVER ( + PARTITION BY t_dp.vendor_id, t_dp.routeset_discriminator_id + ORDER BY + length(prefix_range(t_dp.prefix)) desc, + yeti_ext.tag_compare(t_dp.routing_tag_ids, v_call_tags, t_dp.routing_tag_mode_id) desc, + t_dp.exclusive_route desc -- in case when we have two identical prefixes with different exclusive flag value, we should lift up exclusive route, otherwise it will be filtered at WHERE r=1 and exclusive_rank=1 + ) as r, + rank() OVER ( + ORDER BY t_dp.exclusive_route desc -- force top rank for exclusive route + ) as exclusive_rank, + rank() OVER ( + PARTITION BY t_dp.vendor_id, t_dp.routeset_discriminator_id + ORDER BY length(prefix_range(coalesce(rpsr.prefix,''))) desc + ) as r2, + t_dp.priority as dp_metric_priority, + t_dp.next_rate*t_dp.lcr_rate_multiplier as dp_metric, + t_dp.next_rate as dp_next_rate, + t_dp.locked as dp_locked, + t_dp.enabled as dp_enabled, + t_dp.force_hit_rate as dp_force_hit_rate, + COALESCE(rpsr.priority, t_dp.priority) as rpsr_priority, + COALESCE(rpsr.weight, 100) as rpsr_weight + FROM class4.dialpeers t_dp + JOIN billing.accounts t_vendor_account ON t_dp.account_id=t_vendor_account.id + join public.contractors t_vendor on t_dp.vendor_id=t_vendor.id + left join class4.routing_plan_static_routes rpsr + ON rpsr.routing_plan_id=v_customer_auth_normalized.routing_plan_id + and rpsr.vendor_id=t_dp.vendor_id + AND prefix_range(rpsr.prefix)@>prefix_range(v_routing_key) + WHERE + prefix_range(t_dp.prefix)@>prefix_range(v_routing_key) + AND length(v_routing_key) between t_dp.dst_number_min_length and t_dp.dst_number_max_length + AND t_dp.routing_group_id = ANY(v_routing_groups) + and t_dp.valid_from<=v_now + and t_dp.valid_till>=v_now + AND t_vendor_account.balance 0 + ) + SELECT s1_dialpeer as s2_dialpeer, + s1_vendor_account as s2_vendor_account + FROM step1 + WHERE + r=1 + and exclusive_rank=1 + and r2=1 + and dp_next_rate<=v_rate_limit + and dp_enabled + and not dp_locked + ORDER BY + coalesce(v_random<=dp_force_hit_rate,false) desc, + rpsr_priority, + yeti_ext.rank_dns_srv(rpsr_weight) over ( partition by rpsr_priority order by rpsr_weight), + dp_metric + LIMIT v_rp.max_rerouting_attempts + )LOOP + RETURN QUERY + /*rel{*/SELECT * from process_dp_release(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}rel*/ + /*dbg{*/SELECT * from process_dp_debug(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}dbg*/ + END LOOP; + WHEN'7' THEN -- QD.Static, No ACD&ACR control + v_random:=random(); + FOR routedata in( + WITH step1 AS( -- filtering + SELECT + (t_dp.*)::class4.dialpeers as s1_dialpeer, + (t_vendor_account.*)::billing.accounts as s1_vendor_account, + rank() OVER ( + PARTITION BY t_dp.vendor_id, t_dp.routeset_discriminator_id + ORDER BY + length(prefix_range(t_dp.prefix)) desc, + yeti_ext.tag_compare(t_dp.routing_tag_ids, v_call_tags, t_dp.routing_tag_mode_id) desc, + t_dp.exclusive_route desc -- in case when we have two identical prefixes with different exclusive flag value, we should lift up exclusive route, otherwise it will be filtered at WHERE r=1 and exclusive_rank=1 + ) as r, + rank() OVER ( + ORDER BY t_dp.exclusive_route desc -- force top rank for exclusive route + ) as exclusive_rank, + rank() OVER ( + PARTITION BY t_dp.vendor_id, t_dp.routeset_discriminator_id + ORDER BY length(prefix_range(coalesce(rpsr.prefix,''))) desc + ) as r2, + t_dp.priority as dp_metric_priority, + t_dp.next_rate*t_dp.lcr_rate_multiplier as dp_metric, + t_dp.next_rate as dp_next_rate, + t_dp.enabled as dp_enabled, + t_dp.force_hit_rate as dp_force_hit_rate, + rpsr.priority as rpsr_priority, + rpsr.weight as rpsr_weight + FROM class4.dialpeers t_dp + JOIN billing.accounts t_vendor_account ON t_dp.account_id=t_vendor_account.id + join public.contractors t_vendor on t_dp.vendor_id=t_vendor.id + join class4.routing_plan_static_routes rpsr + ON rpsr.routing_plan_id=v_customer_auth_normalized.routing_plan_id + and rpsr.vendor_id=t_dp.vendor_id + AND prefix_range(rpsr.prefix)@>prefix_range(v_routing_key) + WHERE + prefix_range(t_dp.prefix)@>prefix_range(v_routing_key) + AND length(v_routing_key) between t_dp.dst_number_min_length and t_dp.dst_number_max_length + AND t_dp.routing_group_id = ANY(v_routing_groups) + and t_dp.valid_from<=v_now + and t_dp.valid_till>=v_now + AND t_vendor_account.balance 0 + ) + SELECT s1_dialpeer as s2_dialpeer, + s1_vendor_account as s2_vendor_account + FROM step1 + WHERE + r=1 + and exclusive_rank=1 + and r2=1 + and dp_next_rate<=v_rate_limit + and dp_enabled + ORDER BY + coalesce(v_random<=dp_force_hit_rate,false) desc, + rpsr_priority, + yeti_ext.rank_dns_srv(rpsr_weight) over ( partition by rpsr_priority order by rpsr_weight), + dp_metric + LIMIT v_rp.max_rerouting_attempts + )LOOP + RETURN QUERY + /*rel{*/SELECT * from process_dp_release(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}rel*/ + /*dbg{*/SELECT * from process_dp_debug(v_ret,v_destination,routedata.s2_dialpeer,v_c_acc,v_orig_gw,routedata.s2_vendor_account,i_pop_id,v_customer_auth_normalized.send_billing_information,v_max_call_length,v_diversion,v_privacy,v_pai,v_ppi);/*}dbg*/ + END LOOP; + + ELSE + RAISE NOTICE 'BUG: unknown sorting_id'; + END CASE; + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> Dialpeer search done',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + v_ret.disconnect_code_id=113; --No routes + RETURN NEXT v_ret; + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DONE.',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + RETURN; + END; + $_$; + + + +CREATE or replace FUNCTION switch21.process_gw(i_profile switch21.callprofile_ty, i_destination class4.destinations, i_dp class4.dialpeers, i_customer_acc billing.accounts, i_customer_gw class4.gateways, i_vendor_acc billing.accounts, i_vendor_gw class4.gateways, i_send_billing_information boolean, i_max_call_length integer, i_diversion character varying[], i_privacy character varying[], i_pai character varying[], i_ppi character varying) RETURNS switch21.callprofile_ty + LANGUAGE plpgsql STABLE SECURITY DEFINER COST 100000 + AS $_$ +DECLARE + i integer; + v_vendor_allowtime real; + v_route_found boolean:=false; + v_from_user varchar; + v_from_domain varchar; + v_schema varchar; + v_termination_numberlist class4.numberlists%rowtype; + v_termination_numberlist_item class4.numberlist_items%rowtype; + v_termination_numberlist_size integer; + v_aleg_append_headers_reply varchar[] not null default ARRAY[]::varchar[]; + v_bleg_append_headers_req varchar[] not null default ARRAY[]::varchar[]; + v_diversion varchar[] not null default ARRAY[]::varchar[]; + v_diversion_header varchar; + v_diversion_out varchar[] not null default ARRAY[]::varchar[]; + v_pai_out varchar[] not null default ARRAY[]::varchar[]; + v_pai varchar; + v_allow_pai boolean:=true; + v_to_uri_params varchar[] not null default ARRAY[]::varchar[]; + v_from_uri_params varchar[] not null default ARRAY[]::varchar[]; + v_ruri_host varchar; + v_ruri_params varchar[] not null default ARRAY[]::varchar[]; + v_ruri_user_params varchar[] not null default ARRAY[]::varchar[]; + v_to_username varchar; + /*dbg{*/ + v_start timestamp; + v_end timestamp; + /*}dbg*/ +BEGIN + /*dbg{*/ + v_start:=now(); + --RAISE NOTICE 'process_dp in: %',i_profile; + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DP. Found dialpeer: %',EXTRACT(MILLISECOND from v_end-v_start),row_to_json(i_dp,true); + /*}dbg*/ + + --RAISE NOTICE 'process_dp dst: %',i_destination; + + i_profile.destination_id:=i_destination.id; + i_profile.destination_fee:=i_destination.connect_fee::varchar; + i_profile.destination_rate_policy_id:=i_destination.rate_policy_id; + + --vendor account capacity limit; + i_profile.legb_res= ''; + if i_vendor_acc.termination_capacity is not null then + i_profile.legb_res = '2:'||i_dp.account_id::varchar||':'||i_vendor_acc.termination_capacity::varchar||':1;'; + end if; + + if i_vendor_acc.total_capacity is not null then + i_profile.legb_res = i_profile.legb_res||'7:'||i_dp.account_id::varchar||':'||i_vendor_acc.total_capacity::varchar||':1;'; + end if; + + -- dialpeer account capacity limit; + if i_dp.capacity is not null then + i_profile.legb_res = i_profile.legb_res||'6:'||i_dp.id::varchar||':'||i_dp.capacity::varchar||':1;'; + end if; + + /* */ + i_profile.dialpeer_id=i_dp.id; + i_profile.dialpeer_prefix=i_dp.prefix; + i_profile.dialpeer_next_rate=i_dp.next_rate::varchar; + i_profile.dialpeer_initial_rate=i_dp.initial_rate::varchar; + i_profile.dialpeer_initial_interval=i_dp.initial_interval; + i_profile.dialpeer_next_interval=i_dp.next_interval; + i_profile.dialpeer_fee=i_dp.connect_fee::varchar; + i_profile.dialpeer_reverse_billing=i_dp.reverse_billing; + i_profile.vendor_id=i_dp.vendor_id; + i_profile.vendor_acc_id=i_dp.account_id; + i_profile.term_gw_id=i_vendor_gw.id; + + i_profile.orig_gw_name=i_customer_gw."name"; + i_profile.orig_gw_external_id=i_customer_gw.external_id; + + i_profile.term_gw_name=i_vendor_gw."name"; + i_profile.term_gw_external_id=i_vendor_gw.external_id; + + i_profile.customer_account_name=i_customer_acc."name"; + + i_profile.routing_group_id:=i_dp.routing_group_id; + + if i_send_billing_information then + v_aleg_append_headers_reply=array_append(v_aleg_append_headers_reply, (E'X-VND-INIT-INT:'||i_profile.dialpeer_initial_interval)::varchar); + v_aleg_append_headers_reply=array_append(v_aleg_append_headers_reply, (E'X-VND-NEXT-INT:'||i_profile.dialpeer_next_interval)::varchar); + v_aleg_append_headers_reply=array_append(v_aleg_append_headers_reply, (E'X-VND-INIT-RATE:'||i_profile.dialpeer_initial_rate)::varchar); + v_aleg_append_headers_reply=array_append(v_aleg_append_headers_reply, (E'X-VND-NEXT-RATE:'||i_profile.dialpeer_next_rate)::varchar); + v_aleg_append_headers_reply=array_append(v_aleg_append_headers_reply, (E'X-VND-CF:'||i_profile.dialpeer_fee)::varchar); + end if; + v_aleg_append_headers_reply = array_cat(v_aleg_append_headers_reply,i_customer_gw.orig_append_headers_reply); + i_profile.aleg_append_headers_reply=ARRAY_TO_STRING(v_aleg_append_headers_reply,'\r\n'); + + if i_destination.use_dp_intervals THEN + i_profile.destination_initial_interval:=i_dp.initial_interval; + i_profile.destination_next_interval:=i_dp.next_interval; + ELSE + i_profile.destination_initial_interval:=i_destination.initial_interval; + i_profile.destination_next_interval:=i_destination.next_interval; + end if; + + CASE i_profile.destination_rate_policy_id + WHEN 1 THEN -- fixed + i_profile.destination_next_rate:=i_destination.next_rate::varchar; + i_profile.destination_initial_rate:=i_destination.initial_rate::varchar; + WHEN 2 THEN -- based on dialpeer + i_profile.destination_next_rate:=(COALESCE(i_destination.dp_margin_fixed,0)+i_dp.next_rate*(1+COALESCE(i_destination.dp_margin_percent,0)))::varchar; + i_profile.destination_initial_rate:=(COALESCE(i_destination.dp_margin_fixed,0)+i_dp.initial_rate*(1+COALESCE(i_destination.dp_margin_percent,0)))::varchar; + WHEN 3 THEN -- min + IF i_dp.next_rate >= i_destination.next_rate THEN + i_profile.destination_next_rate:=i_destination.next_rate::varchar; -- FIXED least + i_profile.destination_initial_rate:=i_destination.initial_rate::varchar; + ELSE + i_profile.destination_next_rate:=(COALESCE(i_destination.dp_margin_fixed,0)+i_dp.next_rate*(1+COALESCE(i_destination.dp_margin_percent,0)))::varchar; -- DYNAMIC + i_profile.destination_initial_rate:=(COALESCE(i_destination.dp_margin_fixed,0)+i_dp.initial_rate*(1+COALESCE(i_destination.dp_margin_percent,0)))::varchar; + END IF; + WHEN 4 THEN -- max + IF i_dp.next_rate < i_destination.next_rate THEN + i_profile.destination_next_rate:=i_destination.next_rate::varchar; --FIXED + i_profile.destination_initial_rate:=i_destination.initial_rate::varchar; + ELSE + i_profile.destination_next_rate:=(COALESCE(i_destination.dp_margin_fixed,0)+i_dp.next_rate*(1+COALESCE(i_destination.dp_margin_percent,0)))::varchar; -- DYNAMIC + i_profile.destination_initial_rate:=(COALESCE(i_destination.dp_margin_fixed,0)+i_dp.initial_rate*(1+COALESCE(i_destination.dp_margin_percent,0)))::varchar; + END IF; + ELSE + -- + end case; + + + + /* time limiting START */ + --SELECT INTO STRICT v_c_acc * FROM billing.accounts WHERE id=v_customer_auth.account_id; + --SELECT INTO STRICT v_v_acc * FROM billing.accounts WHERE id=v_dialpeer.account_id; + + + if i_profile.time_limit is null then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> process_gw: customer time limit is not set, calculating',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + IF (i_customer_acc.balance-i_customer_acc.min_balance)-i_destination.connect_fee-i_destination.initial_rate/60*i_destination.initial_interval<0 THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> process_gw: No enough customer balance even for first billing interval. rejecting',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + i_profile.disconnect_code_id=8000; --Not enough customer balance + RETURN i_profile; + ELSIF i_destination.next_rate!=0 AND i_destination.next_interval!=0 THEN + i_profile.time_limit = (i_destination.initial_interval+ + LEAST(FLOOR(((i_customer_acc.balance-i_customer_acc.min_balance)-i_destination.connect_fee-i_destination.initial_rate/60*i_destination.initial_interval)/ + (i_destination.next_rate/60*i_destination.next_interval)),24e6)::integer*i_destination.next_interval)::integer; + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> process_gw: customer time limit: %',EXTRACT(MILLISECOND from v_end-v_start), i_profile.time_limit; + /*}dbg*/ + ELSE /* DST rates is 0, allowing maximum call length */ + i_profile.time_limit = COALESCE(i_customer_acc.max_call_duration, i_max_call_length)::integer; + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> process_gw: DST rate is 0. customer time limit set to max value: %',EXTRACT(MILLISECOND from v_end-v_start), i_profile.time_limit; + /*}dbg*/ + end IF; + end if; + + IF (i_vendor_acc.max_balance-i_vendor_acc.balance)-i_dp.connect_fee <0 THEN /* No enough balance, skipping this profile */ + v_vendor_allowtime:=0; + return null; + ELSIF (i_vendor_acc.max_balance-i_vendor_acc.balance)-i_dp.connect_fee-i_dp.initial_rate/60*i_dp.initial_interval<0 THEN /* No enough balance even for first billing interval - skipping this profile */ + return null; + ELSIF i_dp.next_rate!=0 AND i_dp.next_interval!=0 THEN /* DP rates is not zero, calculating limit */ + v_vendor_allowtime:=i_dp.initial_interval+ + LEAST(FLOOR(((i_vendor_acc.max_balance-i_vendor_acc.balance)-i_dp.connect_fee-i_dp.initial_rate/60*i_dp.initial_interval)/ + (i_dp.next_rate/60*i_dp.next_interval)),24e6)::integer*i_dp.next_interval; + ELSE /* DP rates is 0, allowing maximum call length */ + v_vendor_allowtime:=COALESCE(i_vendor_acc.max_call_duration, i_max_call_length); + end IF; + + i_profile.time_limit=LEAST( + COALESCE(i_customer_acc.max_call_duration, i_max_call_length)::integer, + COALESCE(i_vendor_acc.max_call_duration, i_max_call_length)::integer, + v_vendor_allowtime, + i_profile.time_limit + )::integer; + + + /* number rewriting _After_ routing */ + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DP. Before rewrite src_prefix: % , dst_prefix: %',EXTRACT(MILLISECOND from v_end-v_start),i_profile.src_prefix_out,i_profile.dst_prefix_out; + /*}dbg*/ + i_profile.dst_prefix_out=yeti_ext.regexp_replace_rand(i_profile.dst_prefix_out,i_dp.dst_rewrite_rule,i_dp.dst_rewrite_result); + i_profile.src_prefix_out=yeti_ext.regexp_replace_rand(i_profile.src_prefix_out,i_dp.src_rewrite_rule,i_dp.src_rewrite_result); + i_profile.src_name_out=yeti_ext.regexp_replace_rand(i_profile.src_name_out,i_dp.src_name_rewrite_rule,i_dp.src_name_rewrite_result, true); + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DP. After rewrite src_prefix: % , dst_prefix: %',EXTRACT(MILLISECOND from v_end-v_start),i_profile.src_prefix_out,i_profile.dst_prefix_out; + /*}dbg*/ + + /* + get termination gw data + */ + --SELECT into v_dst_gw * from class4.gateways WHERE id=v_dialpeer.gateway_id; + --SELECT into v_orig_gw * from class4.gateways WHERE id=v_customer_auth.gateway_id; + --vendor gw + if i_vendor_gw.termination_capacity is not null then + i_profile.legb_res:=i_profile.legb_res||'5:'||i_vendor_gw.id::varchar||':'||i_vendor_gw.termination_capacity::varchar||':1;'; + end if; + + + /* + numberlist processing _After_ routing _IN_ termination GW + */ + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW. Before numberlist processing src_prefix: % , dst_prefix: %',EXTRACT(MILLISECOND from v_end-v_start),i_profile.src_prefix_out,i_profile.dst_prefix_out; + /*}dbg*/ + + + ----- DST Numberlist processing------------------------------------------------------------------------------------------------------- + IF i_vendor_gw.termination_dst_numberlist_id is not null then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW DST Numberlist processing. Lookup by key: %',EXTRACT(MILLISECOND from v_end-v_start), i_profile.dst_prefix_out; + /*}dbg*/ + + select into v_termination_numberlist * from class4.numberlists where id=i_vendor_gw.termination_dst_numberlist_id; + CASE v_termination_numberlist.mode_id + when 1 then -- strict match + select into v_termination_numberlist_item * from class4.numberlist_items ni + where + ni.numberlist_id=i_vendor_gw.termination_dst_numberlist_id and + ni.key=i_profile.dst_prefix_out + limit 1; + when 2 then -- prefix match + select into v_termination_numberlist_item * from class4.numberlist_items ni + where + ni.numberlist_id=i_vendor_gw.termination_dst_numberlist_id and + prefix_range(ni.key)@>prefix_range(i_profile.dst_prefix_out) and + length(i_profile.dst_prefix_out) between ni.number_min_length and ni.number_max_length + order by length(prefix_range(ni.key)) desc + limit 1; + when 3 then -- random + select into v_termination_numberlist_size count(*) from class4.numberlist_items where numberlist_id=i_vendor_gw.termination_dst_numberlist_id; + select into v_termination_numberlist_item * from class4.numberlist_items ni + where ni.numberlist_id=i_vendor_gw.termination_dst_numberlist_id order by ni.id OFFSET floor(random()*v_termination_numberlist_size) limit 1; + END CASE; + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW DST Numberlist. key found: %',EXTRACT(MILLISECOND from v_end-v_start), row_to_json(v_termination_numberlist_item); + /*}dbg*/ + + IF v_termination_numberlist_item.action_id is not null and v_termination_numberlist_item.action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW DST Numberlist. Drop by key action. Skipping route. Key: %',EXTRACT(MILLISECOND from v_end-v_start), v_termination_numberlist_item.key; + /*}dbg*/ + RETURN null; + ELSIF v_termination_numberlist_item.action_id is not null and v_termination_numberlist_item.action_id=2 then + i_profile.src_prefix_out=yeti_ext.regexp_replace_rand( + i_profile.src_prefix_out, + v_termination_numberlist_item.src_rewrite_rule, + v_termination_numberlist_item.src_rewrite_result + ); + + i_profile.dst_prefix_out=yeti_ext.regexp_replace_rand( + i_profile.dst_prefix_out, + v_termination_numberlist_item.dst_rewrite_rule, + v_termination_numberlist_item.dst_rewrite_result + ); + ELSIF v_termination_numberlist_item.action_id is null and v_termination_numberlist.default_action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW DST Numberlist. Drop by default action. Skipping route',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + RETURN null; + ELSIF v_termination_numberlist_item.action_id is null and v_termination_numberlist.default_action_id=2 then + i_profile.src_prefix_out=yeti_ext.regexp_replace_rand( + i_profile.src_prefix_out, + v_termination_numberlist.default_src_rewrite_rule, + v_termination_numberlist.default_src_rewrite_result + ); + + i_profile.dst_prefix_out=yeti_ext.regexp_replace_rand( + i_profile.dst_prefix_out, + v_termination_numberlist.default_dst_rewrite_rule, + v_termination_numberlist.default_dst_rewrite_result + ); + END IF; + END IF; + + + + ----- SRC Numberlist processing------------------------------------------------------------------------------------------------------- + IF i_vendor_gw.termination_src_numberlist_id is not null then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW SRC Numberlist processing. Lookup by key: %',EXTRACT(MILLISECOND from v_end-v_start), i_profile.src_prefix_out; + /*}dbg*/ + + select into v_termination_numberlist * from class4.numberlists where id=i_vendor_gw.termination_src_numberlist_id; + CASE v_termination_numberlist.mode_id + when 1 then -- strict match + select into v_termination_numberlist_item * from class4.numberlist_items ni + where + ni.numberlist_id=i_vendor_gw.termination_src_numberlist_id and + ni.key=i_profile.src_prefix_out + limit 1; + when 2 then -- prefix match + select into v_termination_numberlist_item * from class4.numberlist_items ni + where + ni.numberlist_id=i_vendor_gw.termination_src_numberlist_id and + prefix_range(ni.key)@>prefix_range(i_profile.src_prefix_out) and + length(i_profile.src_prefix_out) between ni.number_min_length and ni.number_max_length + order by length(prefix_range(ni.key)) desc + limit 1; + when 3 then -- random + select into v_termination_numberlist_size count(*) from class4.numberlist_items where numberlist_id=i_vendor_gw.termination_src_numberlist_id; + select into v_termination_numberlist_item * from class4.numberlist_items ni + where ni.numberlist_id=i_vendor_gw.termination_src_numberlist_id order by ni.id OFFSET floor(random()*v_termination_numberlist_size) limit 1; + END CASE; + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW SRC Numberlist. key found: %',EXTRACT(MILLISECOND from v_end-v_start), row_to_json(v_termination_numberlist_item); + /*}dbg*/ + + IF v_termination_numberlist_item.action_id is not null and v_termination_numberlist_item.action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW SRC Numberlist. Drop by key action. Skipping route. Key: %',EXTRACT(MILLISECOND from v_end-v_start), v_termination_numberlist_item.key; + /*}dbg*/ + RETURN null; + ELSIF v_termination_numberlist_item.action_id is not null and v_termination_numberlist_item.action_id=2 then + i_profile.src_prefix_out=yeti_ext.regexp_replace_rand( + i_profile.src_prefix_out, + v_termination_numberlist_item.src_rewrite_rule, + v_termination_numberlist_item.src_rewrite_result + ); + + i_profile.dst_prefix_out=yeti_ext.regexp_replace_rand( + i_profile.dst_prefix_out, + v_termination_numberlist_item.dst_rewrite_rule, + v_termination_numberlist_item.dst_rewrite_result + ); + ELSIF v_termination_numberlist_item.action_id is null and v_termination_numberlist.default_action_id=1 then + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW DST Numberlist. Drop by default action. Skipping route.',EXTRACT(MILLISECOND from v_end-v_start); + /*}dbg*/ + -- drop by default + RETURN null; + ELSIF v_termination_numberlist_item.action_id is null and v_termination_numberlist.default_action_id=2 then + i_profile.src_prefix_out=yeti_ext.regexp_replace_rand( + i_profile.src_prefix_out, + v_termination_numberlist.default_src_rewrite_rule, + v_termination_numberlist.default_src_rewrite_result + ); + + i_profile.dst_prefix_out=yeti_ext.regexp_replace_rand( + i_profile.dst_prefix_out, + v_termination_numberlist.default_dst_rewrite_rule, + v_termination_numberlist.default_dst_rewrite_result + ); + END IF; + END IF; + + + + /* + number rewriting _After_ routing _IN_ termination GW + */ + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW. Before rewrite src_prefix: % , dst_prefix: %',EXTRACT(MILLISECOND from v_end-v_start),i_profile.src_prefix_out,i_profile.dst_prefix_out; + /*}dbg*/ + i_profile.dst_prefix_out=yeti_ext.regexp_replace_rand(i_profile.dst_prefix_out,i_vendor_gw.dst_rewrite_rule,i_vendor_gw.dst_rewrite_result); + i_profile.src_prefix_out=yeti_ext.regexp_replace_rand(i_profile.src_prefix_out,i_vendor_gw.src_rewrite_rule,i_vendor_gw.src_rewrite_result); + i_profile.src_name_out=yeti_ext.regexp_replace_rand(i_profile.src_name_out,i_vendor_gw.src_name_rewrite_rule,i_vendor_gw.src_name_rewrite_result, true); + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW. After rewrite src_prefix: % , dst_prefix: %',EXTRACT(MILLISECOND from v_end-v_start),i_profile.src_prefix_out,i_profile.dst_prefix_out; + /*}dbg*/ + + + IF cardinality(i_diversion) > 0 AND i_vendor_gw.diversion_send_mode_id > 1 THEN + v_diversion = yeti_ext.regexp_replace_rand(i_diversion, i_vendor_gw.diversion_rewrite_rule, i_vendor_gw.diversion_rewrite_result); + + IF i_vendor_gw.diversion_send_mode_id = 2 AND i_vendor_gw.diversion_domain is not null AND i_vendor_gw.diversion_domain!='' THEN + /* Diversion as SIP URI */ + FOREACH v_diversion_header IN ARRAY v_diversion LOOP + v_bleg_append_headers_req = array_append( + v_bleg_append_headers_req, + format('Diversion: ', v_diversion_header, i_vendor_gw.diversion_domain)::varchar + ); + v_diversion_out = array_append( + v_diversion_out, + format('', v_diversion_header, i_vendor_gw.diversion_domain)::varchar + ); + END LOOP; + ELSIF i_vendor_gw.diversion_send_mode_id = 3 THEN + /* Diversion as TEL URI */ + FOREACH v_diversion_header IN ARRAY v_diversion LOOP + v_bleg_append_headers_req=array_append( + v_bleg_append_headers_req, + format('Diversion: ', v_diversion_header)::varchar + ); + v_diversion_out = array_append( + v_diversion_out, + format('', v_diversion_header)::varchar + ); + END LOOP; + END IF; + + /* Field for CDR */ + i_profile.diversion_out = array_to_string(v_diversion_out, ','); + END IF; + + CASE i_vendor_gw.privacy_mode_id + WHEN 0 THEN + -- do nothing + WHEN 1 THEN + IF cardinality(array_remove(i_privacy,'none')) > 0 THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW Privacy % requested but privacy_mode_is %. Skipping gw.',EXTRACT(MILLISECOND from v_end-v_start), i_privacy, i_vendor_gw.privacy_mode_id; + /*}dbg*/ + return null; + END IF; + WHEN 2 THEN + IF 'critical' = ANY(i_privacy) THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW Privacy % requested but privacy_mode_is %. Skipping gw.',EXTRACT(MILLISECOND from v_end-v_start), i_privacy, i_vendor_gw.privacy_mode_id; + /*}dbg*/ + return null; + END IF; + WHEN 3 THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW Privacy % requested, privacy_mode_is %. Applying privacy.',EXTRACT(MILLISECOND from v_end-v_start), i_privacy, i_vendor_gw.privacy_mode_id; + /*}dbg*/ + IF 'id' = ANY(i_privacy) OR 'user' = ANY(i_privacy) THEN + i_profile.src_prefix_out='anonymous'; + i_profile.src_name_out='Anonymous'; + v_from_domain = 'anonymous.invalid'; + END IF; + IF 'id' = ANY(i_privacy) OR 'header' = ANY(i_privacy) THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW Privacy % requested, privacy_mode_is %. removing PAI/PPI headers.',EXTRACT(MILLISECOND from v_end-v_start), i_privacy, i_vendor_gw.privacy_mode_id; + /*}dbg*/ + v_allow_pai = false; + END IF; + i_profile.privacy_out = array_to_string(i_privacy,';'); + v_bleg_append_headers_req = array_append(v_bleg_append_headers_req, format('Privacy: %s', array_to_string(i_privacy,';')::varchar)); + WHEN 4 THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW Privacy % requested, privacy_mode_is %. forwarding.',EXTRACT(MILLISECOND from v_end-v_start), i_privacy, i_vendor_gw.privacy_mode_id; + /*}dbg*/ + IF cardinality(i_privacy)>0 THEN + i_profile.privacy_out = array_to_string(i_privacy,';'); + v_bleg_append_headers_req = array_append(v_bleg_append_headers_req, format('Privacy: %s', array_to_string(i_privacy,';')::varchar)); + END IF; + WHEN 5 THEN + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> GW Privacy % requested, privacy_mode_is %. forwarding with anonymous From.',EXTRACT(MILLISECOND from v_end-v_start), i_privacy, i_vendor_gw.privacy_mode_id; + /*}dbg*/ + IF 'id' = ANY(i_privacy) or 'user' = ANY(i_privacy) THEN + i_profile.src_prefix_out='anonymous'; + i_profile.src_name_out='Anonymous'; + v_from_domain = 'anonymous.invalid'; + i_profile.privacy_out = array_to_string(i_privacy,';'); + v_bleg_append_headers_req = array_append(v_bleg_append_headers_req, format('Privacy: %s', array_to_string(i_privacy,';')::varchar)); + END IF; + END CASE; + + IF v_allow_pai THEN + -- only if privacy mode allows to send PAI + IF i_vendor_gw.pai_send_mode_id = 1 THEN + -- TEL URI + v_pai_out = array_append(v_pai_out, format('', i_profile.src_prefix_out)::varchar); + v_bleg_append_headers_req = array_append(v_bleg_append_headers_req, format('P-Asserted-Identity: ', i_profile.src_prefix_out)::varchar); + ELSIF i_vendor_gw.pai_send_mode_id = 2 and i_vendor_gw.pai_domain is not null and i_vendor_gw.pai_domain!='' THEN + -- SIP URL + v_pai_out = array_append(v_pai_out, format('', i_profile.src_prefix_out, i_vendor_gw.pai_domain)::varchar); + v_bleg_append_headers_req = array_append(v_bleg_append_headers_req, format('P-Asserted-Identity: ', i_profile.src_prefix_out, i_vendor_gw.pai_domain)::varchar); + ELSIF i_vendor_gw.pai_send_mode_id = 3 and i_vendor_gw.pai_domain is not null and i_vendor_gw.pai_domain!='' THEN + v_pai_out = array_append(v_pai_out, format('', i_profile.src_prefix_out, i_vendor_gw.pai_domain)::varchar); + v_bleg_append_headers_req = array_append(v_bleg_append_headers_req, format('P-Asserted-Identity: ', i_profile.src_prefix_out, i_vendor_gw.pai_domain)::varchar); + ELSIF i_vendor_gw.pai_send_mode_id = 4 THEN + -- relay + FOREACH v_pai IN ARRAY i_pai LOOP + v_bleg_append_headers_req = array_append(v_bleg_append_headers_req, format('P-Asserted-Identity: %s', v_pai)::varchar); + END LOOP; + v_pai_out = i_pai; + IF i_ppi is not null and i_ppi!='' THEN + v_bleg_append_headers_req = array_append(v_bleg_append_headers_req, format('P-Preferred-Identity: %s', i_ppi)::varchar); + i_profile.ppi_out = i_ppi; + END IF; + END IF; + i_profile.pai_out = NULLIF(array_to_string(v_pai_out, ','),''); + END IF; + + IF i_vendor_gw.stir_shaken_mode_id = 1 AND COALESCE(i_profile.ss_attest_id,0) > 0 THEN + -- insert signature + i_profile.ss_crt_id = i_vendor_gw.stir_shaken_crt_id; + i_profile.ss_otn = i_profile.src_prefix_routing; + i_profile.ss_dtn = i_profile.dst_prefix_routing; + i_profile.legb_ss_status_id = i_profile.ss_attest_id; + END IF; + + v_bleg_append_headers_req = array_cat(v_bleg_append_headers_req, string_to_array(i_vendor_gw.term_append_headers_req,'\r\n')::varchar[]); + i_profile.append_headers_req = array_to_string(v_bleg_append_headers_req,'\r\n'); + + i_profile.aleg_append_headers_req=i_customer_gw.orig_append_headers_req; + + i_profile.next_hop_1st_req=i_vendor_gw.auth_enabled; -- use low delay dns srv if auth enabled + i_profile.next_hop:=i_vendor_gw.term_next_hop; + i_profile.aleg_next_hop:=i_customer_gw.orig_next_hop; + -- i_profile.next_hop_for_replies:=v_dst_gw.term_next_hop_for_replies; + + i_profile.dlg_nat_handling=i_customer_gw.dialog_nat_handling; + + i_profile.call_id:=''; -- Generation by sems + + i_profile.enable_auth:=i_vendor_gw.auth_enabled; + i_profile.auth_pwd:=i_vendor_gw.auth_password; + i_profile.auth_user:=i_vendor_gw.auth_user; + i_profile.enable_aleg_auth:=false; + i_profile.auth_aleg_pwd:=''; + i_profile.auth_aleg_user:=''; + + if i_profile.enable_auth then + v_from_user=COALESCE(i_vendor_gw.auth_from_user,i_profile.src_prefix_out,''); + -- may be it already defined by privacy logic + v_from_domain=COALESCE(v_from_domain, i_vendor_gw.auth_from_domain, '$Oi'); + else + v_from_user=COALESCE(i_profile.src_prefix_out,''); + if i_vendor_gw.preserve_anonymous_from_domain and i_profile.from_domain='anonymous.invalid' then + v_from_domain='anonymous.invalid'; + else + v_from_domain=COALESCE(v_from_domain, '$Oi'); + end if; + end if; + + v_to_username = yeti_ext.regexp_replace_rand(i_profile.dst_prefix_out, i_vendor_gw.to_rewrite_rule, i_vendor_gw.to_rewrite_result); + + if i_vendor_gw.sip_schema_id = 1 then + v_schema='sip'; + elsif i_vendor_gw.sip_schema_id = 2 then + v_schema='sips'; + elsif i_vendor_gw.sip_schema_id = 3 then + v_schema='sip'; + -- user=phone param require e.164 with + in username, but we are not forcing it + v_from_uri_params = array_append(v_from_uri_params,'user=phone'); + v_to_uri_params = array_append(v_to_uri_params,'user=phone'); + v_ruri_params = array_append(v_ruri_params,'user=phone'); + else + RAISE exception 'Unknown termination gateway % SIP schema %', i_vendor_gw.id, i_vendor_gw.sip_schema_id; + end if; + + if i_vendor_gw.send_lnp_information and i_profile.lrn is not null then + if i_profile.lrn=i_profile.dst_prefix_routing then -- number not ported, but request was successf we musr add ;npdi=yes; + v_ruri_user_params = array_append(v_ruri_user_params, 'npdi=yes'); + i_profile.lrn=nullif(i_profile.dst_prefix_routing,i_profile.lrn); -- clear lnr field if number not ported; + else -- if number ported + v_ruri_user_params = array_append(v_ruri_user_params, 'rn='||i_profile.lrn); + v_ruri_user_params = array_append(v_ruri_user_params, 'npdi=yes'); + end if; + end if; + + i_profile.registered_aor_mode_id = i_vendor_gw.registered_aor_mode_id; + if i_vendor_gw.registered_aor_mode_id > 0 then + i_profile.registered_aor_id=i_vendor_gw.id; + v_ruri_host = 'unknown.invalid'; + else + v_ruri_host = i_vendor_gw.host; + end if; + + i_profile."from" = switch21.build_uri(false, v_schema, i_profile.src_name_out, v_from_user, null, v_from_domain, null, v_from_uri_params); + + i_profile."to" = switch21.build_uri(false, v_schema, null, v_to_username, null, v_ruri_host, i_vendor_gw.port, v_to_uri_params); + i_profile.ruri = switch21.build_uri(true, v_schema, null, i_profile.dst_prefix_out, v_ruri_user_params, v_ruri_host, i_vendor_gw.port, v_ruri_params); + + i_profile.bleg_transport_protocol_id:=i_vendor_gw.transport_protocol_id; + i_profile.bleg_protocol_priority_id:=i_vendor_gw.network_protocol_priority_id; + + i_profile.aleg_media_encryption_mode_id:=i_customer_gw.media_encryption_mode_id; + i_profile.bleg_media_encryption_mode_id:=i_vendor_gw.media_encryption_mode_id; + + IF (i_vendor_gw.term_use_outbound_proxy ) THEN + i_profile.outbound_proxy:=v_schema||':'||i_vendor_gw.term_outbound_proxy; + i_profile.force_outbound_proxy:=i_vendor_gw.term_force_outbound_proxy; + i_profile.bleg_outbound_proxy_transport_protocol_id:=i_vendor_gw.term_proxy_transport_protocol_id; + ELSE + i_profile.outbound_proxy:=NULL; + i_profile.force_outbound_proxy:=false; + END IF; + + IF (i_customer_gw.orig_use_outbound_proxy ) THEN + i_profile.aleg_force_outbound_proxy:=i_customer_gw.orig_force_outbound_proxy; + i_profile.aleg_outbound_proxy=v_schema||':'||i_customer_gw.orig_outbound_proxy; + i_profile.aleg_outbound_proxy_transport_protocol_id:=i_customer_gw.orig_proxy_transport_protocol_id; + else + i_profile.aleg_force_outbound_proxy:=FALSE; + i_profile.aleg_outbound_proxy=NULL; + end if; + + i_profile.aleg_policy_id=i_customer_gw.orig_disconnect_policy_id; + i_profile.bleg_policy_id=i_vendor_gw.term_disconnect_policy_id; + + i_profile.transit_headers_a2b:=i_customer_gw.transit_headers_from_origination||';'||i_vendor_gw.transit_headers_from_origination; + i_profile.transit_headers_b2a:=i_vendor_gw.transit_headers_from_termination||';'||i_customer_gw.transit_headers_from_termination; + + i_profile.sdp_filter_type_id:=0; + i_profile.sdp_filter_list:=''; + + i_profile.sdp_alines_filter_type_id:=i_vendor_gw.sdp_alines_filter_type_id; + i_profile.sdp_alines_filter_list:=i_vendor_gw.sdp_alines_filter_list; + + i_profile.enable_session_timer=i_vendor_gw.sst_enabled; + i_profile.session_expires =i_vendor_gw.sst_session_expires; + i_profile.minimum_timer:=i_vendor_gw.sst_minimum_timer; + i_profile.maximum_timer:=i_vendor_gw.sst_maximum_timer; + i_profile.session_refresh_method_id:=i_vendor_gw.session_refresh_method_id; + i_profile.accept_501_reply:=i_vendor_gw.sst_accept501; + + i_profile.enable_aleg_session_timer=i_customer_gw.sst_enabled; + i_profile.aleg_session_expires:=i_customer_gw.sst_session_expires; + i_profile.aleg_minimum_timer:=i_customer_gw.sst_minimum_timer; + i_profile.aleg_maximum_timer:=i_customer_gw.sst_maximum_timer; + i_profile.aleg_session_refresh_method_id:=i_customer_gw.session_refresh_method_id; + i_profile.aleg_accept_501_reply:=i_customer_gw.sst_accept501; + + i_profile.reply_translations:=''; + i_profile.disconnect_code_id:=NULL; + i_profile.enable_rtprelay:=i_vendor_gw.proxy_media OR i_customer_gw.proxy_media; + + i_profile.rtprelay_interface:=i_vendor_gw.rtp_interface_name; + i_profile.aleg_rtprelay_interface:=i_customer_gw.rtp_interface_name; + + i_profile.outbound_interface:=i_vendor_gw.sip_interface_name; + i_profile.aleg_outbound_interface:=i_customer_gw.sip_interface_name; + + i_profile.bleg_force_symmetric_rtp:=i_vendor_gw.force_symmetric_rtp; + i_profile.bleg_symmetric_rtp_nonstop=i_vendor_gw.symmetric_rtp_nonstop; + + i_profile.aleg_force_symmetric_rtp:=i_customer_gw.force_symmetric_rtp; + i_profile.aleg_symmetric_rtp_nonstop=i_customer_gw.symmetric_rtp_nonstop; + + i_profile.bleg_rtp_ping=i_vendor_gw.rtp_ping; + i_profile.aleg_rtp_ping=i_customer_gw.rtp_ping; + + i_profile.bleg_relay_options = i_vendor_gw.relay_options; + i_profile.aleg_relay_options = i_customer_gw.relay_options; + + + i_profile.filter_noaudio_streams = i_vendor_gw.filter_noaudio_streams OR i_customer_gw.filter_noaudio_streams; + i_profile.force_one_way_early_media = i_vendor_gw.force_one_way_early_media OR i_customer_gw.force_one_way_early_media; + i_profile.aleg_relay_reinvite = i_vendor_gw.relay_reinvite; + i_profile.bleg_relay_reinvite = i_customer_gw.relay_reinvite; + + i_profile.aleg_relay_hold = i_vendor_gw.relay_hold; + i_profile.bleg_relay_hold = i_customer_gw.relay_hold; + + i_profile.aleg_relay_prack = i_vendor_gw.relay_prack; + i_profile.bleg_relay_prack = i_customer_gw.relay_prack; + i_profile.aleg_rel100_mode_id = i_customer_gw.rel100_mode_id; + i_profile.bleg_rel100_mode_id = i_vendor_gw.rel100_mode_id; + + i_profile.rtp_relay_timestamp_aligning=i_vendor_gw.rtp_relay_timestamp_aligning OR i_customer_gw.rtp_relay_timestamp_aligning; + i_profile.allow_1xx_wo2tag=i_vendor_gw.allow_1xx_without_to_tag OR i_customer_gw.allow_1xx_without_to_tag; + + i_profile.aleg_sdp_c_location_id=i_customer_gw.sdp_c_location_id; + i_profile.bleg_sdp_c_location_id=i_vendor_gw.sdp_c_location_id; + i_profile.trusted_hdrs_gw=false; + + + + i_profile.aleg_codecs_group_id:=i_customer_gw.codec_group_id; + i_profile.bleg_codecs_group_id:=i_vendor_gw.codec_group_id; + i_profile.aleg_single_codec_in_200ok:=i_customer_gw.single_codec_in_200ok; + i_profile.bleg_single_codec_in_200ok:=i_vendor_gw.single_codec_in_200ok; + i_profile.try_avoid_transcoding = i_customer_gw.try_avoid_transcoding; + i_profile.ringing_timeout=i_vendor_gw.ringing_timeout; + i_profile.dead_rtp_time=GREATEST(i_vendor_gw.rtp_timeout,i_customer_gw.rtp_timeout); + i_profile.invite_timeout=i_vendor_gw.sip_timer_b; + i_profile.srv_failover_timeout=i_vendor_gw.dns_srv_failover_timer; + i_profile.fake_180_timer=i_vendor_gw.fake_180_timer; + i_profile.rtp_force_relay_cn=i_vendor_gw.rtp_force_relay_cn OR i_customer_gw.rtp_force_relay_cn; + i_profile.patch_ruri_next_hop=i_vendor_gw.resolve_ruri; + + i_profile.aleg_sensor_id=i_customer_gw.sensor_id; + i_profile.aleg_sensor_level_id=i_customer_gw.sensor_level_id; + i_profile.bleg_sensor_id=i_vendor_gw.sensor_id; + i_profile.bleg_sensor_level_id=i_vendor_gw.sensor_level_id; + + i_profile.aleg_dtmf_send_mode_id=i_customer_gw.dtmf_send_mode_id; + i_profile.aleg_dtmf_recv_modes=i_customer_gw.dtmf_receive_mode_id; + i_profile.bleg_dtmf_send_mode_id=i_vendor_gw.dtmf_send_mode_id; + i_profile.bleg_dtmf_recv_modes=i_vendor_gw.dtmf_receive_mode_id; + + + i_profile.aleg_rtp_filter_inband_dtmf=false; + i_profile.bleg_rtp_filter_inband_dtmf=false; + + if i_customer_gw.rx_inband_dtmf_filtering_mode_id=3 then -- enable filtering + i_profile.aleg_rtp_filter_inband_dtmf=true; + elsif i_customer_gw.rx_inband_dtmf_filtering_mode_id=1 then -- inherit + if i_vendor_gw.tx_inband_dtmf_filtering_mode_id in (1,2) then -- inherit or disable filtering + i_profile.aleg_rtp_filter_inband_dtmf=false; + elsif i_vendor_gw.tx_inband_dtmf_filtering_mode_id = 3 then -- enable filtering + i_profile.aleg_rtp_filter_inband_dtmf=true; + end if; + end if; + + + if i_vendor_gw.rx_inband_dtmf_filtering_mode_id=3 then -- enable filtering + i_profile.bleg_rtp_filter_inband_dtmf=true; + elsif i_vendor_gw.rx_inband_dtmf_filtering_mode_id=1 then -- inherit + if i_customer_gw.tx_inband_dtmf_filtering_mode_id in (1,2) then -- inherit or disable filtering + i_profile.bleg_rtp_filter_inband_dtmf=false; + elsif i_customer_gw.tx_inband_dtmf_filtering_mode_id = 3 then -- enable filtering + i_profile.bleg_rtp_filter_inband_dtmf=true; + end if; + end if; + + i_profile.aleg_rtp_acl = i_customer_gw.rtp_acl; + i_profile.bleg_rtp_acl = i_vendor_gw.rtp_acl; + + i_profile.rtprelay_force_dtmf_relay=i_vendor_gw.force_dtmf_relay; + i_profile.rtprelay_dtmf_detection=NOT i_vendor_gw.force_dtmf_relay; + i_profile.rtprelay_dtmf_filtering=NOT i_vendor_gw.force_dtmf_relay; + i_profile.bleg_max_30x_redirects = i_vendor_gw.max_30x_redirects; + i_profile.bleg_max_transfers = i_vendor_gw.max_transfers; + + + i_profile.aleg_relay_update=i_customer_gw.relay_update; + i_profile.bleg_relay_update=i_vendor_gw.relay_update; + i_profile.suppress_early_media=i_customer_gw.suppress_early_media OR i_vendor_gw.suppress_early_media; + + i_profile.bleg_radius_acc_profile_id=i_vendor_gw.radius_accounting_profile_id; + i_profile.bleg_force_cancel_routeset=i_vendor_gw.force_cancel_routeset; + + /*dbg{*/ + v_end:=clock_timestamp(); + RAISE NOTICE '% ms -> DP. Finished: % ',EXTRACT(MILLISECOND from v_end-v_start),row_to_json(i_profile,true); + /*}dbg*/ + RETURN i_profile; +END; +$_$; + + + set search_path TO switch21; + SELECT * from switch21.preprocess_all(); + set search_path TO gui, public, switch, billing, class4, runtime_stats, sys, logs, data_import; + + } + end +end diff --git a/db/migrate/20240622173924_package_billing_bill_cdr_batch.rb b/db/migrate/20240622173924_package_billing_bill_cdr_batch.rb new file mode 100644 index 000000000..b116f5673 --- /dev/null +++ b/db/migrate/20240622173924_package_billing_bill_cdr_batch.rb @@ -0,0 +1,249 @@ +class PackageBillingBillCdrBatch < ActiveRecord::Migration[7.0] + def up + execute %q{ + + alter type billing.cdr_v2 + add attribute package_counter_id bigint, + add attribute customer_duration integer; + + CREATE or replace FUNCTION billing.bill_cdr_batch(i_batch_id bigint, i_data text, i_data_version integer DEFAULT 2) RETURNS void + LANGUAGE plpgsql SECURITY DEFINER COST 10 + AS $$ +DECLARE + v_batch_data billing.cdr_v2; + v_package_data record; + v_c_acc_data record; + v_v_acc_data record; + v_dp_stats_data record; + v_gw_stats_data record; + _j_data json; +BEGIN + begin + _j_data:=i_data::json; + exception + when others then + RAISE exception 'billing.bill_cdr_batch: Invalid json payload'; + end; + + BEGIN + insert into billing.cdr_batches(id) values(i_batch_id); + exception + WHEN unique_violation then + RAISE WARNING 'billing.bill_cdr_batch: Data batch % already billed. Skip it.',i_batch_id; + return; -- batch already processed; + end ; + + if i_data_version=2 then + --- ok; + else + RAISE EXCEPTION 'billing.bill_cdr_batch: No logic for this data version'; + end if; + + for v_c_acc_data in + select + customer_acc_id, + sum(customer_price) as customer_price, + destination_reverse_billing + from json_populate_recordset(null::billing.cdr_v2,_j_data) + where customer_acc_id is not null AND customer_price > 0 AND package_counter_id is null + group by customer_acc_id, destination_reverse_billing + LOOP + IF v_c_acc_data.destination_reverse_billing IS NULL OR v_c_acc_data.destination_reverse_billing = false THEN + UPDATE billing.accounts SET balance = balance - COALESCE(v_c_acc_data.customer_price,0) WHERE id = v_c_acc_data.customer_acc_id; + ELSE + UPDATE billing.accounts SET balance = balance + COALESCE(v_c_acc_data.customer_price,0) WHERE id = v_c_acc_data.customer_acc_id; + END IF; + END LOOP; + + for v_package_data in + select + package_counter_id, + sum(customer_duration) as customer_duration + from json_populate_recordset(null::billing.cdr_v2,_j_data) + where customer_duration is not null AND package_counter_id is not null + group by package_counter_id + LOOP + UPDATE billing.package_counters SET duration = duration-v_package_data.customer_duration WHERE id=v_package_data.package_counter_id; + END LOOP; + + for v_v_acc_data in + select + vendor_acc_id, + sum(vendor_price) as vendor_price, + dialpeer_reverse_billing + from json_populate_recordset(null::billing.cdr_v2,_j_data) + where vendor_acc_id is not null AND vendor_price>0 + group by vendor_acc_id, dialpeer_reverse_billing + LOOP + IF v_v_acc_data.dialpeer_reverse_billing IS NULL OR v_v_acc_data.dialpeer_reverse_billing = false THEN + UPDATE billing.accounts SET balance = balance + COALESCE(v_v_acc_data.vendor_price,0) WHERE id = v_v_acc_data.vendor_acc_id; + ELSE + UPDATE billing.accounts SET balance = balance - COALESCE(v_v_acc_data.vendor_price,0) WHERE id = v_v_acc_data.vendor_acc_id; + END IF; + END LOOP; + + FOR v_dp_stats_data IN + SELECT + dialpeer_id, + sum(duration)::integer as duration, + count(*)::integer as calls, + (count(*) FILTER(WHERE duration>0))::integer as successful_calls, + (count(*) FILTER(WHERE duration=0))::integer as failed_calls + FROM json_populate_recordset(null::billing.cdr_v2,_j_data) + WHERE dialpeer_id is not null AND duration is not null AND duration >= 0 /* Negative duration should not exists */ + GROUP BY dialpeer_id + LOOP + perform runtime_stats.update_dp( + v_dp_stats_data.dialpeer_id, + v_dp_stats_data.calls, + v_dp_stats_data.successful_calls, + v_dp_stats_data.failed_calls, + v_dp_stats_data.duration + ); + END LOOP; + + FOR v_gw_stats_data IN + SELECT + term_gw_id, + sum(duration)::integer as duration, + count(*)::integer as calls, + (count(*) FILTER(WHERE duration>0))::integer as successful_calls, + (count(*) FILTER(WHERE duration=0))::integer as failed_calls + FROM json_populate_recordset(null::billing.cdr_v2,_j_data) + WHERE term_gw_id is not null AND duration is not null AND duration >= 0 /* Negative duration should not exists */ + GROUP BY term_gw_id + LOOP + perform runtime_stats.update_gw( + v_gw_stats_data.term_gw_id, + v_gw_stats_data.calls, + v_gw_stats_data.successful_calls, + v_gw_stats_data.failed_calls, + v_gw_stats_data.duration + ); + END LOOP; + +END; +$$; + + } + end + + + def down + execute %q{ + + CREATE or replace FUNCTION billing.bill_cdr_batch(i_batch_id bigint, i_data text, i_data_version integer DEFAULT 2) RETURNS void + LANGUAGE plpgsql SECURITY DEFINER COST 10 + AS $$ +DECLARE + v_batch_data billing.cdr_v2; + v_c_acc_data record; + v_v_acc_data record; + v_dp_stats_data record; + v_gw_stats_data record; + _j_data json; +BEGIN + begin + _j_data:=i_data::json; + exception + when others then + RAISE exception 'billing.bill_cdr_batch: Invalid json payload'; + end; + + BEGIN + insert into billing.cdr_batches(id) values(i_batch_id); + exception + WHEN unique_violation then + RAISE WARNING 'billing.bill_cdr_batch: Data batch % already billed. Skip it.',i_batch_id; + return; -- batch already processed; + end ; + + if i_data_version=2 then + --- ok; + else + RAISE EXCEPTION 'billing.bill_cdr_batch: No logic for this data version'; + end if; + + for v_c_acc_data in + select + customer_acc_id, + sum(customer_price) as customer_price, + destination_reverse_billing + from json_populate_recordset(null::billing.cdr_v2,_j_data) + where customer_acc_id is not null AND customer_price > 0 + group by customer_acc_id, destination_reverse_billing + LOOP + IF v_c_acc_data.destination_reverse_billing IS NULL OR v_c_acc_data.destination_reverse_billing = false THEN + UPDATE billing.accounts SET balance = balance - COALESCE(v_c_acc_data.customer_price,0) WHERE id = v_c_acc_data.customer_acc_id; + ELSE + UPDATE billing.accounts SET balance = balance + COALESCE(v_c_acc_data.customer_price,0) WHERE id = v_c_acc_data.customer_acc_id; + END IF; + END LOOP; + + for v_v_acc_data in + select + vendor_acc_id, + sum(vendor_price) as vendor_price, + dialpeer_reverse_billing + from json_populate_recordset(null::billing.cdr_v2,_j_data) + where vendor_acc_id is not null AND vendor_price>0 + group by vendor_acc_id, dialpeer_reverse_billing + LOOP + IF v_v_acc_data.dialpeer_reverse_billing IS NULL OR v_v_acc_data.dialpeer_reverse_billing = false THEN + UPDATE billing.accounts SET balance = balance + COALESCE(v_v_acc_data.vendor_price,0) WHERE id = v_v_acc_data.vendor_acc_id; + ELSE + UPDATE billing.accounts SET balance = balance - COALESCE(v_v_acc_data.vendor_price,0) WHERE id = v_v_acc_data.vendor_acc_id; + END IF; + END LOOP; + + FOR v_dp_stats_data IN + SELECT + dialpeer_id, + sum(duration)::integer as duration, + count(*)::integer as calls, + (count(*) FILTER(WHERE duration>0))::integer as successful_calls, + (count(*) FILTER(WHERE duration=0))::integer as failed_calls + FROM json_populate_recordset(null::billing.cdr_v2,_j_data) + WHERE dialpeer_id is not null AND duration is not null AND duration >= 0 /* Negative duration should not exists */ + GROUP BY dialpeer_id + LOOP + perform runtime_stats.update_dp( + v_dp_stats_data.dialpeer_id, + v_dp_stats_data.calls, + v_dp_stats_data.successful_calls, + v_dp_stats_data.failed_calls, + v_dp_stats_data.duration + ); + END LOOP; + + FOR v_gw_stats_data IN + SELECT + term_gw_id, + sum(duration)::integer as duration, + count(*)::integer as calls, + (count(*) FILTER(WHERE duration>0))::integer as successful_calls, + (count(*) FILTER(WHERE duration=0))::integer as failed_calls + FROM json_populate_recordset(null::billing.cdr_v2,_j_data) + WHERE term_gw_id is not null AND duration is not null AND duration >= 0 /* Negative duration should not exists */ + GROUP BY term_gw_id + LOOP + perform runtime_stats.update_gw( + v_gw_stats_data.term_gw_id, + v_gw_stats_data.calls, + v_gw_stats_data.successful_calls, + v_gw_stats_data.failed_calls, + v_gw_stats_data.duration + ); + END LOOP; + +END; +$$; + + alter type billing.cdr_v2 + drop attribute package_counter_id, + drop attribute customer_duration; + + + } + end +end diff --git a/db/seeds/main/switch21.sql b/db/seeds/main/switch21.sql index e06567f49..d25fce2ec 100644 --- a/db/seeds/main/switch21.sql +++ b/db/seeds/main/switch21.sql @@ -271,6 +271,7 @@ INSERT INTO switch21.switch_interface_out (id, name, type, custom, rank, for_rad INSERT INTO switch21.switch_interface_out (id, name, type, custom, rank, for_radius) VALUES (1044, 'ss_otn', 'varchar', false, 1983, false); INSERT INTO switch21.switch_interface_out (id, name, type, custom, rank, for_radius) VALUES (1045, 'ss_dtn', 'varchar', false, 1984, false); INSERT INTO switch21.switch_interface_out (id, name, type, custom, rank, for_radius) VALUES (1046, 'ss_attest_id', 'smallint', false, 1985, false); +INSERT INTO switch21.switch_interface_out (id, name, type, custom, rank, for_radius) VALUES (1047, 'package_counter_id', 'bigint', true, 1986, false); -- diff --git a/db/structure.sql b/db/structure.sql index c255d0512..cf674e755 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -260,7 +260,9 @@ CREATE TYPE billing.cdr_v2 AS ( local_tag character varying, from_domain character varying, destination_reverse_billing boolean, - dialpeer_reverse_billing boolean + dialpeer_reverse_billing boolean, + package_counter_id bigint, + customer_duration integer ); @@ -1318,7 +1320,8 @@ CREATE TYPE switch21.callprofile_ty AS ( ss_dtn character varying, ss_attest_id smallint, lega_res character varying, - legb_res character varying + legb_res character varying, + package_counter_id bigint ); @@ -1463,6 +1466,7 @@ CREATE FUNCTION billing.bill_cdr_batch(i_batch_id bigint, i_data text, i_data_ve AS $$ DECLARE v_batch_data billing.cdr_v2; + v_package_data record; v_c_acc_data record; v_v_acc_data record; v_dp_stats_data record; @@ -1496,7 +1500,7 @@ BEGIN sum(customer_price) as customer_price, destination_reverse_billing from json_populate_recordset(null::billing.cdr_v2,_j_data) - where customer_acc_id is not null AND customer_price > 0 + where customer_acc_id is not null AND customer_price > 0 AND package_counter_id is null group by customer_acc_id, destination_reverse_billing LOOP IF v_c_acc_data.destination_reverse_billing IS NULL OR v_c_acc_data.destination_reverse_billing = false THEN @@ -1506,6 +1510,17 @@ BEGIN END IF; END LOOP; + for v_package_data in + select + package_counter_id, + sum(customer_duration) as customer_duration + from json_populate_recordset(null::billing.cdr_v2,_j_data) + where customer_duration is not null AND package_counter_id is not null + group by package_counter_id + LOOP + UPDATE billing.package_counters SET duration = duration-v_package_data.customer_duration WHERE id=v_package_data.package_counter_id; + END LOOP; + for v_v_acc_data in select vendor_acc_id, @@ -2972,6 +2987,7 @@ CREATE TABLE class4.destinations ( reverse_billing boolean DEFAULT false NOT NULL, routing_tag_ids smallint[] DEFAULT '{}'::smallint[] NOT NULL, routing_tag_mode_id smallint DEFAULT 0 NOT NULL, + allow_package_billing boolean DEFAULT false NOT NULL, CONSTRAINT destinations_dst_number_max_length CHECK ((dst_number_max_length >= 0)), CONSTRAINT destinations_dst_number_min_length CHECK ((dst_number_min_length >= 0)), CONSTRAINT destinations_non_zero_initial_interval CHECK ((initial_interval > 0)), @@ -23910,6 +23926,7 @@ BEGIN i_profile.destination_next_interval:=i_destination.next_interval; end if; + IF i_profile.package_counter_id IS NULL THEN CASE i_profile.destination_rate_policy_id WHEN 1 THEN -- fixed i_profile.destination_next_rate:=i_destination.next_rate::varchar; @@ -23936,7 +23953,7 @@ BEGIN ELSE -- end case; - + END IF; /* time limiting START */ @@ -24672,6 +24689,7 @@ BEGIN i_profile.destination_next_interval:=i_destination.next_interval; end if; + IF i_profile.package_counter_id IS NULL THEN CASE i_profile.destination_rate_policy_id WHEN 1 THEN -- fixed i_profile.destination_next_rate:=i_destination.next_rate::varchar; @@ -24698,7 +24716,7 @@ BEGIN ELSE -- end case; - + END IF; /* time limiting START */ @@ -25426,6 +25444,7 @@ BEGIN i_profile.destination_next_interval:=i_destination.next_interval; end if; + IF i_profile.package_counter_id IS NULL THEN CASE i_profile.destination_rate_policy_id WHEN 1 THEN -- fixed i_profile.destination_next_rate:=i_destination.next_rate::varchar; @@ -25452,7 +25471,7 @@ BEGIN ELSE -- end case; - + END IF; /* time limiting START */ @@ -26099,6 +26118,7 @@ CREATE FUNCTION switch21.route(i_node_id integer, i_pop_id integer, i_protocol_i v_defered_dst_rewrites switch21.defered_rewrite[] not null default ARRAY[]::switch21.defered_rewrite[]; v_rate_groups integer[]; v_routing_groups integer[]; + v_package billing.package_counters%rowtype; BEGIN /*dbg{*/ v_start:=now(); @@ -27069,7 +27089,6 @@ CREATE FUNCTION switch21.route(i_node_id integer, i_pop_id integer, i_protocol_i RETURN; END IF; - IF v_rp.validate_dst_number_format AND NOT (v_routing_key ~ '^[0-9]+$') THEN /*dbg{*/ v_end:=clock_timestamp(); @@ -27132,25 +27151,37 @@ CREATE FUNCTION switch21.route(i_node_id integer, i_pop_id integer, i_protocol_i RAISE NOTICE '% ms -> DST. found: %',EXTRACT(MILLISECOND from v_end-v_start),row_to_json(v_destination, true); /*}dbg*/ - v_ret.destination_id:=v_destination.id; - v_ret.destination_prefix=v_destination.prefix; - v_ret.destination_initial_interval:=v_destination.initial_interval; - v_ret.destination_fee:=v_destination.connect_fee::varchar; - v_ret.destination_next_interval:=v_destination.next_interval; - v_ret.destination_rate_policy_id:=v_destination.rate_policy_id; - v_ret.destination_reverse_billing:=v_destination.reverse_billing; + v_ret.destination_id = v_destination.id; + v_ret.destination_prefix = v_destination.prefix; + v_ret.destination_initial_interval = v_destination.initial_interval; + v_ret.destination_next_interval = v_destination.next_interval; + + IF v_destination.allow_package_billing THEN + SELECT INTO v_package * FROM billing.package_counters pc + WHERE pc.account_id = v_customer_auth_normalized.account_id AND + prefix_range(pc.prefix)@>prefix_range(v_routing_key) + ORDER BY length(prefix_range(pc.prefix)) DESC LIMIT 1; + END IF; + IF v_package.id is not null AND v_package.duration > 0 AND NOT v_package.exclude THEN + v_ret.package_counter_id = v_package.id; + v_ret.time_limit = v_package.duration; + ELSE + v_ret.destination_fee = v_destination.connect_fee::varchar; + v_ret.destination_rate_policy_id = v_destination.rate_policy_id; + v_ret.destination_reverse_billing = v_destination.reverse_billing; + if v_destination.next_rate::float > v_destination_rate_limit then + v_ret.disconnect_code_id=8006; -- No destination with appropriate rate found + RETURN NEXT v_ret; + RETURN; + end if; + END IF; + IF v_destination.reject_calls THEN v_ret.disconnect_code_id=112; --Rejected by destination RETURN NEXT v_ret; RETURN; END IF; - if v_destination.next_rate::float>v_destination_rate_limit then - v_ret.disconnect_code_id=8006; -- No destination with appropriate rate found - RETURN NEXT v_ret; - RETURN; - end if; - select into v_rateplan * from class4.rateplans where id=v_customer_auth_normalized.rateplan_id; if COALESCE(v_destination.profit_control_mode_id,v_rateplan.profit_control_mode_id)=2 then -- per call v_rate_limit=v_destination.next_rate::float; @@ -27660,6 +27691,7 @@ CREATE FUNCTION switch21.route_debug(i_node_id integer, i_pop_id integer, i_prot v_defered_dst_rewrites switch21.defered_rewrite[] not null default ARRAY[]::switch21.defered_rewrite[]; v_rate_groups integer[]; v_routing_groups integer[]; + v_package billing.package_counters%rowtype; BEGIN /*dbg{*/ v_start:=now(); @@ -28630,7 +28662,6 @@ CREATE FUNCTION switch21.route_debug(i_node_id integer, i_pop_id integer, i_prot RETURN; END IF; - IF v_rp.validate_dst_number_format AND NOT (v_routing_key ~ '^[0-9]+$') THEN /*dbg{*/ v_end:=clock_timestamp(); @@ -28693,25 +28724,37 @@ CREATE FUNCTION switch21.route_debug(i_node_id integer, i_pop_id integer, i_prot RAISE NOTICE '% ms -> DST. found: %',EXTRACT(MILLISECOND from v_end-v_start),row_to_json(v_destination, true); /*}dbg*/ - v_ret.destination_id:=v_destination.id; - v_ret.destination_prefix=v_destination.prefix; - v_ret.destination_initial_interval:=v_destination.initial_interval; - v_ret.destination_fee:=v_destination.connect_fee::varchar; - v_ret.destination_next_interval:=v_destination.next_interval; - v_ret.destination_rate_policy_id:=v_destination.rate_policy_id; - v_ret.destination_reverse_billing:=v_destination.reverse_billing; + v_ret.destination_id = v_destination.id; + v_ret.destination_prefix = v_destination.prefix; + v_ret.destination_initial_interval = v_destination.initial_interval; + v_ret.destination_next_interval = v_destination.next_interval; + + IF v_destination.allow_package_billing THEN + SELECT INTO v_package * FROM billing.package_counters pc + WHERE pc.account_id = v_customer_auth_normalized.account_id AND + prefix_range(pc.prefix)@>prefix_range(v_routing_key) + ORDER BY length(prefix_range(pc.prefix)) DESC LIMIT 1; + END IF; + IF v_package.id is not null AND v_package.duration > 0 AND NOT v_package.exclude THEN + v_ret.package_counter_id = v_package.id; + v_ret.time_limit = v_package.duration; + ELSE + v_ret.destination_fee = v_destination.connect_fee::varchar; + v_ret.destination_rate_policy_id = v_destination.rate_policy_id; + v_ret.destination_reverse_billing = v_destination.reverse_billing; + if v_destination.next_rate::float > v_destination_rate_limit then + v_ret.disconnect_code_id=8006; -- No destination with appropriate rate found + RETURN NEXT v_ret; + RETURN; + end if; + END IF; + IF v_destination.reject_calls THEN v_ret.disconnect_code_id=112; --Rejected by destination RETURN NEXT v_ret; RETURN; END IF; - if v_destination.next_rate::float>v_destination_rate_limit then - v_ret.disconnect_code_id=8006; -- No destination with appropriate rate found - RETURN NEXT v_ret; - RETURN; - end if; - select into v_rateplan * from class4.rateplans where id=v_customer_auth_normalized.rateplan_id; if COALESCE(v_destination.profit_control_mode_id,v_rateplan.profit_control_mode_id)=2 then -- per call v_rate_limit=v_destination.next_rate::float; @@ -29218,6 +29261,7 @@ CREATE FUNCTION switch21.route_release(i_node_id integer, i_pop_id integer, i_pr v_defered_dst_rewrites switch21.defered_rewrite[] not null default ARRAY[]::switch21.defered_rewrite[]; v_rate_groups integer[]; v_routing_groups integer[]; + v_package billing.package_counters%rowtype; BEGIN @@ -30044,7 +30088,6 @@ CREATE FUNCTION switch21.route_release(i_node_id integer, i_pop_id integer, i_pr RETURN; END IF; - IF v_rp.validate_dst_number_format AND NOT (v_routing_key ~ '^[0-9]+$') THEN @@ -30092,25 +30135,37 @@ CREATE FUNCTION switch21.route_release(i_node_id integer, i_pop_id integer, i_pr END IF; - v_ret.destination_id:=v_destination.id; - v_ret.destination_prefix=v_destination.prefix; - v_ret.destination_initial_interval:=v_destination.initial_interval; - v_ret.destination_fee:=v_destination.connect_fee::varchar; - v_ret.destination_next_interval:=v_destination.next_interval; - v_ret.destination_rate_policy_id:=v_destination.rate_policy_id; - v_ret.destination_reverse_billing:=v_destination.reverse_billing; + v_ret.destination_id = v_destination.id; + v_ret.destination_prefix = v_destination.prefix; + v_ret.destination_initial_interval = v_destination.initial_interval; + v_ret.destination_next_interval = v_destination.next_interval; + + IF v_destination.allow_package_billing THEN + SELECT INTO v_package * FROM billing.package_counters pc + WHERE pc.account_id = v_customer_auth_normalized.account_id AND + prefix_range(pc.prefix)@>prefix_range(v_routing_key) + ORDER BY length(prefix_range(pc.prefix)) DESC LIMIT 1; + END IF; + IF v_package.id is not null AND v_package.duration > 0 AND NOT v_package.exclude THEN + v_ret.package_counter_id = v_package.id; + v_ret.time_limit = v_package.duration; + ELSE + v_ret.destination_fee = v_destination.connect_fee::varchar; + v_ret.destination_rate_policy_id = v_destination.rate_policy_id; + v_ret.destination_reverse_billing = v_destination.reverse_billing; + if v_destination.next_rate::float > v_destination_rate_limit then + v_ret.disconnect_code_id=8006; -- No destination with appropriate rate found + RETURN NEXT v_ret; + RETURN; + end if; + END IF; + IF v_destination.reject_calls THEN v_ret.disconnect_code_id=112; --Rejected by destination RETURN NEXT v_ret; RETURN; END IF; - if v_destination.next_rate::float>v_destination_rate_limit then - v_ret.disconnect_code_id=8006; -- No destination with appropriate rate found - RETURN NEXT v_ret; - RETURN; - end if; - select into v_rateplan * from class4.rateplans where id=v_customer_auth_normalized.rateplan_id; if COALESCE(v_destination.profit_control_mode_id,v_rateplan.profit_control_mode_id)=2 then -- per call v_rate_limit=v_destination.next_rate::float; @@ -31062,6 +31117,39 @@ CREATE SEQUENCE billing.invoices_templates_id_seq ALTER SEQUENCE billing.invoices_templates_id_seq OWNED BY billing.invoice_templates.id; +-- +-- Name: package_counters; Type: TABLE; Schema: billing; Owner: - +-- + +CREATE TABLE billing.package_counters ( + id bigint NOT NULL, + account_id integer NOT NULL, + service_id bigint, + prefix character varying NOT NULL, + exclude boolean DEFAULT false NOT NULL, + duration integer DEFAULT 0 NOT NULL +); + + +-- +-- Name: package_counters_id_seq; Type: SEQUENCE; Schema: billing; Owner: - +-- + +CREATE SEQUENCE billing.package_counters_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: package_counters_id_seq; Type: SEQUENCE OWNED BY; Schema: billing; Owner: - +-- + +ALTER SEQUENCE billing.package_counters_id_seq OWNED BY billing.package_counters.id; + + -- -- Name: payments; Type: TABLE; Schema: billing; Owner: - -- @@ -35989,6 +36077,13 @@ ALTER TABLE ONLY billing.accounts ALTER COLUMN id SET DEFAULT nextval('billing.a ALTER TABLE ONLY billing.invoice_templates ALTER COLUMN id SET DEFAULT nextval('billing.invoices_templates_id_seq'::regclass); +-- +-- Name: package_counters id; Type: DEFAULT; Schema: billing; Owner: - +-- + +ALTER TABLE ONLY billing.package_counters ALTER COLUMN id SET DEFAULT nextval('billing.package_counters_id_seq'::regclass); + + -- -- Name: payments id; Type: DEFAULT; Schema: billing; Owner: - -- @@ -36879,6 +36974,14 @@ ALTER TABLE ONLY billing.invoice_templates ADD CONSTRAINT invoices_templates_pkey PRIMARY KEY (id); +-- +-- Name: package_counters package_counters_pkey; Type: CONSTRAINT; Schema: billing; Owner: - +-- + +ALTER TABLE ONLY billing.package_counters + ADD CONSTRAINT package_counters_pkey PRIMARY KEY (id); + + -- -- Name: payments payments_pkey; Type: CONSTRAINT; Schema: billing; Owner: - -- @@ -38861,6 +38964,27 @@ CREATE UNIQUE INDEX account_balance_notification_settings_account_id_uniq_idx ON CREATE INDEX accounts_contractor_id_idx ON billing.accounts USING btree (contractor_id); +-- +-- Name: package_counters_account_id_idx; Type: INDEX; Schema: billing; Owner: - +-- + +CREATE INDEX package_counters_account_id_idx ON billing.package_counters USING btree (account_id); + + +-- +-- Name: package_counters_prefix_idx; Type: INDEX; Schema: billing; Owner: - +-- + +CREATE INDEX package_counters_prefix_idx ON billing.package_counters USING gist (((prefix)::public.prefix_range)); + + +-- +-- Name: package_counters_service_id_idx; Type: INDEX; Schema: billing; Owner: - +-- + +CREATE INDEX package_counters_service_id_idx ON billing.package_counters USING btree (service_id); + + -- -- Name: payments_account_id_idx; Type: INDEX; Schema: billing; Owner: - -- @@ -39487,6 +39611,14 @@ ALTER TABLE ONLY billing.account_balance_notification_settings ADD CONSTRAINT fk_rails_f185d22f87 FOREIGN KEY (account_id) REFERENCES billing.accounts(id); +-- +-- Name: package_counters package_counters_account_id_fkey; Type: FK CONSTRAINT; Schema: billing; Owner: - +-- + +ALTER TABLE ONLY billing.package_counters + ADD CONSTRAINT package_counters_account_id_fkey FOREIGN KEY (account_id) REFERENCES billing.accounts(id); + + -- -- Name: payments payments_account_id_fkey; Type: FK CONSTRAINT; Schema: billing; Owner: - -- @@ -40762,6 +40894,8 @@ INSERT INTO "public"."schema_migrations" (version) VALUES ('20240410084634'), ('20240422204044'), ('20240425124935'), -('20240611191911'); +('20240611191911'), +('20240615214442'), +('20240622173924'); diff --git a/spec/factories/billing/package_counter.rb b/spec/factories/billing/package_counter.rb new file mode 100644 index 000000000..09c95a1d2 --- /dev/null +++ b/spec/factories/billing/package_counter.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :billing_package_counter, class: Billing::PackageCounter do + account { FactoryBot.create(:account) } + service { FactoryBot.create(:service) } + prefix { '' } + duration { 120 } + exclude { false } + end +end diff --git a/spec/factories/billing/services.rb b/spec/factories/billing/services.rb index 5ad0920e0..198550299 100644 --- a/spec/factories/billing/services.rb +++ b/spec/factories/billing/services.rb @@ -31,6 +31,7 @@ # FactoryBot.define do factory :service, class: Billing::Service do + sequence(:name) { |n| "Service_#{n}" } account { FactoryBot.create(:account) } type { FactoryBot.create(:service_type) } initial_price { rand(100) + rand.round(2) } diff --git a/spec/features/billing/package_counters/export_package_counters_spec.rb b/spec/features/billing/package_counters/export_package_counters_spec.rb new file mode 100644 index 000000000..d8bec3171 --- /dev/null +++ b/spec/features/billing/package_counters/export_package_counters_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +RSpec.describe 'Export Package counters', type: :feature do + include_context :login_as_admin + + before { create(:billing_package_counter) } + + let!(:item) do + create :billing_package_counter + end + + before do + visit package_counters_path(format: :csv) + end + + subject { CSV.parse(page.body).slice(0, 2).transpose } + + it 'has expected header and values' do + expect(subject).to match_array( + [ + ['Id', item.id.to_s], + ['Account name', item.account.name.to_s], + ['Service name', item.service.name.to_s], + ['Prefix', item.prefix], + ['Exclude', item.exclude.to_s], + ['Duration', item.duration.to_s] + ] + ) + end +end diff --git a/spec/features/billing/package_counters/index_package_counters_spec.rb b/spec/features/billing/package_counters/index_package_counters_spec.rb new file mode 100644 index 000000000..37bbd1bf1 --- /dev/null +++ b/spec/features/billing/package_counters/index_package_counters_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +RSpec.describe 'Index package counters', type: :feature, js: true do + subject do + visit package_counters_path + filter_records! + end + + include_context :login_as_admin + let(:filter_records!) { nil } + let!(:counters) do + [ + create(:billing_package_counter), + create(:billing_package_counter, exclude: true), + create(:billing_package_counter, exclude: false) + ] + end + + it 'displays correct table' do + subject + expect(page).to have_table_row(count: counters.count) + counters.each do |c| + within_table_row(id: c.id) do + expect(page).to have_table_cell(column: 'ID', exact_text: c.id.to_s) + expect(page).to have_table_cell(column: 'Account', exact_text: c.account.display_name) + expect(page).to have_table_cell(column: 'Service', exact_text: c.service.display_name) + expect(page).to have_table_cell(column: 'Prefix', exact_text: c.prefix) + expect(page).to have_table_cell(column: 'Duration', exact_text: c.duration.to_s) + end + end + end + + context 'with filter by Account' do + let!(:account) { FactoryBot.create(:account) } + let!(:filtered_counters) { create_list(:billing_package_counter, 2, account:) } + let(:filter_records!) do + within_filters do + fill_in_chosen 'Account', with: account.display_name, exact: true, ajax: true + end + click_on 'Filter' + end + + before do + another_account = FactoryBot.create(:account, :filled) + create(:billing_package_counter, account: another_account) + end + + it 'displays filtered records' do + subject + + expect(page).to have_table_row(count: filtered_counters.size) + filtered_counters.each do |c| + within_table_row(id: c.id) do + expect(page).to have_table_cell(column: 'ID', exact_text: c.id.to_s) + expect(page).to have_table_cell(column: 'Account', exact_text: account.display_name) + end + end + + within_filters do + expect(page).to have_field_chosen('Account', with: account.display_name) + end + end + end +end diff --git a/spec/features/routing/destinations/export_destination_spec.rb b/spec/features/routing/destinations/export_destination_spec.rb index 43d229692..5a6e42ed4 100644 --- a/spec/features/routing/destinations/export_destination_spec.rb +++ b/spec/features/routing/destinations/export_destination_spec.rb @@ -30,6 +30,7 @@ ['Dst number max length', item.dst_number_max_length.to_s, anything], ['Rate group name', item.rate_group.name, anything], ['Reject calls', item.reject_calls.to_s, anything], + ['Allow package billing', item.allow_package_billing.to_s, anything], ['Rate policy name', item.rate_policy_name, anything], ['Initial interval', item.initial_interval.to_s, anything], ['Next interval', item.next_interval.to_s, anything], diff --git a/spec/models/cdr/cdr_spec.rb b/spec/models/cdr/cdr_spec.rb index e42298926..7f5959040 100644 --- a/spec/models/cdr/cdr_spec.rb +++ b/spec/models/cdr/cdr_spec.rb @@ -128,6 +128,7 @@ # orig_call_id :string # orig_gw_external_id :bigint(8) # orig_gw_id :integer(4) +# package_counter_id :bigint(8) # pop_id :integer(4) # rateplan_id :integer(4) # routing_group_id :integer(4) diff --git a/spec/models/report/realtime/bad_routing_spec.rb b/spec/models/report/realtime/bad_routing_spec.rb index 0188b5050..6697e07ab 100644 --- a/spec/models/report/realtime/bad_routing_spec.rb +++ b/spec/models/report/realtime/bad_routing_spec.rb @@ -128,6 +128,7 @@ # orig_call_id :string # orig_gw_external_id :bigint(8) # orig_gw_id :integer(4) +# package_counter_id :bigint(8) # pop_id :integer(4) # rateplan_id :integer(4) # routing_group_id :integer(4) diff --git a/spec/models/report/realtime/not_authenticated_spec.rb b/spec/models/report/realtime/not_authenticated_spec.rb index 1b314c349..f80ff740d 100644 --- a/spec/models/report/realtime/not_authenticated_spec.rb +++ b/spec/models/report/realtime/not_authenticated_spec.rb @@ -128,6 +128,7 @@ # orig_call_id :string # orig_gw_external_id :bigint(8) # orig_gw_id :integer(4) +# package_counter_id :bigint(8) # pop_id :integer(4) # rateplan_id :integer(4) # routing_group_id :integer(4) diff --git a/spec/models/routing/destination_spec.rb b/spec/models/routing/destination_spec.rb index e69706718..d439529ad 100644 --- a/spec/models/routing/destination_spec.rb +++ b/spec/models/routing/destination_spec.rb @@ -6,6 +6,7 @@ # # id :bigint(8) not null, primary key # acd_limit :float default(0.0), not null +# allow_package_billing :boolean default(FALSE), not null # asr_limit :float default(0.0), not null # connect_fee :decimal(, ) default(0.0) # dp_margin_fixed :decimal(, ) default(0.0), not null diff --git a/spec/sql/billing/bill_cdr_batch_spec.rb b/spec/sql/billing/bill_cdr_batch_spec.rb index 61ca64852..04674e820 100644 --- a/spec/sql/billing/bill_cdr_batch_spec.rb +++ b/spec/sql/billing/bill_cdr_batch_spec.rb @@ -12,6 +12,8 @@ let(:dp) { FactoryBot.create(:dialpeer) } let(:gw) { FactoryBot.create(:gateway) } + let(:package_counter) { FactoryBot.create(:billing_package_counter, account_id: customer_acc1.id, duration: 120) } + let(:batch_id) { 1 } let(:batch_data) do [ @@ -24,7 +26,9 @@ dialpeer_reverse_billing: false, dialpeer_id: -22, # there is no such dp term_gw_id: -11, # there is no such gw - duration: 600 + duration: 600, + customer_duration: 1000, + package_counter_id: nil }, { customer_acc_id: customer_acc1.id, @@ -35,7 +39,9 @@ dialpeer_reverse_billing: false, dialpeer_id: dp.id, term_gw_id: -11, # there is no such gw - duration: 10 + duration: 10, + customer_duration: 10, + package_counter_id: nil }, { customer_acc_id: customer_acc1.id, @@ -46,7 +52,9 @@ dialpeer_reverse_billing: false, dialpeer_id: dp.id, term_gw_id: gw.id, - duration: 22 + duration: 22, + customer_duration: 30, + package_counter_id: nil }, { customer_acc_id: customer_acc1.id, @@ -57,7 +65,9 @@ dialpeer_reverse_billing: true, dialpeer_id: dp.id, term_gw_id: gw.id, - duration: 0 + duration: 0, + customer_duration: 0, + package_counter_id: nil }, { customer_acc_id: customer_acc2.id, @@ -68,7 +78,22 @@ dialpeer_reverse_billing: false, dialpeer_id: dp.id, term_gw_id: gw.id, - duration: -11 # should be skipped in stats + duration: -11, # should be skipped in stats + customer_duration: 0, + package_counter_id: nil + }, + { + customer_acc_id: customer_acc1.id, + customer_price: nil, + destination_reverse_billing: false, + vendor_acc_id: vendor_acc2.id, + vendor_price: 0, + dialpeer_reverse_billing: false, + dialpeer_id: dp.id, + term_gw_id: gw.id, + duration: 22, + customer_duration: 30, + package_counter_id: package_counter.id } ] end @@ -89,6 +114,14 @@ expect(customer_acc2.reload.balance).to eq(customer_acc2_balance - customer_acc2_spending) end + it 'updates customer billing package counter' do + duration_before = package_counter.duration + duration_spent = batch_data[5][:customer_duration] + + subject + expect(package_counter.reload.duration).to eq(duration_before - duration_spent) + end + it 'updates vendor accounts' do vendor_acc1_balance = vendor_acc1.balance # dialpeer_reverse_billing=false and vendor_price > 0 @@ -107,20 +140,20 @@ it 'updates statistic' do subject expect(dp.reload.statistic).to have_attributes( - calls: 3, - calls_success: 2, + calls: 4, + calls_success: 3, calls_fail: 1, - total_duration: 32, - asr: 0.6666667, - acd: 16 + total_duration: 54, + asr: 0.75, + acd: 18.0 ) expect(gw.reload.statistic).to have_attributes( - calls: 2, - calls_success: 1, + calls: 3, + calls_success: 2, calls_fail: 1, - total_duration: 22, - asr: 0.5, + total_duration: 44, + asr: 0.6666667, acd: 22 ) end diff --git a/spec/sql/switch/writecdr_spec.rb b/spec/sql/switch/writecdr_spec.rb index a6e92ec57..4b8492d40 100644 --- a/spec/sql/switch/writecdr_spec.rb +++ b/spec/sql/switch/writecdr_spec.rb @@ -290,10 +290,12 @@ "metadata": metadata, "customer_auth_external_type": 'term', "lega_ss_status_id": -1, - "legb_ss_status_id": 2 + "legb_ss_status_id": 2, + "package_counter_id": package_counter_id }.to_json end + let(:package_counter_id) { nil } let!(:metadata) { nil } let(:writecdr_parameters) do @@ -598,6 +600,21 @@ end end + context 'When package billing' do + let(:package_counter_id) { 100_500 } + + it 'customer amount' do + # 560 sec duration, 10s first interval, 11s next, rates = 0.0001/60*10 , 0.0001/60*11 + # (50 × 11) + (1 × 10) = 560s, + # 0.0001/60*11 * 50 + 0.0001/60*10 = 0,0009333333333 + expect { subject }.to change { Cdr::Cdr.count }.by(1) + expect(Cdr::Cdr.last.package_counter_id).to eq(package_counter_id) + expect(Cdr::Cdr.last.customer_duration).to eq(566) + expect(Cdr::Cdr.last.customer_price).to eq(0) + expect(Cdr::Cdr.last.customer_price_no_vat).to eq(0) + end + end + context 'When customer round mode=2' do before do # always UP, precision 4 diff --git a/spec/sql/switch/writecdr_with_internal_disconnect_code_id_spec.rb b/spec/sql/switch/writecdr_with_internal_disconnect_code_id_spec.rb index cb9b9c697..d7cb552a1 100644 --- a/spec/sql/switch/writecdr_with_internal_disconnect_code_id_spec.rb +++ b/spec/sql/switch/writecdr_with_internal_disconnect_code_id_spec.rb @@ -290,11 +290,13 @@ "metadata": metadata, "customer_auth_external_type": 'term', "lega_ss_status_id": -1, - "legb_ss_status_id": 2 + "legb_ss_status_id": 2, + "package_counter_id": package_counter_id }.to_json end let!(:metadata) { nil } + let(:package_counter_id) { nil } let(:writecdr_parameters) do %( @@ -600,6 +602,21 @@ end end + context 'When package billing' do + let(:package_counter_id) { 100_500 } + + it 'customer amount' do + # 560 sec duration, 10s first interval, 11s next, rates = 0.0001/60*10 , 0.0001/60*11 + # (50 × 11) + (1 × 10) = 560s, + # 0.0001/60*11 * 50 + 0.0001/60*10 = 0,0009333333333 + expect { subject }.to change { Cdr::Cdr.count }.by(1) + expect(Cdr::Cdr.last.package_counter_id).to eq(package_counter_id) + expect(Cdr::Cdr.last.customer_duration).to eq(566) + expect(Cdr::Cdr.last.customer_price).to eq(0) + expect(Cdr::Cdr.last.customer_price_no_vat).to eq(0) + end + end + context 'When customer round mode=2' do before do # always UP, precision 4 diff --git a/spec/sql/switch21/route_spec.rb b/spec/sql/switch21/route_spec.rb index 383e076cc..b42b6fb2f 100644 --- a/spec/sql/switch21/route_spec.rb +++ b/spec/sql/switch21/route_spec.rb @@ -615,8 +615,10 @@ def routing_sp next_interval: destination_next_interval, initial_rate: destination_rate, next_rate: destination_rate, - rate_group_id: rate_group.id) + rate_group_id: rate_group.id, + allow_package_billing: destination_allow_package_billing) } + let!(:destination_allow_package_billing) { false } let!(:destination_rate) { 0.11 } let!(:destination_initial_interval) { 1 } let!(:destination_next_interval) { 1 } @@ -921,6 +923,81 @@ def routing_sp end end + context 'Authorized, destination and package billing' do + let!(:destination_rate) { 0.11 } + let!(:customer_account_destination_rate_limit) { 0.11 } + + before do + create(:billing_package_counter, + account_id: customer_account.id, + duration: 48, + prefix: '') + end + + it 'Routing OK without package ' do + expect(subject.size).to eq(2) + expect(subject.first[:customer_auth_id]).to be + expect(subject.first[:customer_id]).to be + expect(subject.first[:destination_id]).to be + expect(subject.first[:destination_prefix]).to be + expect(subject.first[:destination_initial_rate]).to be + expect(subject.first[:destination_next_rate]).to be + expect(subject.first[:destination_initial_interval]).to be + expect(subject.first[:destination_next_interval]).to be + expect(subject.first[:disconnect_code_id]).to eq(nil) + end + + context 'More specific package counter' do + let!(:destination_allow_package_billing) { true } + before do + create(:billing_package_counter, + account_id: customer_account.id, + duration: 480, + prefix: 'uri-name', + exclude: false) + end + + it 'Destination found' do + expect(subject.size).to eq(2) + expect(subject.first[:customer_auth_id]).to be + expect(subject.first[:customer_id]).to be + expect(subject.first[:destination_id]).to be + expect(subject.first[:destination_prefix]).to be + expect(subject.first[:destination_initial_rate]).to eq(nil) + expect(subject.first[:destination_next_rate]).to eq(nil) + expect(subject.first[:destination_initial_interval]).to be + expect(subject.first[:destination_next_interval]).to be + expect(subject.first[:package_counter_id]).to be + expect(subject.first[:time_limit]).to eq(480) + expect(subject.first[:disconnect_code_id]).to eq(nil) + end + end + context 'More specific, excluded package counter' do + let!(:destination_allow_package_billing) { true } + before do + create(:billing_package_counter, + account_id: customer_account.id, + duration: 480, + prefix: 'uri-name', + exclude: true) + end + + it 'Destination found' do + expect(subject.size).to eq(2) + expect(subject.first[:customer_auth_id]).to be + expect(subject.first[:customer_id]).to be + expect(subject.first[:destination_id]).to be + expect(subject.first[:destination_prefix]).to be + expect(subject.first[:destination_initial_rate]).to be + expect(subject.first[:destination_next_rate]).to be + expect(subject.first[:destination_initial_interval]).to be + expect(subject.first[:destination_next_interval]).to be + expect(subject.first[:package_counter_id]).to eq(nil) + expect(subject.first[:disconnect_code_id]).to eq(nil) + end + end + end + context 'Authorized, CustomerAuth DST numberlist' do let!(:nl) { create(:numberlist,