-
Notifications
You must be signed in to change notification settings - Fork 6
HG-544: shumaich base #512
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: epic/shumaich
Are you sure you want to change the base?
Changes from all commits
0fca897
b65b2f1
49da102
b18d264
bd7f022
bca20aa
e46bb17
7b6fd9c
d88626a
c92dc7f
21539f0
784168d
aaba872
8562e08
fef9a7b
b46924d
27b264f
1e9f9e6
186fb65
a6b0dc3
6f9b7ae
cb86a3c
0c9806a
0c66e4c
42831ae
4e65175
e1daeb5
cb7c878
8b2b9fa
a821836
230cae8
2ffa288
8a1e73c
42efffa
8d70d14
55eca2f
91f62ac
b9298a4
bc9deda
a8afcf9
d74d2d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,297 @@ | ||
| %%% Accounting for shumaich | ||
| %%% | ||
| %%% TODO | ||
| %%% - Brittle posting id assignment, it should be a level upper, maybe even in | ||
| %%% `hg_cashflow`. | ||
| %%% - Stuff cash flow details in the posting description fields. | ||
|
|
||
| -module(hg_accounting_new). | ||
|
|
||
| -export([get_account/1]). | ||
| -export([get_account/2]). | ||
|
|
||
| -export([get_balance/1]). | ||
| -export([get_balance/2]). | ||
|
|
||
| -export([collect_account_map/6]). | ||
| -export([collect_merchant_account_map/2]). | ||
| -export([collect_provider_account_map/3]). | ||
| -export([collect_system_account_map/4]). | ||
| -export([collect_external_account_map/4]). | ||
|
|
||
| -export([hold/4]). | ||
|
|
||
| -export([plan/3]). | ||
| -export([plan/4]). | ||
|
|
||
| -export([commit/4]). | ||
|
|
||
| -export([rollback/4]). | ||
|
|
||
| -include_lib("damsel/include/dmsl_payment_processing_thrift.hrl"). | ||
| -include_lib("damsel/include/dmsl_domain_thrift.hrl"). | ||
| -include_lib("shumaich_proto/include/shumaich_shumaich_thrift.hrl"). | ||
|
|
||
| -type amount() :: dmsl_domain_thrift:'Amount'(). | ||
| -type currency_code() :: dmsl_domain_thrift:'CurrencySymbolicCode'(). | ||
| -type account_id() :: dmsl_accounter_thrift:'AccountID'(). | ||
| -type plan_id() :: dmsl_accounter_thrift:'PlanID'(). | ||
| -type batch_id() :: dmsl_accounter_thrift:'BatchID'(). | ||
| -type final_cash_flow() :: dmsl_domain_thrift:'FinalCashFlow'(). | ||
| -type batch() :: {batch_id(), final_cash_flow()}. | ||
| -type clock() :: dmsl_domain_thrift:'AccounterClock'(). | ||
|
|
||
| -type payment() :: dmsl_domain_thrift:'InvoicePayment'(). | ||
| -type shop() :: dmsl_domain_thrift:'Shop'(). | ||
| -type payment_institution() :: dmsl_domain_thrift:'PaymentInstitution'(). | ||
| -type provider() :: dmsl_domain_thrift:'Provider'(). | ||
| -type varset() :: pm_selector:varset(). | ||
| -type revision() :: hg_domain:revision(). | ||
|
|
||
| -export_type([batch/0]). | ||
| -export_type([clock/0]). | ||
|
|
||
| -type account() :: #{ | ||
| account_id => account_id(), | ||
| currency_code => currency_code() | ||
| }. | ||
|
|
||
| -type balance() :: #{ | ||
| account_id => account_id(), | ||
| own_amount => amount(), | ||
| min_available_amount => amount(), | ||
| max_available_amount => amount(), | ||
| clock => clock() | ||
| }. | ||
|
|
||
| -define(DEFAULT_RETRY_STRATEGY, {exponential, 10, 1.1, 100}). | ||
|
|
||
| -spec get_account(account_id()) -> {ok, account()} | {error, not_ready | {account_not_found, _}}. | ||
| get_account(AccountID) -> | ||
| get_account(AccountID, undefined). | ||
|
|
||
| -spec get_account(account_id(), undefined | clock()) -> {ok, account()} | {error, not_ready | {account_not_found, _}}. | ||
| get_account(AccountID, Clock) -> | ||
| case call_accounter('GetAccountByID', {AccountID, to_accounter_clock(Clock)}) of | ||
| {ok, Result} -> | ||
| {ok, construct_account(AccountID, Result)}; | ||
| {error, _} = Error -> | ||
| Error | ||
| end. | ||
|
|
||
| -spec get_balance(account_id()) -> {ok, balance()} | {error, not_ready | {account_not_found, _}}. | ||
| get_balance(AccountID) -> | ||
| get_balance(AccountID, undefined). | ||
|
|
||
| -spec get_balance(account_id(), undefined | clock()) -> {ok, balance()} | {error, not_ready | {account_not_found, _}}. | ||
| get_balance(AccountID, Clock) -> | ||
| case call_accounter('GetBalanceByID', {AccountID, to_accounter_clock(Clock)}) of | ||
| {ok, Result} -> | ||
| {ok, construct_balance(AccountID, Result)}; | ||
| {error, _} = Error -> | ||
| Error | ||
| end. | ||
|
|
||
| -spec collect_account_map(payment(), shop(), payment_institution(), provider(), varset(), revision()) -> map(). | ||
| collect_account_map(Payment, Shop, PaymentInstitution, Provider, VS, Revision) -> | ||
| hg_accounting:collect_account_map(Payment, Shop, PaymentInstitution, Provider, VS, Revision). | ||
|
|
||
| -spec collect_merchant_account_map(shop(), map()) -> map(). | ||
| collect_merchant_account_map(Shop, Acc) -> | ||
| hg_accounting:collect_merchant_account_map(Shop, Acc). | ||
|
|
||
| -spec collect_provider_account_map(payment(), provider(), map()) -> map(). | ||
| collect_provider_account_map(Payment, Provider, Acc) -> | ||
| hg_accounting:collect_provider_account_map(Payment, Provider, Acc). | ||
|
|
||
| -spec collect_system_account_map(payment(), payment_institution(), revision(), map()) -> map(). | ||
| collect_system_account_map(Payment, PaymentInstitution, Revision, Acc) -> | ||
| hg_accounting:collect_system_account_map(Payment, PaymentInstitution, Revision, Acc). | ||
|
|
||
| -spec collect_external_account_map(payment(), varset(), revision(), map()) -> map(). | ||
| collect_external_account_map(Payment, VS, Revision, Acc) -> | ||
| hg_accounting:collect_external_account_map(Payment, VS, Revision, Acc). | ||
|
|
||
| %% | ||
| -spec plan(plan_id(), [batch()], hg_datetime:timestamp()) -> | ||
| {ok, clock()} | {error, not_ready | {invalid_posting_params, _}}. | ||
| plan(_PlanID, [], _Timestamp) -> | ||
| error(badarg); | ||
| plan(_PlanID, Batches, _Timestamp) when not is_list(Batches) -> | ||
| error(badarg); | ||
| plan(PlanID, Batches, Timestamp) -> | ||
| execute_plan(PlanID, Batches, Timestamp, undefined). | ||
|
|
||
| -spec plan(plan_id(), [batch()], hg_datetime:timestamp(), clock()) -> | ||
| {ok, clock()} | {error, not_ready | {invalid_posting_params, _}}. | ||
| plan(_PlanID, [], _Timestamp, _Clock) -> | ||
| error(badarg); | ||
| plan(_PlanID, Batches, _Timestamp, _Clock) when not is_list(Batches) -> | ||
| error(badarg); | ||
| plan(PlanID, Batches, Timestamp, Clock) -> | ||
| execute_plan(PlanID, Batches, Timestamp, Clock). | ||
|
|
||
| -spec hold(plan_id(), batch(), hg_datetime:timestamp(), clock() | undefined) -> | ||
| {ok, clock()} | {error, not_ready | {invalid_posting_params, _}}. | ||
| hold(PlanID, Batch, Timestamp, Clock) -> | ||
| AccounterClock = to_accounter_clock(Clock), | ||
| do('Hold', construct_plan_change(PlanID, Batch, Timestamp), AccounterClock). | ||
|
|
||
| -spec commit(plan_id(), [batch()], hg_datetime:timestamp(), clock() | undefined) -> | ||
| {ok, clock()} | {error, not_ready | {invalid_posting_params, _}}. | ||
| commit(PlanID, Batches, Timestamp, Clock) -> | ||
| AccounterClock = to_accounter_clock(Clock), | ||
| do('CommitPlan', construct_plan(PlanID, Batches, Timestamp), AccounterClock). | ||
|
|
||
| -spec rollback(plan_id(), [batch()], hg_datetime:timestamp(), clock() | undefined) -> | ||
| {ok, clock()} | {error, not_ready | {invalid_posting_params, _}}. | ||
| rollback(PlanID, Batches, Timestamp, Clock) -> | ||
| AccounterClock = to_accounter_clock(Clock), | ||
| do('RollbackPlan', construct_plan(PlanID, Batches, Timestamp), AccounterClock). | ||
|
|
||
| do(Op, Plan, PreviousClock) -> | ||
| case call_accounter(Op, {Plan, PreviousClock}) of | ||
| {ok, Clock} -> | ||
| {ok, to_domain_clock(Clock)}; | ||
| {error, _} = Error -> | ||
| Error | ||
| end. | ||
|
|
||
| execute_plan(_PlanID, [], _Timestamp, Clock) -> | ||
| {ok, Clock}; | ||
| execute_plan(PlanID, [Batch | Rest], Timestamp, Clock) -> | ||
| case hold(PlanID, Batch, Timestamp, Clock) of | ||
| {ok, NewClock} -> | ||
| execute_plan(PlanID, Rest, Timestamp, NewClock); | ||
| {error, _} = Error -> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Я кстати только сейчас осознал, что с ш̤̖͙͍̄͂а̦̣̫̪̱̬̃͛̊ͯͧм̟͍͈̞̝̩̓̄ͬ̉̂ͯӓ̱͔͕̻͔̯̦́̒̓̑̑̑и̩̖ͥч͕̊̈́ͫͮ͌̉̎ем мы можем оказаться в ситуации, когда половина плана захолдирована, и машина упала. Надо бы наверное понять, что с этим делать, если что. По идее эта ошибка может только на этапе интеграции проявиться, поэтому повода какую-то обвязку по обработке в виде кода вроде как нет, но тем не менее.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Мне казалось, что шамаич умеет в идемпотентность, и поэтому ничего страшного что мы после поднятия машины попробуем захолдировать захолженные холды еще раз. 🤔
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Тут я скорее про ошибку
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. не совсем понятен кейс когда ты такую ошибку можешь получить |
||
| Error | ||
| end. | ||
|
|
||
| construct_plan_change(PlanID, {BatchID, Cashflow}, Timestamp) -> | ||
| #shumaich_PostingPlanChange{ | ||
| id = PlanID, | ||
| creation_time = Timestamp, | ||
| batch = #shumaich_PostingBatch{ | ||
| id = BatchID, | ||
| postings = collect_postings(Cashflow) | ||
| } | ||
| }. | ||
|
|
||
| construct_plan(PlanID, Batches, Timestamp) -> | ||
| #shumaich_PostingPlan{ | ||
| id = PlanID, | ||
| creation_time = Timestamp, | ||
| batch_list = [ | ||
| #shumaich_PostingBatch{ | ||
| id = BatchID, | ||
| postings = collect_postings(Cashflow) | ||
| } | ||
| || {BatchID, Cashflow} <- Batches | ||
| ] | ||
| }. | ||
|
|
||
| collect_postings(Cashflow) -> | ||
| [ | ||
| #shumaich_Posting{ | ||
| from_account = #shumaich_Account{id = Source, currency_symbolic_code = CurrencyCode}, | ||
| to_account = #shumaich_Account{id = Destination, currency_symbolic_code = CurrencyCode}, | ||
| amount = Amount, | ||
| currency_symbolic_code = CurrencyCode, | ||
| description = construct_posting_description(Details) | ||
| } | ||
| || #domain_FinalCashFlowPosting{ | ||
| source = #domain_FinalCashFlowAccount{account_id = Source}, | ||
| destination = #domain_FinalCashFlowAccount{account_id = Destination}, | ||
| details = Details, | ||
| volume = #domain_Cash{ | ||
| amount = Amount, | ||
| currency = #domain_CurrencyRef{symbolic_code = CurrencyCode} | ||
| } | ||
| } <- Cashflow | ||
| ]. | ||
|
|
||
| construct_posting_description(Details) when is_binary(Details) -> | ||
| Details; | ||
| construct_posting_description(undefined) -> | ||
| <<>>. | ||
|
|
||
| %% | ||
|
|
||
| construct_account( | ||
| AccountID, | ||
| #shumaich_Account{ | ||
| currency_symbolic_code = CurrencyCode | ||
| } | ||
| ) -> | ||
| #{ | ||
| account_id => AccountID, | ||
| currency_code => CurrencyCode | ||
| }. | ||
|
|
||
| construct_balance( | ||
| AccountID, | ||
| #shumaich_Balance{ | ||
| own_amount = OwnAmount, | ||
| min_available_amount = MinAvailableAmount, | ||
| max_available_amount = MaxAvailableAmount, | ||
| clock = Clock | ||
| } | ||
| ) -> | ||
| genlib_map:compact(#{ | ||
| account_id => AccountID, | ||
| own_amount => OwnAmount, | ||
| min_available_amount => MinAvailableAmount, | ||
| max_available_amount => MaxAvailableAmount, | ||
| clock => to_domain_clock(Clock) | ||
| }). | ||
|
|
||
| %% | ||
|
|
||
| call_accounter(Function, Args) -> | ||
| hg_retry:apply( | ||
| fun() -> | ||
| case call_service(Function, Args) of | ||
| {ok, _} = Ok -> | ||
| {return, Ok}; | ||
| {error, ErrorType} = Error -> | ||
| {map_error_action(ErrorType), Error} | ||
| end | ||
| end, | ||
| get_retry_strategy(Function) | ||
| ). | ||
|
|
||
| call_service(Function, Args) -> | ||
| case hg_woody_wrapper:call(accounter_new, Function, Args) of | ||
| {ok, _} = Ok -> | ||
| Ok; | ||
| {exception, Exception} -> | ||
| {error, map_exception(Exception)} | ||
| end. | ||
|
|
||
| map_error_action(not_ready) -> | ||
| retry; | ||
| map_error_action(_) -> | ||
| return. | ||
|
|
||
| map_exception(#shumaich_NotReady{}) -> | ||
| not_ready; | ||
| map_exception(#shumaich_AccountNotFound{account_id = AccountID}) -> | ||
| {account_not_found, AccountID}; | ||
| map_exception(#shumaich_PlanNotFound{plan_id = PlanID}) -> | ||
| {plan_not_found, PlanID}; | ||
| map_exception(#shumaich_InvalidPostingParams{wrong_postings = WrongPostings}) -> | ||
| {invalid_posting_params, WrongPostings}. | ||
|
|
||
| to_domain_clock({latest, #shumaich_LatestClock{}}) -> | ||
| undefined; | ||
| to_domain_clock({vector, #shumaich_VectorClock{state = State}}) -> | ||
| {vector, #domain_VectorClock{state = State}}. | ||
|
|
||
| to_accounter_clock(undefined) -> | ||
| {latest, #shumaich_LatestClock{}}; | ||
| to_accounter_clock({vector, #domain_VectorClock{state = State}}) -> | ||
| {vector, #shumaich_VectorClock{state = State}}. | ||
|
|
||
| get_retry_strategy(Function) -> | ||
| PolicyConfig = genlib_app:env(hellgate, accounter_retry_policy, #{}), | ||
| hg_retry:new_strategy(maps:get(Function, PolicyConfig, ?DEFAULT_RETRY_STRATEGY)). | ||
Uh oh!
There was an error while loading. Please reload this page.