diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-22 05:23:52 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-22 05:23:52 +0800 |
| commit | 6abd4c206fbfe8369b3fde4c905619d08f13c5d1 (patch) | |
| tree | f53ce1746591b640f78cd11d63c8411160059d98 /src/FundLab.Api/Persistence.fs | |
| parent | 545f2a507fd0138f7fd50ed7fb2631d0273db625 (diff) | |
| download | fund-lab-6abd4c206fbfe8369b3fde4c905619d08f13c5d1.tar.gz | |
Add SIP plan pause/resume and rebalance plan archive lifecycle (3d-24)
Diffstat (limited to 'src/FundLab.Api/Persistence.fs')
| -rw-r--r-- | src/FundLab.Api/Persistence.fs | 105 |
1 files changed, 105 insertions, 0 deletions
diff --git a/src/FundLab.Api/Persistence.fs b/src/FundLab.Api/Persistence.fs index 5000161..379c10d 100644 --- a/src/FundLab.Api/Persistence.fs +++ b/src/FundLab.Api/Persistence.fs @@ -389,6 +389,10 @@ type SipPlanWriteResult = | SipPlanFundNotFound | SipPlanInstrumentNotFound +type SipPlanStatusResult = + | SipPlanStatusChanged of SipPlanRecord + | SipPlanStatusNotFound + type SipExecutionOutcome = { TradeDate: DateOnly @@ -494,6 +498,10 @@ type RebalanceWriteResult = | RebalanceFundNotFound | RebalanceInstrumentNotFound +type RebalanceArchiveResult = + | RebalancePlanArchived of RebalancePlanRecord + | RebalanceArchiveNotFound + type RebalanceOrderOutcome = { InstrumentCode: string @@ -3347,6 +3355,54 @@ type FundRepository(connectionString: string) = records |> Seq.toList + member _.SetSipPlanStatus(fundId: Guid, planId: Guid, status: string) : SipPlanStatusResult = + use connection = new NpgsqlConnection(connectionString) + connection.Open() + use transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted) + + try + use lockCommand = + commandWithTransaction + connection + (Some transaction) + "SELECT pg_advisory_xact_lock(hashtext(@lock_key))" + + addParameter lockCommand "lock_key" NpgsqlDbType.Text (box (sprintf "sip-plan-status:%O" planId)) |> ignore + lockCommand.ExecuteNonQuery() |> ignore + + match findSipPlan connection (Some transaction) planId with + | Some plan when plan.FundId = fundId -> + if plan.Status = status then + transaction.Commit() + SipPlanStatusResult.SipPlanStatusChanged plan + else + use updateCommand = + commandWithTransaction + connection + (Some transaction) + "UPDATE sip_plans SET status = @status WHERE id = @plan_id" + + addParameter updateCommand "status" NpgsqlDbType.Text (box status) |> ignore + addParameter updateCommand "plan_id" NpgsqlDbType.Uuid (box planId) |> ignore + updateCommand.ExecuteNonQuery() |> ignore + + let updated = findSipPlan connection (Some transaction) planId + transaction.Commit() + + match updated with + | Some updatedPlan -> SipPlanStatusResult.SipPlanStatusChanged updatedPlan + | None -> SipPlanStatusResult.SipPlanStatusNotFound + | _ -> + transaction.Rollback() + SipPlanStatusResult.SipPlanStatusNotFound + with error -> + try + transaction.Rollback() + with _ -> + () + + raise error + member _.CreateInvestmentPlan(idempotencyKey: string, fundId: Guid, command: InvestmentPlanCommand, ?anchorOverride: DateOnly) : InvestmentPlanWriteResult = if String.IsNullOrWhiteSpace idempotencyKey then InvestmentPlanWriteResult.InvestmentPlanInvalid "idempotency key cannot be empty" @@ -3838,6 +3894,53 @@ type FundRepository(connectionString: string) = reader.Close() plans |> Seq.toList |> List.map (rebalancePlanWithTargets connection None) + member _.ArchiveRebalancePlan(fundId: Guid, planId: Guid) : RebalanceArchiveResult = + use connection = new NpgsqlConnection(connectionString) + connection.Open() + use transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted) + + try + use lockCommand = + commandWithTransaction + connection + (Some transaction) + "SELECT pg_advisory_xact_lock(hashtext(@lock_key))" + + addParameter lockCommand "lock_key" NpgsqlDbType.Text (box (sprintf "rebalance-archive:%O" planId)) |> ignore + lockCommand.ExecuteNonQuery() |> ignore + + match findRebalancePlan connection (Some transaction) planId with + | Some plan when plan.FundId = fundId -> + if plan.Status = "archived" then + transaction.Commit() + RebalanceArchiveResult.RebalancePlanArchived plan + else + use updateCommand = + commandWithTransaction + connection + (Some transaction) + "UPDATE rebalance_plans SET status = 'archived' WHERE id = @plan_id" + + addParameter updateCommand "plan_id" NpgsqlDbType.Uuid (box planId) |> ignore + updateCommand.ExecuteNonQuery() |> ignore + + let updated = findRebalancePlan connection (Some transaction) planId + transaction.Commit() + + match updated with + | Some updatedPlan -> RebalanceArchiveResult.RebalancePlanArchived updatedPlan + | None -> RebalanceArchiveResult.RebalanceArchiveNotFound + | _ -> + transaction.Rollback() + RebalanceArchiveResult.RebalanceArchiveNotFound + with error -> + try + transaction.Rollback() + with _ -> + () + + raise error + member this.ExecuteRebalancePlan(planId: Guid) : Result<RebalanceExecutionResult, string> = use connection = new NpgsqlConnection(connectionString) connection.Open() @@ -3868,6 +3971,7 @@ type FundRepository(connectionString: string) = match plan with | None -> Error "rebalance plan was not found" + | Some plan when plan.Status = "archived" -> Error "rebalance plan is archived" | Some plan -> let fundId = plan.FundId let runDate = ConfirmationPolicy.tradeDateFor DateTimeOffset.UtcNow @@ -4033,6 +4137,7 @@ type FundRepository(connectionString: string) = match findRebalancePlan connection None planId with | None -> Error "rebalance plan was not found" + | Some plan when plan.Status = "archived" -> Error "rebalance plan is archived" | Some plan -> match this.GetFund plan.FundId with | None -> Error "fund was not found" |
