Skip to content

Commit

Permalink
SSSU-20: the good, the bad, the ugly standing (2600hz#5331)
Browse files Browse the repository at this point in the history
* SSSU-20: the good, the bad, the ugly standing

* SSSU-20: check if account is in good stand for flat rate call

* SSSU-20: check account's standing in crossbar

* SSSU-20: check for billable quantities

* SSSU-20: use already cached limits parameters

* SSSU-20: check creditably in kazoo number manager before commit services

* SSSU-20: un-underscore account id

* nom num numbers

* SSSU-20: forgot about this one

* SSSU-20: make apis

* SSSU-20: props -> kz_json

* lets be nice to ops team by not crashing here

* just to be crash safe

* reflecting changes in 2600hz#5337 and 2600hz#5338

* SSSU-20: check if there is any diff

* SSSU-20: don't be too generous

* SSSU-20: keep it opaque

* remove trailing new line with whitespace
  • Loading branch information
icehess authored and k-anderson committed Dec 12, 2018
1 parent 81cbc8c commit 77dcd96
Show file tree
Hide file tree
Showing 15 changed files with 450 additions and 75 deletions.
8 changes: 8 additions & 0 deletions applications/crossbar/priv/api/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -30663,10 +30663,18 @@
"system_config.jonny5": {
"description": "Schema for jonny5 system_config",
"properties": {
"default_allow_postpay": {
"description": "jonny5 default_allow_postpay",
"type": "boolean"
},
"default_inbound_trunks": {
"description": "jonny5 default inbound trunks",
"type": "integer"
},
"default_max_postpay_amount": {
"description": "jonny5 default_max_postpay_amount",
"type": "number"
},
"default_twoway_trunks": {
"description": "jonny5 default twoway trunks",
"type": "integer"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,18 @@
"_id": "system_config.jonny5",
"description": "Schema for jonny5 system_config",
"properties": {
"default_allow_postpay": {
"description": "jonny5 default_allow_postpay",
"type": "boolean"
},
"default_inbound_trunks": {
"description": "jonny5 default inbound trunks",
"type": "integer"
},
"default_max_postpay_amount": {
"description": "jonny5 default_max_postpay_amount",
"type": "number"
},
"default_twoway_trunks": {
"description": "jonny5 default twoway trunks",
"type": "integer"
Expand Down
71 changes: 51 additions & 20 deletions applications/crossbar/src/crossbar_services.erl
Original file line number Diff line number Diff line change
Expand Up @@ -18,44 +18,75 @@

-include("crossbar.hrl").

-type billables() :: kz_services_quantities:billables() |
kz_services_quantities:billable().

%%------------------------------------------------------------------------------
%% @doc
%% @end
%%------------------------------------------------------------------------------
-spec maybe_dry_run(cb_context:context(), kz_services_invoices:jobjs()) -> cb_context:context().
-spec maybe_dry_run(cb_context:context(), billables()) -> cb_context:context().
maybe_dry_run(Context, ProposedJObj) ->
CurrentJObj = cb_context:fetch(Context, 'db_doc'),
maybe_dry_run(Context, CurrentJObj, ProposedJObj).

-spec maybe_dry_run(cb_context:context(), kz_services_invoices:jobjs(), kz_services_invoices:jobjs()) -> cb_context:context().
-spec maybe_dry_run(cb_context:context(), billables(), billables()) -> cb_context:context().
maybe_dry_run(Context, CurrentJObj, ProposedJObj) ->
case should_dry_run(Context) of
'true' -> dry_run(Context, CurrentJObj, ProposedJObj);
'false' -> Context
end.

-spec dry_run(cb_context:context(), kz_services_invoices:jobjs(), kz_services_invoices:jobjs()) -> cb_context:context().
dry_run(Context, CurrentJObj, ProposedJObj) ->
AccountId = cb_context:account_id(Context),
AuthAccountId = cb_context:auth_account_id(Context),
Services = kz_services:set_updates(kz_services:fetch(AuthAccountId)
,AccountId
,CurrentJObj
,ProposedJObj
),
Quotes = kz_services_invoices:create(Services),
case kz_services_invoices:has_additions(Quotes) of
'true' ->
JObj = kz_services_invoices:public_json(Quotes),
crossbar_util:response_402(JObj, Context);
'false' -> Context
Services = kz_services:fetch(AuthAccountId),
Updated = kz_services:set_updates(Services
,AccountId
,CurrentJObj
,ProposedJObj
),
Quotes = kz_services_invoices:create(Updated),
HasAdditions = kz_services_invoices:has_billable_additions(Quotes),
case should_dry_run(Context) of
'true' -> dry_run(Context, Quotes, HasAdditions);
'false' -> check_creditably(Context, Services, Quotes, HasAdditions)
end.

-spec dry_run(cb_context:context(), kz_services_invoices:invoices(), boolean()) ->
cb_context:context().
dry_run(Context, _Quotes, 'false') ->
Context;
dry_run(Context, Quotes, 'true') ->
JObj = kz_services_invoices:public_json(Quotes),
crossbar_util:response_402(JObj, Context).

-spec should_dry_run(cb_context:context()) -> boolean().
should_dry_run(Context) ->
cb_context:accepting_charges(Context) =/= 'true'
andalso cb_context:api_version(Context) =/= ?VERSION_1.

-spec check_creditably(cb_context:context(), kz_services:services(), kz_services_invoices:invoices(), boolean()) ->
cb_context:context().
check_creditably(Context, _Services, _Quotes, 'false') ->
Context;
check_creditably(Context, Services, Quotes, 'true') ->
Key = [<<"difference">>, <<"billable">>],
Additions = [begin
Changes = kz_services_item:changes(Item),
BillableQuantity = kz_json:get_integer_value(Key, Changes, 0),
Rate = kz_services_item:rate(Item),
BillableQuantity * Rate
end
|| Invoice <- kz_services_invoices:billable_additions(Quotes),
Item <- kz_services_invoice:items(Invoice),
kz_services_item:has_billable_additions(Item)
],
check_creditably(Context, Services, Quotes, lists:sum(Additions));
check_creditably(Context, _Services, _Quotes, Amount) when Amount =< 0 ->
Context;
check_creditably(Context, Services, _Quotes, Amount) ->
Options = #{amount => kz_currency:dollars_to_units(Amount)},
case kz_services:is_good_standing(Services, Options) of
'true' -> Context;
'false' ->
cb_context:add_system_error('no_credit', Context)
end.

%%------------------------------------------------------------------------------
%% @doc
%% @end
Expand Down
4 changes: 3 additions & 1 deletion applications/crossbar/src/modules/cb_services.erl
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ authorize(Context, _Path) ->
is_authorized(_Context, ?HTTP_GET, [{<<"services">>, [?EDITABLE]}]) ->
'true';
is_authorized(_Context, ?HTTP_GET, [{<<"services">>, [?QUOTE]}]) ->
'true'.
'true';
is_authorized(_Context, _, _) ->
'false'.

%%------------------------------------------------------------------------------
%% @doc Given the path tokens related to this module, what HTTP methods are
Expand Down
18 changes: 15 additions & 3 deletions applications/jonny5/src/j5_flat_rate.erl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,19 @@
%%------------------------------------------------------------------------------
-spec authorize(j5_request:request(), j5_limits:limits()) -> j5_request:request().
authorize(Request, Limits) ->
case eligible_for_flat_rate(Request) of
Options = #{allow_postpay => j5_limits:allow_postpay(Limits)
,max_postpay_amount => j5_limits:max_postpay(Limits)
},
authorize(Request, Limits, kz_services:is_good_standing(j5_limits:account_id(Limits), Options)).

-spec authorize(j5_request:request(), j5_limits:limits(), boolean()) -> j5_request:request().
authorize(Request, Limits, 'false') ->
lager:debug("account ~s does not have enough credit for flat rate trunks"
,[j5_limits:account_id(Limits)]
),
Request;
authorize(Request, Limits, 'true') ->
case is_number_eligible_for_flat_rate(Request) of
'true' ->
lager:debug("checking if account ~s has available flat rate trunks"
,[j5_limits:account_id(Limits)]
Expand All @@ -46,8 +58,8 @@ reconcile_cdr(_, _) -> 'ok'.
%% @doc
%% @end
%%------------------------------------------------------------------------------
-spec eligible_for_flat_rate(j5_request:request()) -> boolean().
eligible_for_flat_rate(Request) ->
-spec is_number_eligible_for_flat_rate(j5_request:request()) -> boolean().
is_number_eligible_for_flat_rate(Request) ->
Number = knm_converters:normalize(j5_request:number(Request)),
{TrunkWhitelist, TrunkBlacklist} = maybe_get_resource_flat_rate(Request),
lager:debug("using whitelist: ~p for flat rate check", [TrunkWhitelist]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ changes_props(_, <<"created">>, Quantity) ->
];
changes_props(ItemJObj, <<"modified">>, _) ->
Changes = kz_json:get_json_value(<<"changes">>, ItemJObj),
DiffQuantity = kz_json:get_integer_value([<<"difference">>, <<"quantity">>], Changes),
DiffQuantity = kz_json:get_integer_value([<<"difference">>, <<"quantity">>], Changes, 0),
Quantity = erlang:abs(DiffQuantity),
case DiffQuantity > 0 of
'true' ->
Expand Down
13 changes: 9 additions & 4 deletions core/kazoo_number_manager/src/knm_errors.erl
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ service_restriction(Number, Message) ->
carrier_not_specified(Number) ->
throw({'error', 'carrier_not_specified', Number}).

-spec not_enough_credit(kn(), integer()) -> no_return().
not_enough_credit(Number, Units) ->
throw({'error', 'not_enough_credit', Number, Units}).
-spec not_enough_credit(kz_term:ne_binary(), integer()) -> no_return().
not_enough_credit(AccountId, Units) ->
throw({'error', 'not_enough_credit', AccountId, Units}).

-spec invalid(kn(), kz_term:ne_binary()) -> no_return().
invalid(Number, Reason) ->
Expand Down Expand Up @@ -125,7 +125,7 @@ to_json(Reason)->
to_json(Reason, Num)->
to_json(Reason, Num, 'undefined').

-spec to_json(reason(), kz_term:api_ne_binary(), atom() | kz_term:ne_binary()) -> error().
-spec to_json(reason(), kz_term:api_ne_binary() | kz_term:ne_binaries(), atom() | kz_term:ne_binary() | any()) -> error().
to_json('number_is_porting', Num=?NE_BINARY, _) ->
Message = <<"number ", Num/binary, " is porting">>,
build_error(400, 'number_is_porting', Message, Num);
Expand Down Expand Up @@ -158,6 +158,11 @@ to_json(Reason='invalid', _, Cause) ->
to_json('by_carrier', Num, {_Carrier,_Cause}) ->
lager:error("carrier ~s fault: ~p", [_Carrier, _Cause]),
build_error(500, 'unspecified_fault', <<"fault by carrier">>, Num);
to_json('not_enough_credit', AccountId, Units) ->
Message = io_lib:format("account doesn't have enough credit ~p to perform the operation"
,[Units]
),
build_error(402, 'not_enough_credit', kz_term:to_binary(Message), AccountId);
to_json(Reason, _, Cause) ->
?LOG_ERROR("funky 500 error: ~p/~p", [Reason, Cause]),
build_error(500, 'unspecified_fault', Reason, Cause).
Expand Down
52 changes: 45 additions & 7 deletions core/kazoo_number_manager/src/knm_numbers.erl
Original file line number Diff line number Diff line change
Expand Up @@ -653,12 +653,20 @@ update_services(T=#{todo := Numbers, options := Options}) ->
run_services(T=#{todo := Numbers}) ->
Updates = services_group_numbers(Numbers),
AccountIds = kz_json:get_keys(Updates),
_ = run_services(AccountIds, Updates),
ok(Numbers, T).
try run_services(AccountIds, Updates, []) of
'ok' ->
ok(Numbers, T)
catch
'throw':{'error', 'not_enough_credit', AccountId, Units} ->
Reason = knm_errors:to_json('not_enough_credit', AccountId, Units),
ko(Numbers, Reason, T)
end.

-spec run_services(kz_term:ne_binaries(), kz_json:object()) -> 'ok'.
run_services([], _Updates) -> 'ok';
run_services([AccountId|AccountIds], Updates) ->
-spec run_services(kz_term:ne_binaries(), kz_json:object(), kz_services:services()) -> 'ok'.
run_services([], _Updates, UpdatedServicesAcc) ->
_ = [kz_services:commit(UpdatedServices) || UpdatedServices <- lists:reverse(UpdatedServicesAcc)],
'ok';
run_services([AccountId|AccountIds], Updates, UpdatedServicesAcc) ->
CurrentJObjs = kz_json:get_value([AccountId, <<"current">>], Updates),
ProposedJObjs = kz_json:get_value([AccountId, <<"proposed">>], Updates),
Services = kz_services:fetch(AccountId),
Expand All @@ -667,8 +675,38 @@ run_services([AccountId|AccountIds], Updates) ->
,CurrentJObjs
,ProposedJObjs
),
_ = kz_services:commit(UpdatedServices),
run_services(AccountIds, Updates).
Quotes = kz_services_invoices:create(UpdatedServices),
HasAdditions = kz_services_invoices:has_billable_additions(Quotes),
check_creditably(Services, Quotes, HasAdditions),
run_services(AccountIds, Updates, [UpdatedServices | UpdatedServicesAcc]).

-spec check_creditably(kz_services:services(), kz_services_invoices:invoices(), boolean()) -> 'ok'.
check_creditably(_Services, _Quotes, 'false') ->
'ok';
check_creditably(Services, Quotes, 'true') ->
Key = [<<"difference">>, <<"billable">>],
Additions = [begin
Changes = kz_services_item:changes(Item),
BillableQuantity = props:get_integer_value(Key, Changes, 0),
Rate = kz_services_item:rate(Item),
BillableQuantity * Rate
end
|| Invoice <- kz_services_invoices:billable_additions(Quotes),
Item <- kz_services_invoice:items(Invoice),
kz_services_item:has_billable_additions(Item)
],
check_creditably(Services, Quotes, lists:sum(Additions));
check_creditably(_Services, _Quotes, Amount) when Amount =< 0 ->
'ok';
check_creditably(Services, _Quotes, Amount) ->
Options = #{amount => kz_currency:dollars_to_units(Amount)},
case kz_services:is_good_standing(Services, Options) of
'true' -> 'ok';
'false' ->
knm_errors:not_enough_credit(kz_services:account_id(Services)
,kz_currency:dollars_to_units(Amount)
)
end.

-spec maybe_dry_run_services(collection()) -> collection().
maybe_dry_run_services(T=#{todo := Numbers, options := Options}) ->
Expand Down
Loading

0 comments on commit 77dcd96

Please sign in to comment.