summaryrefslogtreecommitdiff
path: root/src/FundLab.Api/Persistence.fs
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-22 05:23:52 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-22 05:23:52 +0800
commit6abd4c206fbfe8369b3fde4c905619d08f13c5d1 (patch)
treef53ce1746591b640f78cd11d63c8411160059d98 /src/FundLab.Api/Persistence.fs
parent545f2a507fd0138f7fd50ed7fb2631d0273db625 (diff)
downloadfund-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.fs105
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"