From 9818daebaf6fb99124a3196f880b3b8a7998d3a6 Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Tue, 22 Sep 2026 11:08:05 +0800 Subject: Add cash ledger event-source breakdown and reconciliation API/UI (3d-31 B4c) --- tests/FundLab.Domain.Tests/DomainTests.fs | 81 +++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) (limited to 'tests/FundLab.Domain.Tests/DomainTests.fs') diff --git a/tests/FundLab.Domain.Tests/DomainTests.fs b/tests/FundLab.Domain.Tests/DomainTests.fs index 63bcab2..c3d4551 100644 --- a/tests/FundLab.Domain.Tests/DomainTests.fs +++ b/tests/FundLab.Domain.Tests/DomainTests.fs @@ -880,3 +880,84 @@ module DividendPolicyTests = Assert.Equal("dividend:7c9e6679-7425-40de-944b-e07fc1f90ae7", DividendPolicy.recordKey recordId) Assert.Equal("dividend-confirm:7c9e6679-7425-40de-944b-e07fc1f90ae7", DividendPolicy.confirmKey recordId) + +module CashLedgerTests = + + open System + open Xunit + open FundLab.Domain + + let private event source amount : CashLedger.CashLedgerEvent = + { + Source = source + EventDate = DateOnly(2026, 9, 21) + Amount = amount + ReferenceType = "test" + ReferenceId = Guid.NewGuid().ToString("D") + Note = None + } + + [] + let ``cash source text roundtrips through parse`` () = + let sources = + [ + CashLedger.Subscription + CashLedger.Redemption + CashLedger.Dividend + CashLedger.Sip + CashLedger.StockBuy + CashLedger.StockSell + CashLedger.BondBuy + CashLedger.BondSell + CashLedger.BondCoupon + CashLedger.BondMaturity + CashLedger.CapitalDeposit + ] + + for source in sources do + Assert.Equal(Some source, CashLedger.parseSource (CashLedger.sourceText source)) + + Assert.Equal(None, CashLedger.parseSource "unknown") + + [] + let ``scheduled contribution keys are tagged as sip`` () = + Assert.Equal(CashLedger.Sip, CashLedger.subscriptionSourceForKey "sip:plan:2026-09-21") + Assert.Equal(CashLedger.Sip, CashLedger.subscriptionSourceForKey "investment-plan:abc:2026-09-21") + Assert.Equal(CashLedger.Subscription, CashLedger.subscriptionSourceForKey "manual-key") + Assert.Equal(CashLedger.Subscription, CashLedger.subscriptionSourceForKey null) + + [] + let ``reconciliation groups sources and balances`` () = + let events = + [ + event CashLedger.CapitalDeposit 5000.00m + event CashLedger.StockBuy -1000.00m + event CashLedger.StockSell 400.00m + event CashLedger.StockSell 100.00m + ] + + let reconciliation = CashLedger.reconcile 10000.00m 14500.00m events + + Assert.Equal(10000.00m, reconciliation.OpeningCash) + Assert.Equal(4500.00m, reconciliation.NetInflow) + Assert.Equal(14500.00m, reconciliation.ClosingCash) + Assert.Equal(14500.00m, reconciliation.LedgerBalance) + Assert.Equal(0m, reconciliation.Difference) + + let bySource = + reconciliation.Sources |> List.map (fun total -> CashLedger.sourceText total.Source, total.NetAmount, total.EventCount) + + Assert.Equal<(string * decimal * int) list>( + [ ("capital_deposit", 5000.00m, 1); ("stock_buy", -1000.00m, 1); ("stock_sell", 500.00m, 2) ], + bySource + ) + + [] + let ``reconciliation reports a non-zero difference`` () = + let reconciliation = CashLedger.reconcile 10000.00m 9000.00m [ event CashLedger.StockBuy -500.00m ] + Assert.Equal(9500.00m, reconciliation.ClosingCash) + Assert.Equal(500.00m, reconciliation.Difference) + + let empty = CashLedger.reconcile 100.00m 100.00m [] + Assert.Equal(0m, empty.Difference) + Assert.Empty(empty.Sources) -- cgit v1.2.3