summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/FundLab.Api/Persistence.fs9
-rw-r--r--src/FundLab.Web/App.fs57
2 files changed, 42 insertions, 24 deletions
diff --git a/src/FundLab.Api/Persistence.fs b/src/FundLab.Api/Persistence.fs
index 88c0350..6c60bdd 100644
--- a/src/FundLab.Api/Persistence.fs
+++ b/src/FundLab.Api/Persistence.fs
@@ -131,13 +131,16 @@ type FundRepository(connectionString: string) =
fund_id uuid NOT NULL REFERENCES funds(id),
fund_code text NOT NULL,
amount numeric(20, 2) NOT NULL CHECK (amount > 0),
- fee_amount numeric(20, 2) NOT NULL CHECK (fee_amount > 0),
+ fee_amount numeric(20, 2) NOT NULL CHECK (fee_amount >= 0),
reserved_total numeric(20, 2) NOT NULL CHECK (reserved_total > 0),
status text NOT NULL,
is_synthetic boolean NOT NULL,
submitted_at timestamptz NOT NULL DEFAULT now()
);
+ ALTER TABLE subscription_orders DROP CONSTRAINT IF EXISTS subscription_orders_fee_amount_check;
+ ALTER TABLE subscription_orders ADD CONSTRAINT subscription_orders_fee_amount_check CHECK (fee_amount >= 0);
+
CREATE TABLE IF NOT EXISTS subscription_order_idempotencies (
idempotency_key text PRIMARY KEY,
request_hash text NOT NULL,
@@ -564,8 +567,8 @@ type FundRepository(connectionString: string) =
Error "fund code cannot be empty"
elif command.Amount <= 0m then
Error "amount must be positive"
- elif command.FeeAmount <= 0m then
- Error "fee amount must be positive"
+ elif command.FeeAmount < 0m then
+ Error "fee amount cannot be negative"
elif Decimal.Round(command.Amount, 2) <> command.Amount then
Error "amount exceeds cash precision"
elif Decimal.Round(command.FeeAmount, 2) <> command.FeeAmount then
diff --git a/src/FundLab.Web/App.fs b/src/FundLab.Web/App.fs
index 5a89b5b..51113a0 100644
--- a/src/FundLab.Web/App.fs
+++ b/src/FundLab.Web/App.fs
@@ -228,7 +228,8 @@ type Model =
orderCode: string
orderAmount: string
orderFee: string
- orderSeq: int
+ orderCreateSeq: int
+ orderReadSeq: int
orderInFlight: bool
lastOrderAttempt: OrderAttempt option
pendingOrders: RawOrder list
@@ -258,8 +259,8 @@ type Msg =
| OrderAmountChanged of string
| OrderFeeChanged of string
| OrderCreateRequested
- | OrderCreateCompleted of requestId: int * order: RawOrder
- | OrderCreateFailed of requestId: int * message: string
+ | OrderCreateCompleted of requestId: int * fundId: string * order: RawOrder
+ | OrderCreateFailed of requestId: int * fundId: string * message: string
| OrdersReadRequested
| OrdersReadCompleted of requestId: int * orders: RawOrder array
| OrdersReadFailed of requestId: int * message: string
@@ -276,6 +277,11 @@ let isPositiveCash (text: string) =
| true, value -> value > 0M
| false, _ -> false
+let isNonNegativeCash (text: string) =
+ match Decimal.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture) with
+ | true, value -> value >= 0M
+ | false, _ -> false
+
let resolveCreateKey (lastAttempt: CreateAttempt option) (name: string) (cash: string) =
match lastAttempt with
| Some attempt when attempt.name = name && attempt.cash = cash -> attempt.idempotencyKey
@@ -307,7 +313,8 @@ let init () =
orderCode = ""
orderAmount = ""
orderFee = ""
- orderSeq = 0
+ orderCreateSeq = 0
+ orderReadSeq = 0
orderInFlight = false
lastOrderAttempt = None
pendingOrders = []
@@ -359,8 +366,8 @@ let private createOrderCommand token fundId payload requestId =
Cmd.OfPromise.either
(fun () -> Api.createOrder token fundId payload)
()
- (fun order -> OrderCreateCompleted(requestId, order))
- (fun error -> OrderCreateFailed(requestId, errorText error))
+ (fun order -> OrderCreateCompleted(requestId, fundId, order))
+ (fun error -> OrderCreateFailed(requestId, fundId, errorText error))
let private readOrdersCommand token fundId requestId =
Cmd.OfPromise.either
@@ -391,7 +398,8 @@ let update message model =
orderCode = ""
orderAmount = ""
orderFee = ""
- orderSeq = model.orderSeq + 1
+ orderCreateSeq = model.orderCreateSeq + 1
+ orderReadSeq = model.orderReadSeq + 1
orderInFlight = false
lastOrderAttempt = None
pendingOrders = []
@@ -543,6 +551,11 @@ let update message model =
createdFund = Some fund
fundInFlight = false
lastCreateAttempt = None
+ orderCreateSeq = model.orderCreateSeq + 1
+ orderReadSeq = model.orderReadSeq + 1
+ orderInFlight = false
+ lastOrderAttempt = None
+ pendingOrders = []
error = None
},
Cmd.ofMsg OrdersReadRequested
@@ -596,12 +609,12 @@ let update message model =
{ model with error = Some "请输入基金代码" }, Cmd.none
elif not (isValidCashText amount) || not (isPositiveCash amount) then
{ model with error = Some "认购金额必须是大于零的两位小数金额,例如 100.00" }, Cmd.none
- elif not (isValidCashText fee) || not (isPositiveCash fee) then
- { model with error = Some "认购手续费必须是大于零的两位小数金额,例如 1.50" }, Cmd.none
+ elif not (isValidCashText fee) || not (isNonNegativeCash fee) then
+ { model with error = Some "认购手续费必须是不小于零的两位小数金额,例如 0.00" }, Cmd.none
elif model.orderInFlight then
model, Cmd.none
else
- let requestId = model.orderSeq + 1
+ let requestId = model.orderCreateSeq + 1
let idempotencyKey = resolveOrderKey model.lastOrderAttempt code amount fee
{
@@ -609,7 +622,7 @@ let update message model =
orderCode = code
orderAmount = amount
orderFee = fee
- orderSeq = requestId
+ orderCreateSeq = requestId
orderInFlight = true
lastOrderAttempt =
Some
@@ -631,8 +644,9 @@ let update message model =
feeAmount = fee
}
requestId
- | OrderCreateCompleted (requestId, order) ->
- if requestId = model.orderSeq then
+ | OrderCreateCompleted (requestId, fundId, order) ->
+ if requestId = model.orderCreateSeq
+ && (match model.createdFund with Some fund -> fund.id = fundId | None -> false) then
{
model with
orderInFlight = false
@@ -642,24 +656,25 @@ let update message model =
Cmd.batch [ Cmd.ofMsg FundReadRequested; Cmd.ofMsg OrdersReadRequested ]
else
model, Cmd.none
- | OrderCreateFailed (requestId, message) ->
- if requestId = model.orderSeq then
+ | OrderCreateFailed (requestId, fundId, message) ->
+ if requestId = model.orderCreateSeq
+ && (match model.createdFund with Some fund -> fund.id = fundId | None -> false) then
{ model with orderInFlight = false; error = Some message }, Cmd.none
else
model, Cmd.none
| OrdersReadRequested ->
match model.createdFund with
| Some fund when not (String.IsNullOrWhiteSpace model.token) ->
- let requestId = model.orderSeq + 1
+ let requestId = model.orderReadSeq + 1
- { model with orderSeq = requestId; error = None },
+ { model with orderReadSeq = requestId; error = None },
readOrdersCommand model.token fund.id requestId
| Some _ ->
{ model with error = Some "请输入 API token" }, Cmd.none
| None ->
model, Cmd.none
| OrdersReadCompleted (requestId, orders) ->
- if requestId = model.orderSeq then
+ if requestId = model.orderReadSeq then
{
model with
pendingOrders = orders |> Array.toList
@@ -669,7 +684,7 @@ let update message model =
else
model, Cmd.none
| OrdersReadFailed (requestId, message) ->
- if requestId = model.orderSeq then
+ if requestId = model.orderReadSeq then
{ model with error = Some message }, Cmd.none
else
model, Cmd.none
@@ -1068,7 +1083,7 @@ let private subscribePanel model dispatch =
Html.span "认购手续费(元)"
Html.input [
prop.className "text-input order-fee-input"
- prop.placeholder "例如 1.50"
+ prop.placeholder "0.00 或 1.50"
prop.value model.orderFee
prop.onChange (fun value -> dispatch (OrderFeeChanged value))
]
@@ -1084,7 +1099,7 @@ let private subscribePanel model dispatch =
]
Html.p [
prop.className "hint"
- prop.text "金额与手续费按两位小数字符串提交;下单即冻结现金,订单保持已提交 · 待确认,不产生任何份额或收益。"
+ prop.text "金额与手续费按两位小数字符串提交,手续费允许 0.00;下单即冻结现金,订单保持已提交 · 待确认,不产生任何份额或收益。"
]
Html.div [
prop.className "pending-orders"