Ejemplo n.º 1
0
        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));
        }
Ejemplo n.º 2
0
        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" : "")}")
                                  );
        }