public static void AutomatchAgainstUnbalancedTransactions(Organization organization) { // Matches unbalanced financial transactions against unclosed payouts // Should this be in bot? Payouts payouts = ForOrganization(organization); FinancialTransactions transactions = FinancialTransactions.GetUnbalanced(organization); foreach (FinancialTransaction transaction in transactions) { // Console.WriteLine("Looking at transaction #{0} ({1:yyyy-MM-dd}, {2:N2}).", transaction.Identity, transaction.DateTime, transaction.Rows.AmountTotal); // First, establish that there are no similar transactions within 7 days. N^2 search. DateTime timeLow = transaction.DateTime.AddDays(-7); DateTime timeHigh = transaction.DateTime.AddDays(7); bool foundCompeting = false; foreach (FinancialTransaction possiblyCompetingTransaction in transactions) { if (possiblyCompetingTransaction.Rows.AmountCentsTotal == transaction.Rows.AmountCentsTotal && possiblyCompetingTransaction.DateTime >= timeLow && possiblyCompetingTransaction.DateTime <= timeHigh && possiblyCompetingTransaction.Identity != transaction.Identity) { foundCompeting = true; // Console.WriteLine(" - Transaction #{0} ({1:yyyy-MM-dd} is competing, aborting", possiblyCompetingTransaction.Identity, possiblyCompetingTransaction.DateTime); } } if (foundCompeting) { continue; } // Console.WriteLine(" - no competing transactions...\r\n - transaction description is \"{0}\".", transaction.Description); // Console.WriteLine(" - looking for matching payouts"); int foundCount = 0; int payoutIdFound = 0; // As the amount of payouts grow, this becomes less efficient exponentially. foreach (Payout payout in payouts) { // Ugly hack to fix cash advance payouts DateTime payoutLowerTimeLimit = timeLow; DateTime payoutUpperTimeLimit = timeHigh; if (payout.AmountCents == -transaction.Rows.AmountCentsTotal && (payout.DependentCashAdvancesPayout.Count > 0 || payout.DependentCashAdvancesPayback.Count > 0)) { // HACK: While PW5 doesn't have a manual-debug interface, special case for cash advances payoutLowerTimeLimit = transaction.DateTime.AddDays(-60); payoutUpperTimeLimit = transaction.DateTime.AddDays(60); } // HACK: Allow for up to 20 days beyond scheduled payment to catch tax payments if (payout.DependentSalariesTax.Count > 0) { payoutLowerTimeLimit = transaction.DateTime.AddDays(-25); payoutUpperTimeLimit = transaction.DateTime.AddDays(3); // nobody pays taxes early... } if (payout.ExpectedTransactionDate >= payoutLowerTimeLimit && payout.ExpectedTransactionDate <= payoutUpperTimeLimit && payout.AmountCents == -transaction.Rows.AmountCentsTotal) { // Console.WriteLine(" - - payout #{0} matches ({1}, {2:yyyy-MM-dd})", payout.Identity, payout.Recipient, payout.ExpectedTransactionDate); try { // If this succeeds, there is a transaction already FinancialTransaction.FromDependency(payout); break; } catch (Exception) { // There isn't such a transaction, which is what we want } foundCount++; payoutIdFound = payout.Identity; } } if (foundCount == 0) { // Console.WriteLine(" - none found"); } else if (foundCount > 1) { // Console.WriteLine(" - multiple found, not autoprocessing"); } else { Payout payout = Payout.FromIdentity(payoutIdFound); payout.BindToTransactionAndClose(transaction, null); } } }
public static void AutomatchAgainstUnbalancedTransactions(Organization organization, Person person) { // Matches unbalanced financial transactions against unclosed outbound invoices // Should this be in bot? OutboundInvoices invoices = ForOrganization(organization); // gets all open // build a hash of all invoice reference numbers, ours and theirs (and let's hope for no collision...) // TODO: Collision detection Dictionary <string, OutboundInvoice> invoiceLookup = new Dictionary <string, OutboundInvoice>(); foreach (OutboundInvoice invoice in invoices) { invoiceLookup[RemoveNoise(invoice.TheirReference)] = invoice; invoiceLookup[RemoveNoise(invoice.Reference)] = invoice; } FinancialTransactions transactions = FinancialTransactions.GetUnbalanced(organization); foreach (FinancialTransaction transaction in transactions) { string[] words = transaction.Description.Split(' '); bool collision = false; int invoiceId = 0; OutboundInvoice identifiedInvoice = null; foreach (string word in words) { string cleanWord = RemoveNoise(word); if (invoiceLookup.ContainsKey(cleanWord)) { OutboundInvoice invoice = invoiceLookup[cleanWord]; if (transaction.Rows.AmountCentsTotal == invoice.AmountCents) { // Matching description and amount if (invoiceId != 0 && invoiceId != invoice.Identity) { collision = true; } else if (invoice.Open) // double check it wasn't closed previously in loop { invoiceId = invoice.Identity; identifiedInvoice = invoice; } } } } // If invoiceId != 0 and collision == false, we've found exactly one match if (invoiceId != 0 && !collision) { Payment.CreateSingle(organization, transaction.DateTime, identifiedInvoice.Currency, identifiedInvoice.AmountCents, identifiedInvoice, person); // Balance transaction against outbound invoices transaction.AddRow(organization.FinancialAccounts.AssetsOutboundInvoices, -identifiedInvoice.AmountCents, person); } } }