add subtraction behaviour in limit amount computation

This commit is contained in:
0x42 2021-06-21 13:04:50 +03:00
parent 9bf3e29636
commit 4beba47b5c
No known key found for this signature in database
GPG Key ID: AB0CF4321FCCF431
9 changed files with 179 additions and 37 deletions

View File

@ -30,7 +30,7 @@ get_body(BodyType, Config = #{body_type := {cash, ConfigCurrency}}, LimitContext
case get_body_for_operation(BodyType, Operation, Config, LimitContext) of case get_body_for_operation(BodyType, Operation, Config, LimitContext) of
{ok, {cash, #{currency := ConfigCurrency}}} = Result -> {ok, {cash, #{currency := ConfigCurrency}}} = Result ->
Result; Result;
{ok, {cash, #{amount := Amount, currency := Currency}}} -> {ok, {cash, #{amount := Amount, currency := Currency}} = _Body} ->
case lim_rates:get_converted_amount({Amount, Currency}, Config, LimitContext) of case lim_rates:get_converted_amount({Amount, Currency}, Config, LimitContext) of
{ok, ConvertedAmount} -> {ok, ConvertedAmount} ->
{ok, create_body_from_cash(ConvertedAmount, ConfigCurrency)}; {ok, create_body_from_cash(ConvertedAmount, ConfigCurrency)};
@ -57,7 +57,7 @@ get_body_for_operation(full, invoice_payment_adjustment, Config, LimitContext) -
lim_context:get_from_context(ContextType, cost, invoice_payment, LimitContext); lim_context:get_from_context(ContextType, cost, invoice_payment, LimitContext);
get_body_for_operation(full, invoice_payment_refund, Config, LimitContext) -> get_body_for_operation(full, invoice_payment_refund, Config, LimitContext) ->
ContextType = lim_config_machine:context_type(Config), ContextType = lim_config_machine:context_type(Config),
lim_context:get_from_context(ContextType, cost, invoice_payment, LimitContext); lim_context:get_from_context(ContextType, cost, invoice_payment_refund, LimitContext);
get_body_for_operation(full, invoice_payment_chargeback = Operation, Config, LimitContext) -> get_body_for_operation(full, invoice_payment_chargeback = Operation, Config, LimitContext) ->
ContextType = lim_config_machine:context_type(Config), ContextType = lim_config_machine:context_type(Config),
lim_context:get_from_context(ContextType, body, Operation, LimitContext); lim_context:get_from_context(ContextType, body, Operation, LimitContext);

View File

@ -59,9 +59,31 @@ marshal_config(Config) ->
time_range_type = marshal_time_range_type(lim_config_machine:time_range_type(Config)), time_range_type = marshal_time_range_type(lim_config_machine:time_range_type(Config)),
context_type = marshal_context_type(lim_config_machine:context_type(Config)), context_type = marshal_context_type(lim_config_machine:context_type(Config)),
type = maybe_apply(lim_config_machine:type(Config), fun marshal_type/1), type = maybe_apply(lim_config_machine:type(Config), fun marshal_type/1),
scope = maybe_apply(lim_config_machine:scope(Config), fun marshal_scope/1) scope = maybe_apply(lim_config_machine:scope(Config), fun marshal_scope/1),
op_behaviour = maybe_apply(lim_config_machine:op_behaviour(Config), fun marshal_op_behaviour/1)
}. }.
marshal_op_behaviour(OpBehaviour) ->
Invoice = maps:get(invoice, OpBehaviour, undefined),
Payment = maps:get(invoice_payment, OpBehaviour, undefined),
Adjustment = maps:get(invoice_adjustment, OpBehaviour, undefined),
PaymentAdjustment = maps:get(invoice_payment_adjustment, OpBehaviour, undefined),
PaymentRefund = maps:get(invoice_payment_refund, OpBehaviour, undefined),
PaymentChargeback = maps:get(invoice_payment_adjustment, OpBehaviour, undefined),
#limiter_config_OperationLimitBehaviour{
invoice = maybe_apply(Invoice, fun marshal_behaviour/1),
invoice_adjustment = maybe_apply(Adjustment, fun marshal_behaviour/1),
invoice_payment = maybe_apply(Payment, fun marshal_behaviour/1),
invoice_payment_adjustment = maybe_apply(PaymentAdjustment, fun marshal_behaviour/1),
invoice_payment_refund = maybe_apply(PaymentRefund, fun marshal_behaviour/1),
invoice_payment_chargeback = maybe_apply(PaymentChargeback, fun marshal_behaviour/1)
}.
marshal_behaviour(subtraction) ->
{subtraction, #limiter_config_Subtraction{}};
marshal_behaviour(addition) ->
{addition, #limiter_config_Addition{}}.
marshal_body_type(amount) -> marshal_body_type(amount) ->
{amount, #limiter_config_LimitBodyTypeAmount{}}; {amount, #limiter_config_LimitBodyTypeAmount{}};
marshal_body_type({cash, Currency}) -> marshal_body_type({cash, Currency}) ->
@ -139,7 +161,8 @@ unmarshal_config(#limiter_config_LimitConfig{
time_range_type = TimeRangeType, time_range_type = TimeRangeType,
context_type = ContextType, context_type = ContextType,
type = Type, type = Type,
scope = Scope scope = Scope,
op_behaviour = OpBehaviour
}) -> }) ->
genlib_map:compact(#{ genlib_map:compact(#{
id => ID, id => ID,
@ -152,9 +175,33 @@ unmarshal_config(#limiter_config_LimitConfig{
context_type => unmarshal_context_type(ContextType), context_type => unmarshal_context_type(ContextType),
type => maybe_apply(Type, fun unmarshal_type/1), type => maybe_apply(Type, fun unmarshal_type/1),
scope => maybe_apply(Scope, fun unmarshal_scope/1), scope => maybe_apply(Scope, fun unmarshal_scope/1),
description => Description description => Description,
op_behaviour => maybe_apply(OpBehaviour, fun unmarshal_op_behaviour/1)
}). }).
unmarshal_op_behaviour(OpBehaviour) ->
#limiter_config_OperationLimitBehaviour{
invoice = Invoice,
invoice_adjustment = Adjustment,
invoice_payment = Payment,
invoice_payment_adjustment = PaymentAdjustment,
invoice_payment_refund = Refund,
invoice_payment_chargeback = Chargeback
} = OpBehaviour,
genlib_map:compact(#{
invoice => maybe_apply(Invoice, fun unmarshal_behaviour/1),
invoice_adjustment => maybe_apply(Adjustment, fun unmarshal_behaviour/1),
invoice_payment => maybe_apply(Payment, fun unmarshal_behaviour/1),
invoice_payment_adjustment => maybe_apply(PaymentAdjustment, fun unmarshal_behaviour/1),
invoice_payment_refund => maybe_apply(Refund, fun unmarshal_behaviour/1),
invoice_payment_chargeback => maybe_apply(Chargeback, fun unmarshal_behaviour/1)
}).
unmarshal_behaviour({subtraction, #limiter_config_Subtraction{}}) ->
subtraction;
unmarshal_behaviour({addition, #limiter_config_Addition{}}) ->
addition.
-spec unmarshal_body_type(encoded_value()) -> decoded_value(). -spec unmarshal_body_type(encoded_value()) -> decoded_value().
unmarshal_body_type({amount, #limiter_config_LimitBodyTypeAmount{}}) -> unmarshal_body_type({amount, #limiter_config_LimitBodyTypeAmount{}}) ->
amount; amount;

View File

@ -16,6 +16,7 @@
-export([type/1]). -export([type/1]).
-export([scope/1]). -export([scope/1]).
-export([context_type/1]). -export([context_type/1]).
-export([op_behaviour/1]).
%% API %% API
@ -62,7 +63,8 @@
context_type := context_type(), context_type := context_type(),
type => limit_type(), type => limit_type(),
scope => limit_scope(), scope => limit_scope(),
description => description() description => description(),
op_behaviour => op_behaviour()
}. }.
-type create_params() :: #{ -type create_params() :: #{
@ -77,6 +79,14 @@
description => description() description => description()
}. }.
-type op_behaviour() :: #{operation_type() := addition | subtraction}.
-type operation_type() :: invoice
| invoice_adjustment
| invoice_payment
| invoice_payment_adjustment
| invoice_payment_refund
| invoice_payment_chargeback.
-type lim_id() :: lim_limiter_thrift:'LimitID'(). -type lim_id() :: lim_limiter_thrift:'LimitID'().
-type lim_change() :: lim_limiter_thrift:'LimitChange'(). -type lim_change() :: lim_limiter_thrift:'LimitChange'().
-type limit() :: lim_limiter_thrift:'Limit'(). -type limit() :: lim_limiter_thrift:'Limit'().
@ -209,6 +219,12 @@ scope(_) ->
context_type(#{context_type := Value}) -> context_type(#{context_type := Value}) ->
Value. Value.
-spec op_behaviour(config()) -> lim_maybe:maybe(op_behaviour()).
op_behaviour(#{op_behaviour := Value}) ->
Value;
op_behaviour(_) ->
undefined.
%% %%
-spec start(lim_id(), create_params(), lim_context()) -> {ok, config()}. -spec start(lim_id(), create_params(), lim_context()) -> {ok, config()}.

View File

@ -69,7 +69,8 @@ mk_limit_config(<<"ShopMonthTurnover">>) ->
scope => {scope, shop}, scope => {scope, shop},
shard_size => 12, shard_size => 12,
context_type => payment_processing, context_type => payment_processing,
time_range_type => {calendar, month} time_range_type => {calendar, month},
op_behaviour => #{invoice_payment_refund => subtraction}
}}; }};
mk_limit_config(<<"PartyMonthTurnover">>) -> mk_limit_config(<<"PartyMonthTurnover">>) ->
{ok, #{ {ok, #{

View File

@ -84,7 +84,8 @@ hold(LimitChange = #limiter_LimitChange{id = LimitID}, Config = #{body_type := B
{ok, #{account_id_from := AccountIDFrom, account_id_to := AccountIDTo}} = {ok, #{account_id_from := AccountIDFrom, account_id_to := AccountIDTo}} =
lim_range_machine:ensure_range_exist_in_state(TimeRange, LimitRangeState, LimitContext), lim_range_machine:ensure_range_exist_in_state(TimeRange, LimitRangeState, LimitContext),
Postings = lim_p_transfer:construct_postings(AccountIDFrom, AccountIDTo, Body), Postings = lim_p_transfer:construct_postings(AccountIDFrom, AccountIDTo, Body),
lim_accounting:hold(construct_plan_id(LimitChange), {1, Postings}, LimitContext) Postings1 = maybe_inverse_posting(Postings, LimitContext, Config),
lim_accounting:hold(construct_plan_id(LimitChange), {1, Postings1}, LimitContext)
end). end).
-spec commit(lim_change(), config(), lim_context()) -> ok | {error, commit_error()}. -spec commit(lim_change(), config(), lim_context()) -> ok | {error, commit_error()}.
@ -130,16 +131,27 @@ partial_commit(PartialBody, LimitChange = #limiter_LimitChange{id = LimitID}, Co
TimeRange = lim_config_machine:calculate_time_range(Timestamp, Config), TimeRange = lim_config_machine:calculate_time_range(Timestamp, Config),
{ok, #{account_id_from := AccountIDFrom, account_id_to := AccountIDTo}} = {ok, #{account_id_from := AccountIDFrom, account_id_to := AccountIDTo}} =
lim_range_machine:get_range(TimeRange, LimitRangeState), lim_range_machine:get_range(TimeRange, LimitRangeState),
PartialPostings0 = lim_p_transfer:construct_postings(AccountIDFrom, AccountIDTo, PartialBody),
PartialPostings = lim_p_transfer:construct_postings(AccountIDFrom, AccountIDTo, PartialBody), FullPostings0 = lim_p_transfer:construct_postings(AccountIDFrom, AccountIDTo, FullBody),
FullPostings = lim_p_transfer:construct_postings(AccountIDFrom, AccountIDTo, FullBody), PartialPostings1 = maybe_inverse_posting(PartialPostings0, LimitContext, Config),
NewBatchList = [{2, lim_p_transfer:reverse_postings(FullPostings)} | [{3, PartialPostings}]], FullPostings1 = maybe_inverse_posting(FullPostings0, LimitContext, Config),
NewBatchList = [{2, lim_p_transfer:reverse_postings(FullPostings1)} | [{3, PartialPostings1}]],
PlanID = construct_plan_id(LimitChange), PlanID = construct_plan_id(LimitChange),
unwrap(lim_accounting:plan(PlanID, NewBatchList, LimitContext)), unwrap(lim_accounting:plan(PlanID, NewBatchList, LimitContext)),
unwrap(lim_accounting:commit(PlanID, [{1, FullPostings} | NewBatchList], LimitContext)) unwrap(lim_accounting:commit(PlanID, [{1, FullPostings1} | NewBatchList], LimitContext))
end). end).
maybe_inverse_posting(Posting, LimitContext, #{op_behaviour := ComputationConfig}) ->
{ok, Operation} = lim_context:get_operation(payment_processing, LimitContext),
case maps:get(Operation, ComputationConfig, undefined) of
subtraction ->
lim_p_transfer:reverse_postings(Posting);
Type when Type =:= undefined orelse Type =:= additional ->
Posting
end;
maybe_inverse_posting(Body, _LimitContext, _Config) ->
Body.
assert_partial_body( assert_partial_body(
{cash, #{amount := Partial, currency := Currency}}, {cash, #{amount := Partial, currency := Currency}},
{cash, #{amount := Full, currency := Currency}} {cash, #{amount := Full, currency := Currency}}

View File

@ -0,0 +1,49 @@
-ifndef(__limiter_ct_helper__).
-define(__limiter_ct_helper__, 42).
-include_lib("limiter_proto/include/lim_configurator_thrift.hrl").
-define(cash(Amount), #limiter_base_Cash{
amount = Amount,
currency = #limiter_base_CurrencyRef{symbolic_code = <<"RUB">>}
}).
-define(ctx_invoice_payment(Cost, CaptureCost), ?ctx_invoice_payment(undefined, undefined, Cost, CaptureCost)).
-define(ctx_invoice_payment(OwnerID, ShopID, Cost, CaptureCost), #limiter_context_LimitContext{
payment_processing = #limiter_context_ContextPaymentProcessing{
op = {invoice_payment, #limiter_context_PaymentProcessingOperationInvoicePayment{}},
invoice = #limiter_context_Invoice{
owner_id = OwnerID,
shop_id = ShopID,
effective_payment = #limiter_context_InvoicePayment{
created_at = <<"2000-01-01T00:00:00Z">>,
cost = Cost,
capture_cost = CaptureCost
}
}
}
}).
-define(ctx_invoice_payment_refund(OwnerID, ShopID, Cost, CaptureCost, RefundCost), #limiter_context_LimitContext{
payment_processing = #limiter_context_ContextPaymentProcessing{
op = {invoice_payment_refund, #limiter_context_PaymentProcessingOperationInvoicePaymentRefund{}},
invoice = #limiter_context_Invoice{
owner_id = OwnerID,
shop_id = ShopID,
effective_payment = #limiter_context_InvoicePayment{
created_at = <<"2000-01-01T00:00:00Z">>,
cost = Cost,
capture_cost = CaptureCost,
effective_refund = #limiter_context_InvoicePaymentRefund{
cost = RefundCost,
created_at = <<"2000-01-01T00:00:00Z">>
}
}
}
}
}).
-endif.

View File

@ -2,6 +2,7 @@
-include_lib("stdlib/include/assert.hrl"). -include_lib("stdlib/include/assert.hrl").
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-include_lib("lim_ct_helper.hrl").
-include_lib("limiter_proto/include/lim_configurator_thrift.hrl"). -include_lib("limiter_proto/include/lim_configurator_thrift.hrl").
-include_lib("xrates_proto/include/xrates_rate_thrift.hrl"). -include_lib("xrates_proto/include/xrates_rate_thrift.hrl").
@ -22,6 +23,7 @@
-export([hold_ok/1]). -export([hold_ok/1]).
-export([commit_ok/1]). -export([commit_ok/1]).
-export([rollback_ok/1]). -export([rollback_ok/1]).
-export([refund_ok/1]).
-export([get_config_ok/1]). -export([get_config_ok/1]).
-type test_case_name() :: atom(). -type test_case_name() :: atom().
@ -48,7 +50,8 @@ groups() ->
hold_ok, hold_ok,
commit_ok, commit_ok,
rollback_ok, rollback_ok,
get_config_ok get_config_ok,
refund_ok
]} ]}
]. ].
@ -266,32 +269,45 @@ commit_ok(C) ->
rollback_ok(C) -> rollback_ok(C) ->
ID = <<"ID">>, ID = <<"ID">>,
#{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C),
Context = #limiter_context_LimitContext{ Context0 = ?ctx_invoice_payment(?cash(10), ?cash(10)),
payment_processing = #limiter_context_ContextPaymentProcessing{ Context1 = ?ctx_invoice_payment(?cash(10), ?cash(0)),
op = {invoice_payment, #limiter_context_PaymentProcessingOperationInvoicePayment{}},
invoice = #limiter_context_Invoice{
effective_payment = #limiter_context_InvoicePayment{
created_at = <<"2000-01-01T00:00:00Z">>,
cost = #limiter_base_Cash{
amount = 10,
currency = #limiter_base_CurrencyRef{symbolic_code = <<"RUB">>}
},
capture_cost = #limiter_base_Cash{
amount = 0,
currency = #limiter_base_CurrencyRef{symbolic_code = <<"RUB">>}
}
}
}
}
},
Timestamp = lim_time:to_rfc3339(lim_time:now()), Timestamp = lim_time:to_rfc3339(lim_time:now()),
LimitChangeID = <<Timestamp/binary, "Rollback">>, LimitChangeID = <<Timestamp/binary, "Rollback">>,
Change = #limiter_LimitChange{ Change = #limiter_LimitChange{
id = ID, id = ID,
change_id = LimitChangeID change_id = LimitChangeID
}, },
{ok, {vector, _}} = hold_and_commit(Change, Context, Client), {ok, {vector, _}} = lim_client:hold(Change, Context0, Client),
{ok, #limiter_Limit{}} = lim_client:get(ID, Context, Client). {ok, {vector, _}} = lim_client:commit(Change, Context1, Client).
-spec refund_ok(config()) -> _.
refund_ok(C) ->
ID = lim_time:to_rfc3339(lim_time:now()),
OwnerID = <<"WWWcool Ltd">>,
ShopID = <<"shop">>,
#{client := Client} = _LimitConfig = prepare_environment(ID, <<"ShopMonthTurnover">>, C),
Context0 = ?ctx_invoice_payment(OwnerID, ShopID, ?cash(15), ?cash(15)),
RefundContext1 = ?ctx_invoice_payment_refund(OwnerID, ShopID, ?cash(10), ?cash(10), ?cash(10)),
Timestamp = lim_time:to_rfc3339(lim_time:now()),
LimitChangeID = <<Timestamp/binary, "Payment">>,
Change = #limiter_LimitChange{
id = ID,
change_id = LimitChangeID
},
{ok, {vector, _}} = hold_and_commit(Change, Context0, Client),
Timestamp2 = lim_time:to_rfc3339(lim_time:now()),
LimitChangeID2 = <<Timestamp2/binary, "Refund">>,
Change2 = #limiter_LimitChange{
id = ID,
change_id = LimitChangeID2
},
{ok, {vector, _}} = hold_and_commit(Change2, RefundContext1, Client),
{ok, #limiter_Limit{} = Limit2} = lim_client:get(ID, RefundContext1, Client),
?assertEqual(Limit2#limiter_Limit.amount, 5).
-spec get_config_ok(config()) -> _. -spec get_config_ok(config()) -> _.
get_config_ok(C) -> get_config_ok(C) ->

View File

@ -33,7 +33,8 @@
}, },
{limiter_proto, {limiter_proto,
{git, "git@github.com:rbkmoney/limiter-proto.git", {git, "git@github.com:rbkmoney/limiter-proto.git",
{branch, "master"} % {branch, "master"}
{branch, "ED-181/ft/add_computation_type_in_config"}
} }
}, },
{xrates_proto, {xrates_proto,

View File

@ -35,7 +35,7 @@
{<<"jsx">>,{pkg,<<"jsx">>,<<"3.0.0">>},1}, {<<"jsx">>,{pkg,<<"jsx">>,<<"3.0.0">>},1},
{<<"limiter_proto">>, {<<"limiter_proto">>,
{git,"git@github.com:rbkmoney/limiter-proto.git", {git,"git@github.com:rbkmoney/limiter-proto.git",
{ref,"84cc6b7355aa838c2a91dfab64d000f57ff63bf7"}}, {ref,"a53f50853fc888898ca25dbdc0089a4851213be6"}},
0}, 0},
{<<"machinery">>, {<<"machinery">>,
{git,"https://github.com/rbkmoney/machinery.git", {git,"https://github.com/rbkmoney/machinery.git",