summaryrefslogtreecommitdiff
path: root/src/FundLab.Web/App.fs
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-21 12:38:56 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-21 12:38:56 +0800
commitd2a547b6a60ea769092894fff1f2b5105954f226 (patch)
tree5dfd493d926891509258ef133c0a54b0fac2363f /src/FundLab.Web/App.fs
parentf4ab0b08d7648914f6fd049fa1887c0422f69318 (diff)
downloadfund-lab-d2a547b6a60ea769092894fff1f2b5105954f226.tar.gz
Drive SIP plans through the shared order pipeline (3d-6)
Advance executes due periods as real subscription orders: each due trade date claims a sip_executions row (PRIMARY KEY plan_id+trade_date makes same-plan same-day runs atomic), places the order through CreateSubscriptionOrder with a deterministic idempotency key (sip:{plan}:{date}) and the period's trade date, then confirms through the shared ConfirmSubscriptionOrder pipeline with key sip-confirm:{plan}:{date} — identical freeze/fee/units math as manual orders, nothing bypasses confirmation. Outcomes: succeeded, pending_nav (cash stays frozen; later drives retry the confirmation once the NAV lands), insufficient_cash (no order placed, period fails visibly, reruns keep the marker), failed. Plans roll their next_trade_date past the processed window and repeats of the same endDate replay recorded executions instead of re-debiting. Domain adds SipPolicy.advancePlan (due-date enumeration; weekends-only trading-calendar approximation, noted in code). POST /funds/{id}/sip/advance {endDate?, limit?} drives execution on demand (no daemon). Plan responses/rows now carry last execution status/date; front-end shows 已执行/待净值/现金不足 per plan. Optional tradeDateOverride/anchorOverride keep manual flows untouched.
Diffstat (limited to 'src/FundLab.Web/App.fs')
-rw-r--r--src/FundLab.Web/App.fs21
1 files changed, 21 insertions, 0 deletions
diff --git a/src/FundLab.Web/App.fs b/src/FundLab.Web/App.fs
index 6fe1189..5d091dc 100644
--- a/src/FundLab.Web/App.fs
+++ b/src/FundLab.Web/App.fs
@@ -169,6 +169,8 @@ type RawSipPlan =
status: string
anchorDate: string
nextTradeDate: string
+ lastExecutionStatus: obj
+ lastExecutionDate: obj
}
type RawRedemption =
@@ -276,6 +278,8 @@ type SipPlan =
status: string
anchorDate: string
nextTradeDate: string
+ lastExecutionStatus: string option
+ lastExecutionDate: string option
}
type OrderDetail =
@@ -458,6 +462,8 @@ module Api =
status = raw.status
anchorDate = raw.anchorDate
nextTradeDate = raw.nextTradeDate
+ lastExecutionStatus = decodeOptionalText raw.lastExecutionStatus
+ lastExecutionDate = decodeOptionalText raw.lastExecutionDate
}
let decodeRedemption (raw: RawRedemption) : RedemptionDetail =
@@ -2253,6 +2259,21 @@ let private sipPlanRow (plan: SipPlan) =
Html.span [ prop.className "order-cell"; prop.text (sprintf "频率 %s" (sipFrequencyText plan.frequency)) ]
Html.span [ prop.className "order-cell"; prop.text (sprintf "起投日 %s" plan.anchorDate) ]
Html.span [ prop.className "order-cell"; prop.text (sprintf "下次扣款 %s" plan.nextTradeDate) ]
+
+ let lastExecution =
+ match plan.lastExecutionStatus with
+ | Some status ->
+ let label =
+ if status = "succeeded" then "已执行"
+ elif status = "pending_nav" then "待净值"
+ elif status = "insufficient_cash" then "现金不足"
+ elif status = "failed" then "失败"
+ else status
+
+ sprintf "最近执行 %s(%s)" label (plan.lastExecutionDate |> Option.defaultValue "—")
+ | None -> "最近执行 暂无"
+
+ Html.span [ prop.className "order-cell"; prop.text lastExecution ]
Html.span [ prop.className "order-status"; prop.text (if plan.status = "active" then "进行中" else plan.status) ]
]
]