%%%-----------------------------------------------------------------------------
%%% @copyright (C) 2011-2019, 2600Hz
%%% @doc
%%% @author Luis Azedo
%%% @author Pierre Fenoll
%%% @end
%%%-----------------------------------------------------------------------------
-module(knm_managed).
-behaviour(knm_gen_carrier).
-export([info/0]).
-export([is_local/0]).
-export([find_numbers/3]).
-export([acquire_number/1]).
-export([disconnect_number/1]).
-export([is_number_billable/1]).
-export([should_lookup_cnam/0]).
-export([check_numbers/1]).
-export([generate_numbers/3]).
-export([import_numbers/2]).
-include("knm.hrl").
%%------------------------------------------------------------------------------
%% @doc
%% @end
%%------------------------------------------------------------------------------
-spec info() -> map().
info() ->
#{?CARRIER_INFO_MAX_PREFIX => 15
}.
%%------------------------------------------------------------------------------
%% @doc Is this carrier handling numbers local to the system?
%%
%%
A non-local (foreign) carrier module makes HTTP requests.
%% @end
%%------------------------------------------------------------------------------
-spec is_local() -> boolean().
is_local() -> 'true'.
%%------------------------------------------------------------------------------
%% @doc Check with carrier if these numbers are registered with it.
%% @end
%%------------------------------------------------------------------------------
-spec check_numbers(kz_term:ne_binaries()) -> {ok, kz_json:object()} |
{error, any()}.
check_numbers(_Numbers) -> {error, not_implemented}.
%%------------------------------------------------------------------------------
%% @doc Query the local system for a quantity of available numbers
%% in a rate center
%% @end
%%------------------------------------------------------------------------------
-spec find_numbers(kz_term:ne_binary(), pos_integer(), knm_search:options()) ->
{'ok', knm_number:knm_numbers()} |
{'error', any()}.
find_numbers(<<"+", _/binary>>=Prefix, Quantity, Options) ->
AccountId = knm_search:account_id(Options),
find_numbers_in_account(Prefix, Quantity, AccountId, Options);
find_numbers(Prefix, Quantity, Options) ->
find_numbers(<<"+",Prefix/binary>>, Quantity, Options).
-spec find_numbers_in_account(kz_term:ne_binary(), pos_integer(), kz_term:api_ne_binary(), knm_search:options()) ->
{'ok', knm_number:knm_numbers()} |
{'error', any()}.
find_numbers_in_account(Prefix, Quantity, AccountId, Options) ->
case do_find_numbers_in_account(Prefix, Quantity, AccountId, Options) of
{'error', 'not_available'}=Error ->
ResellerId = knm_search:reseller_id(Options),
case AccountId =:= 'undefined'
orelse AccountId =:= ResellerId
of
'true' -> Error;
'false' ->
find_numbers_in_account(Prefix, Quantity, ResellerId, Options)
end;
Result -> Result
end.
-spec do_find_numbers_in_account(kz_term:ne_binary(), pos_integer(), kz_term:api_ne_binary(), knm_search:options()) ->
{'ok', list()} |
{'error', any()}.
do_find_numbers_in_account(Prefix, Quantity, AccountId, Options) ->
ViewOptions = [{'startkey', [AccountId, ?NUMBER_STATE_AVAILABLE, Prefix]}
,{'endkey', [AccountId, ?NUMBER_STATE_AVAILABLE, <>]}
,{'limit', Quantity}
,'include_docs'
],
case kz_datamgr:get_results(?KZ_MANAGED_DB, <<"numbers_managed/status">>, ViewOptions) of
{'ok', []} ->
lager:debug("found no available managed numbers for account ~s", [AccountId]),
{'error', 'not_available'};
{'ok', JObjs} ->
lager:debug("found ~B available managed numbers for account ~s", [length(JObjs), AccountId]),
format_numbers_resp(JObjs, Options);
{'error', _R}=E ->
lager:debug("failed to lookup available managed numbers: ~p", [_R]),
E
end.
-spec format_numbers_resp(kz_json:objects(), knm_search:options()) -> {'ok', list()}.
format_numbers_resp(JObjs, Options) ->
QID = knm_search:query_id(Options),
Numbers = [format_number_resp(QID, JObj) || JObj <- JObjs],
{'ok', Numbers}.
format_number_resp(QID, JObj) ->
Num = kz_doc:id(kz_json:get_value(<<"doc">>, JObj)),
{QID, {Num, ?MODULE, ?NUMBER_STATE_DISCOVERY, kz_json:new()}}.
%%------------------------------------------------------------------------------
%% @doc
%% @end
%%------------------------------------------------------------------------------
-spec is_number_billable(knm_phone_number:knm_phone_number()) -> boolean().
is_number_billable(_Number) -> 'true'.
%%------------------------------------------------------------------------------
%% @doc Acquire a given number from the carrier
%% @end
%%------------------------------------------------------------------------------
-spec acquire_number(knm_number:knm_number()) -> knm_number:knm_number().
acquire_number(Number) ->
PhoneNumber = knm_number:phone_number(Number),
AssignTo = knm_phone_number:assigned_to(PhoneNumber),
State = knm_phone_number:state(PhoneNumber),
lager:debug("acquiring number ~s", [knm_phone_number:number(PhoneNumber)]),
update_doc(Number, [{?PVT_STATE, State}
,{?PVT_ASSIGNED_TO, AssignTo}
]).
%%------------------------------------------------------------------------------
%% @doc Release a number from the routing table
%% @end
%%------------------------------------------------------------------------------
-spec disconnect_number(knm_number:knm_number()) ->
knm_number:knm_number().
disconnect_number(Number) ->
lager:debug("disconnecting number ~s"
,[knm_phone_number:number(knm_number:phone_number(Number))]
),
update_doc(Number, [{?PVT_STATE, ?NUMBER_STATE_AVAILABLE}
,{?PVT_ASSIGNED_TO, 'null'}
]).
-spec generate_numbers(kz_term:ne_binary(), pos_integer(), non_neg_integer()) -> 'ok'.
generate_numbers(_AccountId, _Number, 0) -> 'ok';
generate_numbers(?MATCH_ACCOUNT_RAW(AccountId), Number, Quantity)
when Quantity > 0
andalso is_integer(Number)
andalso is_integer(Quantity) ->
{'ok', _JObj} = save_doc(AccountId, <<"+",(kz_term:to_binary(Number))/binary>>),
generate_numbers(AccountId, Number+1, Quantity-1).
-spec import_numbers(kz_term:ne_binary(), kz_term:ne_binaries()) -> kz_json:object().
import_numbers(AccountId, Numbers) ->
import_numbers(AccountId, Numbers, kz_json:new()).
-spec import_numbers(kz_term:ne_binary(), kz_term:ne_binaries(), kz_json:object()) -> kz_json:object().
import_numbers(_AccountId, [], JObj) -> JObj;
import_numbers(AccountId, [Number | Numbers], JObj) ->
NewJObj =
case save_doc(AccountId, Number) of
{'ok', _Doc} ->
kz_json:set_value([<<"success">>, Number], kz_json:new(), JObj);
{'error', Reason} ->
Error = kz_json:from_list([{<<"reason">>, Reason}
,{<<"message">>, <<"error adding number to DB">>}
]),
kz_json:set_value([<<"errors">>, Number], Error, JObj)
end,
import_numbers(AccountId, Numbers, NewJObj).
-spec save_doc(kz_term:ne_binary(), kz_term:ne_binary()) -> {'ok', kz_json:object()} |
{'error', any()}.
save_doc(AccountId, Number) ->
JObj = kz_json:from_list([{<<"_id">>, knm_converters:normalize(Number)}
,{<<"pvt_account_id">>, AccountId}
,{?PVT_STATE, ?NUMBER_STATE_AVAILABLE}
,{?PVT_TYPE, <<"number">>}
,{<<"pvt_module_name">>, <>}
]),
save_doc(JObj).
-spec save_doc(kz_json:object()) -> {'ok', kz_json:object()} |
{'error', any()}.
save_doc(JObj) ->
kz_datamgr:save_doc(?KZ_MANAGED_DB, JObj).
-spec update_doc(knm_number:knm_number(), kz_term:proplist()) ->
knm_number:knm_number().
update_doc(Number, UpdateProps) ->
PhoneNumber = knm_number:phone_number(Number),
Num = knm_phone_number:number(PhoneNumber),
Updates = [{?PVT_MODULE_NAME, kz_term:to_binary(?MODULE)}
| UpdateProps
],
UpdateOptions = [{'update', Updates}],
case kz_datamgr:update_doc(?KZ_MANAGED_DB, Num, UpdateOptions) of
{'ok', _UpdatedDoc} -> Number;
{'error', Reason} ->
knm_errors:database_error(Reason, PhoneNumber)
end.
%%------------------------------------------------------------------------------
%% @doc
%% @end
%%------------------------------------------------------------------------------
-spec should_lookup_cnam() -> 'true'.
should_lookup_cnam() -> 'true'.