using BusinessLogic.Entities; using BusinessLogic.Extensions; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; namespace BusinessLogic.Models { public class Transaction { #region Private private decimal TransactionLinesTotal { get { return TransactionLines.Sum(x => x.Amount).ToTwoDecimalPlaces(); } } private decimal PendingRefundsTotal { get { return PendingRefunds.Sum(x => x.Amount).ToTwoDecimalPlaces(); } } private decimal ProcessedRefundsTotal { get { return ProcessedRefunds.Sum(x => x.Amount).ToTwoDecimalPlaces(); } } private decimal TransfersTotal { get { return Transfers.Where(x => x.Mop.IsAJournal()).Sum(x => x.Amount).ToTwoDecimalPlaces(); } } #endregion public int? SuspenseId { get { if (TransactionLines.FirstOrDefault().SuspenseProcessedTransactions.FirstOrDefault() != null) { return TransactionLines.FirstOrDefault().SuspenseProcessedTransactions.FirstOrDefault().SuspenseId; } else if (TransactionLines.FirstOrDefault().SuspenseProcessedTransactions1.FirstOrDefault() != null) { return TransactionLines.FirstOrDefault().SuspenseProcessedTransactions1.FirstOrDefault().SuspenseId; } return null; } } [Display(Name = "PSP reference")] public string PspReference { get { return TransactionLines.FirstOrDefault().PspReference; } } public string ParentPspReference { get; private set; } [Display(Name = "Entry date")] public DateTime? EntryDate { get { return TransactionLines.FirstOrDefault().EntryDate; } } [Display(Name = "Last updated date")] public DateTime? LastUpdatedDate { get { return TransactionLines.Union(ProcessedRefunds).Union(Transfers).OrderByDescending(x => x.TransactionDate).FirstOrDefault().TransactionDate; } } [Display(Name = "Authorisation code")] public string AuthorisationCode { get { return TransactionLines.FirstOrDefault().AuthorisationCode; } } [Display(Name = "Method of payment")] public string MopName { get { return string.Format("{0} ({1})", TransactionLines.FirstOrDefault().Mop.MopName, TransactionLines.FirstOrDefault().Mop.MopCode); } } [Display(Name = "Address line 1")] public string AddressLine1 { get { return TransactionLines.FirstOrDefault().CardHolderAddressLine1; } } [Display(Name = "Address line 1")] public string AddressLine2 { get { return TransactionLines.FirstOrDefault().CardHolderAddressLine2; } } [Display(Name = "Town or City")] public string AddressLine3 { get { return TransactionLines.FirstOrDefault().CardHolderAddressLine3; } } [Display(Name = "County")] public string AddressLine4 { get { return TransactionLines.FirstOrDefault().CardHolderAddressLine4; } } [Display(Name = "Postcode")] public string PostCode { get { return TransactionLines.FirstOrDefault().CardHolderPostCode; } } [Display(Name = "Internal reference")] public string InternalReference { get { return TransactionLines.FirstOrDefault().InternalReference; } } [Display(Name = "Payee name")] public string PayeeName { get { return TransactionLines == null || !TransactionLines.Any() ? string.Empty : TransactionLines.FirstOrDefault().CardHolderName; } } [Display(Name = "Created by")] public string CreatedBy { get { return User != null ? User.DisplayName : "System / Customer"; } } [Display(Name = "User")] public Entities.User User { get { return TransactionLines.FirstOrDefault().User; } } public List TransactionLines { get; private set; } public List RefundableTransactionLines { get; private set; } public List PendingRefunds { get; private set; } public List FailedRefunds { get; private set; } public List ProcessedRefunds { get; private set; } public List Transfers { get; private set; } public List FormattedTransfers { get; private set; } public List CreditNotes { get; private set; } public Dictionary> Journals { get { return GetJournals(); } } public decimal Total { get { return TransactionLines.Sum(x => x.Amount).ToTwoDecimalPlaces(); } } [Display(Name = "Total VAT")] public decimal TotalVat { get { return TransactionLines.Sum(x => x.VatAmount).ToTwoDecimalPlaces(); } } public bool RefundEnabled { get { return !PendingRefunds.Any() && AmountAvailableToTransferOrRefund > 0 && RefundableTransactionLines.Any(); } } public decimal AmountAvailableToTransferOrRefund { get { return (TransactionLinesTotal - Math.Abs(PendingRefundsTotal) - Math.Abs(ProcessedRefundsTotal) - Math.Abs(TransfersTotal)).ToTwoDecimalPlaces(); } } public bool ReceiptIssued { get { return TransactionLines.Any(x => x.ReceiptIssued == true); } } public Transaction(List processedTransactions , List pendingRefunds , List failedRefunds , List processedRefunds , List transfers , string parentPspReference) { if (processedTransactions == null) throw new ArgumentNullException("processedTransactions"); if (pendingRefunds == null) pendingRefunds = new List(); if (failedRefunds == null) failedRefunds = new List(); if (processedRefunds == null) processedRefunds = new List(); if (transfers == null) transfers = new List(); PendingRefunds = pendingRefunds; FailedRefunds = failedRefunds; ProcessedRefunds = processedRefunds; Transfers = transfers; TransactionLines = processedTransactions.Where(x => !x.IsACreditNote()).ToList(); RefundableTransactionLines = processedTransactions.Where(x => x.IsRefundable()).ToList(); CreditNotes = processedTransactions.Where(x => x.IsACreditNote()).ToList(); ParentPspReference = parentPspReference; FormattedTransfers = FormatTransfers(transfers); } public decimal AmountAvailableToTransferOrRefundForTransactionLine(string transactionReference) { var transaction = TransactionLines.FirstOrDefault(x => x.TransactionReference == transactionReference); if (transaction == null) return 0; var totalRefunded = ProcessedRefunds .Where(x => x.AccountReference == transaction.AccountReference && x.FundCode == transaction.FundCode) .Sum(x => x.Amount) ?? 0; var totalPendingRefunds = PendingRefunds .Sum(x => x.Amount) ?? 0; var totalTransferred = Transfers .Where(x => x.TransferReference == transactionReference && x.Mop.IsAJournal()) .Sum(x => x.Amount) ?? 0; return ((transaction.Amount ?? 0) - Math.Abs(totalRefunded) - Math.Abs(totalPendingRefunds) - Math.Abs(totalTransferred)).ToTwoDecimalPlaces(); } public bool CanTransferBeUndone(string transactionReference) { return Transfers.Where(x => x.TransactionReference == transactionReference) .All(x => x.TransferRollbackGuid == null); } [Display(Name = "Address")] public string FormattedAddress { get { var transaction = TransactionLines.FirstOrDefault(); return $@"{transaction.CardHolderAddressLine1?.Trim()}, {transaction.CardHolderAddressLine2?.Trim()}, {transaction.CardHolderAddressLine3?.Trim()}, {transaction.CardHolderAddressLine4?.Trim()}, {transaction.CardHolderPostCode?.Trim()}" .Replace(", , , , ,", "") .Replace(", , , ,", "") .Replace(", , ,", ",") .Replace(", ,", ",") .Trim(',', ' '); } } private Dictionary> GetJournals() { var journals = new Dictionary>(); var endpoints = new List(); // Get the end-point journals foreach (var transfer in Transfers) { if (Transfers.Any(x => x.TransferReference == transfer.TransactionReference)) { continue; } // transfer isn't a parent to any other transfer, so it's an end point. endpoints.Add(transfer); } foreach (var line in TransactionLines) { var exists = true; var audit = new List(); var level = 1; var transactionReference = line.TransactionReference; while (exists) { if (Transfers.Any(x => x.TransferReference == transactionReference)) { foreach (var transfer in Transfers.Where(x => x.TransferReference == transactionReference).OrderBy(x => x.EntryDate)) { audit.Add(new JournalItem() { Level = level, Journal = transfer }); level++; } } } if (audit.Any()) { journals.Add(line.TransactionReference, audit); } } return null; } private List FormatTransfers(List transfers) { var formattedTransfers = new List(); var pspReferences = transfers.Select(x => x.PspReference).Distinct(); foreach (var pspReference in pspReferences) { var credit = transfers.OrderBy(x => x.Id) .FirstOrDefault(x => x.PspReference == pspReference && x.IsACredit()); if (credit == null) throw new NullReferenceException(string.Format("Unable to locate credit record for PSP: {0}", pspReference)); var debit = transfers.OrderBy(x => x.Id) .FirstOrDefault(x => x.PspReference == pspReference && x.IsADebit()); if (credit == null) throw new NullReferenceException(string.Format("Unable to locate debit record for PSP: {0}", pspReference)); formattedTransfers.Add(new JournalTransaction(credit, debit, null)); } return formattedTransfers; } } public class JournalItem { public int Level { get; set; } public ProcessedTransaction Journal { get; set; } } }