From 3d9398234a54774544eb48a1caed6a1b2d5132eb Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Mon, 21 Sep 2026 10:43:09 +0800 Subject: Add confirmation status and holdings UI (3d-2c) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire the subscription confirmation and positions endpoints into the frontend: order rows show per-order trade date and confirmed outcome (NAV, units, NAV date, invested cash, residual) or pending_nav reason, each confirmable order gets a confirm action posting to the existing confirm endpoint with an idempotency key and request-sequence guard, and a new holdings panel renders available/reserved cash plus per-code positions (units, cost, latest valuation NAV with date, or an explicit '估值待更新' when no valuation exists). Orders list/create responses now return the full detail shape so pending reasons and confirmation results survive refresh. The optional text decoder accepts the current plain-value/null wire format alongside the legacy case/fields envelope, restoring fund type and daily return display. Browser QA gains a J-series covering the real confirm flow, holdings rendering and cash accounting; Web tests cover decoders, confirm guard, stale-response protection and token invalidation. --- src/FundLab.Api/App.fs | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) (limited to 'src/FundLab.Api') diff --git a/src/FundLab.Api/App.fs b/src/FundLab.Api/App.fs index 839a641..97787aa 100644 --- a/src/FundLab.Api/App.fs +++ b/src/FundLab.Api/App.fs @@ -57,19 +57,6 @@ type FundApiResponse = status: string } -type SubscriptionOrderApiResponse = - { - id: Guid - fundId: Guid - fundCode: string - amount: string - feeAmount: string - reservedTotal: string - status: string - submittedAt: string - isSynthetic: bool - } - type ConfirmedQuoteEvidenceResponse = { navDate: string @@ -198,20 +185,7 @@ module App = status = fund.Status } - let private orderResponse (order: SubscriptionOrderRecord) : SubscriptionOrderApiResponse = - { - id = order.Id - fundId = order.FundId - fundCode = order.FundCode - amount = cashText order.Amount - feeAmount = cashText order.FeeAmount - reservedTotal = cashText order.ReservedTotal - status = order.Status - submittedAt = timestampText order.SubmittedAt - isSynthetic = order.IsSynthetic - } - - let private confirmationResponse (order: SubscriptionOrderRecord) : SubscriptionOrderDetailResponse = + let private orderResponse (order: SubscriptionOrderRecord) : SubscriptionOrderDetailResponse = let quoteEvidence = order.ConfirmedQuote |> Option.map (fun quote -> @@ -461,7 +435,7 @@ module App = match repository.ConfirmSubscriptionOrder(idempotencyKey, fundId, orderId) with | OrderConfirmed order | ConfirmReplayed order - | ConfirmPendingNav order -> json (confirmationResponse order) next ctx + | ConfirmPendingNav order -> json (orderResponse order) next ctx | ConfirmIdempotencyConflict -> errorResponse 409 "IDEMPOTENCY_CONFLICT" "idempotency key was used with a different request" next ctx | ConfirmAlreadyConfirmed -> -- cgit v1.2.3