summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tests/FundLab.Api.Tests/CashReconciliationTests.fs71
1 files changed, 71 insertions, 0 deletions
diff --git a/tests/FundLab.Api.Tests/CashReconciliationTests.fs b/tests/FundLab.Api.Tests/CashReconciliationTests.fs
index e969472..b9b024a 100644
--- a/tests/FundLab.Api.Tests/CashReconciliationTests.fs
+++ b/tests/FundLab.Api.Tests/CashReconciliationTests.fs
@@ -210,6 +210,30 @@ type CashReconciliationTests(fixture: PostgresFixture) =
| SubscriptionConfirmResult.OrderConfirmed _ -> ()
| other -> failwithf "unexpected subscription confirm: %A" other
+ let redeem fundId code units idempotencyKey =
+ let order =
+ match
+ repository().CreateRedemptionOrder(
+ idempotencyKey,
+ fundId,
+ { InstrumentCode = code; Units = units; FeeAmount = 0m }
+ )
+ with
+ | RedemptionWriteResult.RedemptionCreated order
+ | RedemptionWriteResult.RedemptionReplayed order -> order
+ | other -> failwithf "unexpected redemption order result: %A" other
+
+ match
+ repository().ConfirmRedemptionOrder(
+ fixture.Key(sprintf "cash-recon-redeem-confirm-%s" (Guid.NewGuid().ToString("N"))),
+ fundId,
+ order.Id
+ )
+ with
+ | RedemptionConfirmResult.RedemptionConfirmed _
+ | RedemptionConfirmResult.RedemptionConfirmReplayed _ -> ()
+ | other -> failwithf "unexpected redemption confirm: %A" other
+
let app () = App.createApplication (repository ())
let reconciliation fundId =
@@ -234,6 +258,11 @@ type CashReconciliationTests(fixture: PostgresFixture) =
|> Seq.filter (fun entry -> entry.GetProperty("source").GetString() = source)
|> Seq.length
+ let sourceNet (body: string) (source: string) =
+ match (sourceEntry body source).GetProperty("netAmount").GetString() with
+ | null -> failwithf "source %s has no netAmount" source
+ | text -> Decimal.Parse(text, System.Globalization.CultureInfo.InvariantCulture)
+
let deleteLedgerEvents fundId =
use connection = new NpgsqlConnection(fixture.ConnectionString)
connection.Open()
@@ -471,3 +500,45 @@ type CashReconciliationTests(fixture: PostgresFixture) =
Assert.Contains("\"difference\":\"0.00\"", body)
Assert.Equal(1, eventCount body "stock_sell")
Assert.Equal(1L, tableCount "stock_sells" fundId)
+
+ [<Fact>]
+ member _.``external flows and internal trades stay separate sources reconciled against net inflow``() =
+ let fundId = createFund 10000.00m
+ let code = seedInstrument ()
+ deposit fundId 5000.00m (fixture.Key "cash-flow-split-deposit")
+ subscribe fundId code 1000.00m (fixture.Key "cash-flow-split-subscribe")
+ redeem fundId code 400m (fixture.Key "cash-flow-split-redeem")
+ buyStock fundId "600519" 100m 10.00m (fixture.Key "cash-flow-split-buy")
+ sellStock fundId "600519" 40m 12.00m (fixture.Key "cash-flow-split-sell")
+
+ let status, body = reconciliation fundId
+ Assert.Equal(200, status)
+ Assert.Contains("\"openingCash\":\"10000.00\"", body)
+ Assert.Contains("\"netInflow\":\"3880.00\"", body)
+ Assert.Contains("\"closingCash\":\"13880.00\"", body)
+ Assert.Contains("\"ledgerBalance\":\"13880.00\"", body)
+ Assert.Contains("\"difference\":\"0.00\"", body)
+
+ // external capital/subscription/redemption are their own ledger sources
+ Assert.Equal(5000.00m, sourceNet body "capital_deposit")
+ Assert.Equal(-1000.00m, sourceNet body "subscription")
+ Assert.Equal(400.00m, sourceNet body "redemption")
+ Assert.Equal(1, eventCount body "subscription")
+ Assert.Equal(1, eventCount body "redemption")
+
+ // internal stock trades are their own ledger sources, never external flows
+ Assert.Equal(-1000.00m, sourceNet body "stock_buy")
+ Assert.Equal(480.00m, sourceNet body "stock_sell")
+ Assert.Equal(1, eventCount body "stock_buy")
+ Assert.Equal(1, eventCount body "stock_sell")
+
+ // the two buckets add up to net inflow: external flows are not smuggled into trade sources
+ let externalTotal =
+ sourceNet body "capital_deposit" + sourceNet body "subscription" + sourceNet body "redemption"
+
+ let internalTotal = sourceNet body "stock_buy" + sourceNet body "stock_sell"
+
+ Assert.Equal(4400.00m, externalTotal)
+ Assert.Equal(-520.00m, internalTotal)
+ Assert.Equal(3880.00m, externalTotal + internalTotal)
+ Assert.Equal(0.00m, (repository().GetCashReconciliation fundId |> Result.defaultWith failwith).Difference)