diff options
Diffstat (limited to 'src/FundLab.Web')
| -rw-r--r-- | src/FundLab.Web/App.fs | 134 | ||||
| -rw-r--r-- | src/FundLab.Web/src/api.js | 12 |
2 files changed, 146 insertions, 0 deletions
diff --git a/src/FundLab.Web/App.fs b/src/FundLab.Web/App.fs index 7eb4469..dc558c9 100644 --- a/src/FundLab.Web/App.fs +++ b/src/FundLab.Web/App.fs @@ -227,6 +227,19 @@ type CreateRedemptionPayload = feeAmount: string } +type CapitalAttempt = + { + idempotencyKey: string + amount: string + } + +type CreateCapitalPayload = + { + idempotencyKey: string + amount: string + note: string + } + type OrderDetail = { id: string @@ -318,6 +331,9 @@ module Api = [<Import("confirmRedemption", "./src/api.js")>] let confirmRedemption (token: string) (fundId: string) (orderId: string) (idempotencyKey: string) : JS.Promise<RawRedemption> = jsNative + [<Import("createCapitalDeposit", "./src/api.js")>] + let createCapitalDeposit (token: string) (fundId: string) (payload: CreateCapitalPayload) : JS.Promise<obj> = jsNative + let decodeOptionalText (raw: obj) : string option = if isNull raw then None @@ -448,6 +464,11 @@ type Model = redemptionConfirmSeq: int redemptionConfirmInFlight: bool lastRedemptionConfirmAttempt: RedemptionConfirmAttempt option + capitalAmount: string + capitalSeq: int + capitalInFlight: bool + lastCapitalAttempt: CapitalAttempt option + capitalNote: string option error: string option } @@ -497,6 +518,10 @@ type Msg = | RedemptionConfirmRequested of orderId: string | RedemptionConfirmCompleted of requestId: int * orderId: string * order: RawRedemption | RedemptionConfirmFailed of requestId: int * orderId: string * message: string + | CapitalAmountChanged of string + | CapitalDepositRequested + | CapitalDepositCompleted of requestId: int * amount: string + | CapitalDepositFailed of requestId: int * message: string let defaultInitialUnitNav = "1.00000000" @@ -568,6 +593,11 @@ let init () = redemptionConfirmSeq = 0 redemptionConfirmInFlight = false lastRedemptionConfirmAttempt = None + capitalAmount = "" + capitalSeq = 0 + capitalInFlight = false + lastCapitalAttempt = None + capitalNote = None error = None } @@ -661,6 +691,13 @@ let private confirmRedemptionCommand token fundId orderId idempotencyKey request (fun order -> RedemptionConfirmCompleted(requestId, orderId, order)) (fun error -> RedemptionConfirmFailed(requestId, orderId, errorText error)) +let private createCapitalDepositCommand token fundId payload requestId = + Cmd.OfPromise.either + (fun () -> Api.createCapitalDeposit token fundId payload) + () + (fun _ -> CapitalDepositCompleted(requestId, payload.amount)) + (fun error -> CapitalDepositFailed(requestId, errorText error)) + let update message model = match message with | TokenChanged token -> @@ -705,6 +742,11 @@ let update message model = redemptionConfirmSeq = model.redemptionConfirmSeq + 1 redemptionConfirmInFlight = false lastRedemptionConfirmAttempt = None + capitalAmount = "" + capitalSeq = model.capitalSeq + 1 + capitalInFlight = false + lastCapitalAttempt = None + capitalNote = None error = None }, Cmd.none @@ -875,6 +917,11 @@ let update message model = redemptionConfirmSeq = model.redemptionConfirmSeq + 1 redemptionConfirmInFlight = false lastRedemptionConfirmAttempt = None + capitalAmount = "" + capitalSeq = model.capitalSeq + 1 + capitalInFlight = false + lastCapitalAttempt = None + capitalNote = None error = None }, Cmd.ofMsg OrdersReadRequested @@ -1215,6 +1262,60 @@ let update message model = Cmd.ofMsg RedemptionsReadRequested else model, Cmd.none + | CapitalAmountChanged value -> + { model with capitalAmount = value; error = None; lastCapitalAttempt = None }, Cmd.none + | CapitalDepositRequested -> + let amount = model.capitalAmount.Trim() + + if String.IsNullOrWhiteSpace model.token then + { model with error = Some "请输入 API token" }, Cmd.none + elif model.createdFund.IsNone then + { model with error = Some "请先创建一个基金" }, Cmd.none + elif not (isValidCashText amount) || not (isPositiveCash amount) then + { model with error = Some "追加金额必须是大于零的两位小数金额,例如 1000.00" }, Cmd.none + elif model.capitalInFlight then + model, Cmd.none + else + let requestId = model.capitalSeq + 1 + + let idempotencyKey = + match model.lastCapitalAttempt with + | Some attempt when attempt.amount = amount -> attempt.idempotencyKey + | _ -> Guid.NewGuid().ToString("N") + + { + model with + capitalAmount = amount + capitalSeq = requestId + capitalInFlight = true + lastCapitalAttempt = Some { idempotencyKey = idempotencyKey; amount = amount } + error = None + }, + createCapitalDepositCommand + model.token + model.createdFund.Value.id + { + idempotencyKey = idempotencyKey + amount = amount + note = "追加资金" + } + requestId + | CapitalDepositCompleted (requestId, amount) -> + if requestId = model.capitalSeq then + { + model with + capitalInFlight = false + capitalNote = Some(sprintf "追加成功 + %s(现金余额已刷新)" amount) + error = None + }, + Cmd.ofMsg FundReadRequested + else + model, Cmd.none + | CapitalDepositFailed (requestId, message) -> + if requestId = model.capitalSeq then + { model with capitalInFlight = false; error = Some message }, Cmd.none + else + model, Cmd.none let private navText (text: string) = match Decimal.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture) with @@ -1544,6 +1645,39 @@ let private fundPanel model dispatch = prop.className "hint" prop.text "创建成功后,这里会展示服务端返回的基金档案,可通过重新读取复核持久化结果。" ] + match model.createdFund with + | Some _ -> + Html.div [ + prop.className "capital-block" + prop.children [ + Html.label [ + prop.className "field-label" + prop.children [ + Html.span "追加资金(元)" + Html.input [ + prop.className "text-input capital-amount-input" + prop.placeholder "例如 1000.00" + prop.value model.capitalAmount + prop.onChange (fun value -> dispatch (CapitalAmountChanged value)) + ] + ] + ] + Html.button [ + prop.className "primary-action capital-deposit-action" + prop.disabled model.capitalInFlight + prop.onClick (fun _ -> dispatch CapitalDepositRequested) + prop.text ((if model.capitalInFlight then "追加中..." else "追加资金"): string) + ] + ] + ] + Html.p [ + prop.className "hint capital-result" + prop.text + (match model.capitalNote with + | Some note -> note + | None -> "追加资金直接计入可用现金,不产生份额、不分摊成本、不计入已实现收益。") + ] + | None -> Html.none ] ] diff --git a/src/FundLab.Web/src/api.js b/src/FundLab.Web/src/api.js index 743229c..ab3235a 100644 --- a/src/FundLab.Web/src/api.js +++ b/src/FundLab.Web/src/api.js @@ -118,3 +118,15 @@ export function confirmRedemption(token, fundId, orderId, idempotencyKey) { } ); } + +export function createCapitalDeposit(token, fundId, payload) { + const body = `{"amount":${JSON.stringify(payload.amount)},"note":${JSON.stringify(payload.note || null)}}`; + return requestJson(`/api/funds/${encodeURIComponent(fundId)}/capital/deposit`, token, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Idempotency-Key": payload.idempotencyKey + }, + body + }); +} |
