public void StatementMatch() { Utils.Check(SessionData.StatementMatch != null, "Invalid call to StatementMatch"); MatchInfo match = SessionData.StatementMatch.ToObject <MatchInfo>(); int acct = SessionData.StatementImport.id; Account account = Database.Get <Account>(acct); // The existing transaction to match (or empty record if none) Extended_Document transaction = match.transaction < 0 ? Database.EmptyRecord <Extended_Document>() : SessionData.StatementImport.transactions[match.transaction].ToObject <Extended_Document>(); // The statement transaction dynamic current = SessionData.StatementImport.import[match.current]; Utils.Check(current != null, "No current transaction"); bool same = match.type == "Same"; bool documentHasVat = false; bool payment = false; decimal cAmount = current.Amount; int id = transaction.idDocument ?? 0; DocType type; if (match.transaction >= 0) { type = (DocType)transaction.DocumentTypeId; } else { switch (match.type) { case "Deposit": Utils.Check(account.AccountTypeId == (int)AcctType.Bank || account.AccountTypeId == (int)AcctType.OtherAsset || account.AccountTypeId == (int)AcctType.OtherLiability, "Deposit not to bank account"); type = DocType.Deposit; break; case "CardCredit": Utils.Check(account.AccountTypeId == (int)AcctType.CreditCard, "Credit not to credit card"); type = DocType.CreditCardCredit; break; case "Transfer": type = DocType.Transfer; break; case "CustomerPayment": type = DocType.Payment; break; case "Subscriptions": type = DocType.Subscriptions; break; case "Withdrawal": Utils.Check(account.AccountTypeId == (int)AcctType.Bank || account.AccountTypeId == (int)AcctType.OtherAsset || account.AccountTypeId == (int)AcctType.OtherLiability, "Withdrawal not to bank account"); type = DocType.Withdrawal; break; case "CardCharge": Utils.Check(account.AccountTypeId == (int)AcctType.CreditCard, "Charge not to credit card"); type = DocType.CreditCardCharge; break; case "BillPayment": type = DocType.BillPayment; break; default: throw new CheckException("Unknown match type {0}", match.type); } } GetParameters["acct"] = acct.ToString(); // This bank account string nameType = "O"; // Call appropriate method to get Record, and therefore transaction // Also set Module and Method, so appropriate template is used to display transaction before posting switch (type) { case DocType.Payment: Module = "customer"; Method = "payment"; Customer cust = new Customer() { CopyFrom = this }; cust.Payment(id); this.Record = cust.Record; this.Form = cust.Form; payment = true; nameType = "C"; break; case DocType.BillPayment: Module = "supplier"; Method = "payment"; Supplier supp = new Supplier() { CopyFrom = this }; supp.Payment(id); this.Record = supp.Record; this.Form = supp.Form; payment = true; nameType = "S"; break; case DocType.Withdrawal: case DocType.Deposit: case DocType.CreditCardCharge: case DocType.CreditCardCredit: Method = "document"; Document(id, type); documentHasVat = true; break; case DocType.Transfer: Method = "transfer"; Transfer(id); break; case DocType.Subscriptions: Module = "Members"; Method = "document"; Members member = new Members() { CopyFrom = this }; member.Document(id); this.Record = member.Record; this.Form = member.Form; nameType = "M"; break; default: throw new CheckException("Unexpected document type:{0}", type.UnCamel()); } dynamic record = (JObject)Record; dynamic doc = record.header; if (id == 0 && type == DocType.Transfer && cAmount > 0) { // New transfer in doc.TransferAccountId = acct; doc.DocumentAccountId = 0; doc.DocumentAccountName = ""; } if (string.IsNullOrWhiteSpace(doc.DocumentMemo.ToString())) { // Generate a memo string name = current.Name; string memo = current.Memo; if (string.IsNullOrWhiteSpace(memo)) { memo = name; } else if (!memo.Contains(name)) { memo = name + " " + memo; } doc.DocumentMemo = memo; } if (!same && type != DocType.Subscriptions) { // They want to create a new document - try to guess the DocumentName string name = doc.DocumentName; string currentName = current.Name; currentName = currentName.Split('\n', '\t')[0]; if (string.IsNullOrWhiteSpace(name) || (!payment && name.SimilarTo(currentName) < 0.5)) { doc.DocumentName = currentName; doc.DocumentNameAddressId = 0; float maxsimilarity = 0.4999f; foreach (NameAddress n in Database.Query <NameAddress>("SELECT * FROM NameAddress WHERE Type = " + Database.Quote(nameType) + " AND Name <= " + Database.Quote(currentName) + " AND Name LIKE " + Database.Quote(currentName.Length <= 5 ? currentName : currentName.Substring(0, 5) + "%"))) { float similarity = n.Name.SimilarTo(currentName); if (similarity > maxsimilarity) { doc.DocumentName = n.Name; doc.DocumentNameAddressId = n.idNameAddress; maxsimilarity = similarity; } } } } doc.DocumentDate = current.Date; decimal tAmount = doc.DocumentAmount; decimal diff = Math.Abs(cAmount) - Math.Abs(tAmount); doc.DocumentAmount += diff; if (same) { Utils.Check(diff == 0, "Amounts must be the same"); } else { // New transaction doc.DocumentOutstanding = doc.DocumentAmount; doc.Clr = ""; doc.idDocument = doc.Id = null; if (Utils.ExtractNumber(doc.DocumentIdentifier.ToString()) > 0) { doc.DocumentIdentifier = "<next>"; } } if (string.IsNullOrEmpty(doc.DocumentIdentifier.ToString())) { if (current.Id != null) { doc.DocumentIdentifier = current.Id; } else { int no = Utils.ExtractNumber(current.Name.ToString()); if (no != 0) { doc.DocumentIdentifier = no.ToString(); } } } if (diff != 0 && documentHasVat) { // Adjust first line to account for difference if (record.detail.Count == 0) { record.detail.Add(new InvoiceLine().ToJToken()); } dynamic line = record.detail[0]; decimal val = line.LineAmount + line.VatAmount + diff; if (line.VatRate != 0) { line.VatAmount = Math.Round(val * line.VatRate / (100 + line.VatRate), 2); val -= line.VatAmount; } line.LineAmount = val; } if (payment && !same) { removePayments(record); } record.StatementAccount = acct; if (same) { // Just post the new information if (type == DocType.Transfer) { record = record.header; // Transfer posts header alone } AjaxReturn p = StatementMatchSave((JObject)record); if (p.error == null) { Redirect(p.redirect); // If no error, go on with matching } } }
/// <summary> /// Save a matched transaction. /// May be called direct from StatementMatch for Same transactions, /// or when the user presses "Save" for other transactions /// </summary> public AjaxReturn StatementMatchSave(JObject json) { Utils.Check(SessionData.StatementMatch != null, "Invalid call to StatementMatchSave"); MatchInfo match = SessionData.StatementMatch.ToObject <MatchInfo>(); JArray transactions = SessionData.StatementImport.transactions; dynamic transaction = match.transaction < 0 ? null : SessionData.StatementImport.transactions[match.transaction]; DocType type = match.transaction < 0 ? match.type == "Transfer" ? DocType.Transfer : DocType.Withdrawal : (DocType)((JObject)transactions[match.transaction]).AsInt("DocumentTypeId"); AjaxReturn result; switch (type) { case DocType.Payment: result = new Customer() { Context = Context, GetParameters = GetParameters, PostParameters = PostParameters, Parameters = Parameters, }.PaymentSave(json.To <CustomerSupplier.PaymentDocument>()); break; case DocType.BillPayment: result = new Supplier() { Context = Context, GetParameters = GetParameters, PostParameters = PostParameters, Parameters = Parameters, }.PaymentSave(json.To <CustomerSupplier.PaymentDocument>()); break; case DocType.Withdrawal: case DocType.Deposit: case DocType.CreditCardCharge: case DocType.CreditCardCredit: result = DocumentSave(json.To <BankingDocument>()); break; case DocType.Transfer: result = TransferSave(json.To <TransferDocument>()); break; case DocType.Subscriptions: result = new Members() { Context = Context, GetParameters = GetParameters, PostParameters = PostParameters, Parameters = Parameters, }.DocumentSave(json.To <Members.SubscriptionDocument>()); break; default: throw new CheckException("Unexpected document type:{0}", type.UnCamel()); } if (result.error == null) { if (match.transaction >= 0 && match.type == "Same") { transaction.Matched = 1; } JArray items = SessionData.StatementImport.import; items.RemoveAt(match.current); int acct = SessionData.StatementImport.id; result.redirect = "/banking/" + (items.Count == 0 ? "detail.html?id=" + acct : "statementmatching.html"); } return(result); }