public async Task TestCreateMatched() { var files = Enumerable.Range(2010, 10).Select(o => $"output_{o}.se"); var roots = await TestingTools.ReadSIEFiles(files); var allAccountTypes = roots.SelectMany(o => o.Children).OfType <AccountRecord>().GroupBy(o => o.AccountId).ToDictionary(o => o.Key, o => string.Join(" | ", o.Select(o => o.AccountName).Distinct())); var allVouchers = roots.SelectMany(o => o.Children).OfType <VoucherRecord>(); var transactionsWithUndefinedAccounts = allVouchers.SelectMany(o => o.Transactions.Where(tx => !allAccountTypes.ContainsKey(tx.AccountId)).Select(tx => new { tx.CompanyName, tx.AccountId })); Assert.False(transactionsWithUndefinedAccounts.Any()); var matchResult = MatchSLRResult.MatchSLRVouchers(allVouchers, VoucherRecord.DefaultIgnoreVoucherTypes); //All must have a single (24400|15200) transaction var transactionsMissingRequired = matchResult.Matches.SelectMany(o => new[] { o.SLR, o.Other }) .Where(o => o.Transactions.Count(tx => TransactionMatched.RequiredAccountIds.Contains(tx.AccountId)) != 1); Assert.False(transactionsMissingRequired.Any()); Assert.Equal(0, matchResult.Matches.Count(o => o.Other.TransactionsNonAdminOrCorrections.Count() > 1)); var txs = TransactionMatched.FromVoucherMatches(matchResult, TransactionMatched.RequiredAccountIds); var dbg = string.Join("\n", txs.OrderBy(o => o.DateRegistered ?? LocalDate.MinIsoValue)); }
public async Task Test1() { var files = Enumerable.Range(2010, 10).Select(o => $"output_{o}.se"); var roots = await TestingTools.ReadSIEFiles(files); // new[] { "output_2016.se", "output_2017.se", "output_2018.se" }); var allVouchers = roots.SelectMany(o => o.Children).OfType <VoucherRecord>(); var annoyingAccountIds = new[] { 24400, 26410 }.ToList(); IEnumerable <TransactionRecord> txFilter(IEnumerable <TransactionRecord> txs) => TransactionRecord.PruneCorrections(txs).Where(t => !annoyingAccountIds.Contains(t.AccountId)); var multi = allVouchers.Where(o => !(new[] { "FAS", "LAN", "LON", "MA", "BS", "RV" }.Contains(o.VoucherTypeCode)) && TransactionRecord.PruneCorrections(o.Transactions).Count(t => !(new[] { '1', '2' }.Contains(t.AccountId.ToString()[0]))) > 1).ToList(); var matchResult = MatchSLRResult.MatchSLRVouchers(allVouchers, VoucherRecord.DefaultIgnoreVoucherTypes); var multiAccount = matchResult.Matches.Where(mv => txFilter(mv.SLR.Transactions).Count() > 1); //var dbg2 = string.Join("\n\n", matchResult.Matched.OrderBy(o => o.other.Date) // .Select(mv => $"{PrintVoucher(mv.slr, txFilter)}\n{PrintVoucher(mv.other, txFilter)}")); Assert.DoesNotContain(matchResult.Matches, o => !o.SLR.Transactions.Any()); Assert.DoesNotContain(matchResult.NotMatchedOther, o => !o.Transactions.Any()); Assert.DoesNotContain(matchResult.NotMatchedSLR, o => !o.Transactions.Any()); var cc = matchResult.Matches .Select(o => new { o.Other.Date, txFilter(o.SLR.Transactions).First().Amount, txFilter(o.SLR.Transactions).First().AccountId, txFilter(o.SLR.Transactions).First().CompanyName, Comment = "", }) .Concat((matchResult.NotMatchedSLR.Concat(matchResult.NotMatchedOther)) .Select(o => new { o.Date, Amount = txFilter(o.Transactions).FirstOrDefault()?.Amount ?? 0, AccountId = 0, CompanyName = txFilter(o.Transactions).FirstOrDefault()?.CompanyName ?? "N/A", Comment = o.VoucherTypeCode, })); //cc = cc.Where(o => !string.IsNullOrEmpty(o.Comment) && o.Comment != "LON" && o.Comment != "LAN"); cc = cc.OrderBy(o => o.Date).ToList(); //var dbg = string.Join("\n", cc.Select(o => $"{o.Date.AtMidnight().ToDateTimeUnspecified():yyyy-MM-dd}\t{o.Comment}\t{o.Amount}\t{o.AccountId}\t{o.CompanyName}")); //string PrintVoucher(VoucherRecord voucher, Func<IEnumerable<TransactionRecord>, IEnumerable<TransactionRecord>> funcModifyTransactions = null) //{ // if (funcModifyTransactions == null) // funcModifyTransactions = val => val; // return voucher.ToString() + "\n\t" + string.Join("\n\t", funcModifyTransactions(voucher.Transactions).Select(t => t.ToString())); //} }
public async Task TestMethod1() { Func <int, bool> accountFilter = accountId => accountId.ToString().StartsWith("45"); //Load SBC invoices var fromSBC = (await Tools.LoadSBCInvoices(accountFilter)).Select(o => new SBCVariant { AccountId = o.AccountId, Amount = o.Amount, CompanyName = o.Supplier, DateRegistered = NodaTime.LocalDate.FromDateTime(o.RegisteredDate), DateFinalized = o.PaymentDate.HasValue ? NodaTime.LocalDate.FromDateTime(o.PaymentDate.Value) : (NodaTime.LocalDate?)null, Source = o, }).ToList(); //Load SIE vouchers List <TransactionMatched> fromSIE; { var files = Enumerable.Range(2010, 10).Select(o => $"output_{o}.se"); var sieDir = Tools.GetOutputFolder("SIE"); var roots = await SBCExtensions.ReadSIEFiles(files.Select(file => Path.Combine(sieDir, file))); var allVouchers = roots.SelectMany(o => o.Children).OfType <VoucherRecord>(); var matchResult = MatchSLRResult.MatchSLRVouchers(allVouchers, VoucherRecord.DefaultIgnoreVoucherTypes); fromSIE = TransactionMatched.FromVoucherMatches(matchResult, TransactionMatched.RequiredAccountIds).Where(o => accountFilter(o.AccountId)).ToList(); } var sbcByName = fromSBC.GroupBy(o => o.CompanyName).ToDictionary(o => o.Key, o => o.ToList()); var sieByName = fromSIE.GroupBy(o => o.CompanyName).ToDictionary(o => o.Key, o => o.ToList()); //Create name lookup (can be truncated in one source but not the other): Dictionary <string, string> nameLookup; { var(Intersection, OnlyInA, OnlyInB) = IntersectInfo(sbcByName.Keys, sieByName.Keys); nameLookup = Intersection.ToDictionary(o => o, o => o); AddLookups(OnlyInA, OnlyInB); AddLookups(OnlyInB, OnlyInA); void AddLookups(List <string> enumA, List <string> enumB) { for (int i = enumA.Count - 1; i >= 0; i--) { var itemA = enumA[i]; var itemB = enumB.FirstOrDefault(o => o.StartsWith(itemA)); if (itemB != null) { enumB.Remove(itemB); enumA.Remove(itemA); nameLookup.Add(itemB, itemA); nameLookup.Add(itemA, itemB); } } } //Non-matched: intersectInfo.OnlyInA and intersectInfo.OnlyInB } var matches = new List <(TransactionMatched, SBCVariant)>(); foreach (var(sieName, sieList) in sieByName) { if (nameLookup.ContainsKey(sieName)) { var inSbc = sbcByName[nameLookup[sieName]].GroupBy(o => o.Amount).ToDictionary(o => o.Key, o => o.ToList()); //Multiple passes of the following until no more matches while (true) { var newMatches = new List <(TransactionMatched, SBCVariant)>(); for (int sieIndex = sieList.Count - 1; sieIndex >= 0; sieIndex--) { var item = sieList[sieIndex]; if (inSbc.TryGetValue(item.Amount, out var sbcSameAmount)) { var sbcSameAmountAccount = sbcSameAmount.Where(o => o.AccountId == item.AccountId); //Find those with same register date (could be many) //If multiple or none, take those with closest payment date. //Remove match from inSbc so it can't be matched again var found = new List <SBCVariant>(); if (item.DateRegistered is NodaTime.LocalDate dateRegistered) { found = sbcSameAmountAccount.Where(o => (dateRegistered - item.DateRegistered.Value).Days <= 1).ToList(); } else { found = sbcSameAmountAccount.Where(o => (o.DateFinalized.HasValue && item.DateFinalized.HasValue) && (o.DateFinalized.Value - item.DateFinalized.Value).Days <= 1).ToList(); } if (found.Count > 1) { var orderByDateDiff = found .Where(o => (o.DateFinalized.HasValue && item.DateFinalized.HasValue)) .Select(o => new { #pragma warning disable CS8629 // Nullable value type may be null. Diff = Math.Abs((o.DateFinalized.Value - item.DateFinalized.Value).Days), #pragma warning restore CS8629 // Nullable value type may be null. Object = o }) .OrderBy(o => o.Diff); var minDiff = orderByDateDiff.First().Diff; if (orderByDateDiff.Count(o => o.Diff == minDiff) == 1) { found = orderByDateDiff.Take(1).Select(o => o.Object).ToList(); } } if (found.Count == 1) { newMatches.Add((item, found.Single())); sieList.RemoveAt(sieIndex); sbcSameAmount.Remove(found.First()); } } } if (!newMatches.Any()) { break; } matches.AddRange(newMatches); } } } var nonmatchedSBC = sbcByName.Values.SelectMany(o => o).Except(matches.Select(o => o.Item2)); //Remove cancelled-out pairs (same everything but opposite amount): var cancelling = nonmatchedSBC.GroupBy(o => $"{o.CompanyName} {Math.Abs(o.Amount)} {o.DateRegistered?.ToSimpleDateString()} {o.DateFinalized?.ToSimpleDateString()}") .Where(o => o.Count() == 2 && o.Sum(o => o.Amount) == 0).SelectMany(o => o); nonmatchedSBC = nonmatchedSBC.Except(cancelling); var nonmatched = nonmatchedSBC.Concat(sieByName.Values.SelectMany(o => o).Except(matches.Select(o => o.Item1))); var all = matches.Select(o => o.Item1).Concat(nonmatched); var sss = string.Join("\n", all .OrderBy(o => o.CompanyName).ThenBy(o => o.DateRegistered) .Select(o => $"{o.CompanyName}\t{o.Amount}\t{o.AccountId}\t{o.DateRegistered?.ToSimpleDateString()}\t{o.DateFinalized?.ToSimpleDateString()}\t{(nonmatched.Contains(o) ? "X" : "")}\t{((o is SBCVariant) ? "SBC" : "")}") ); }