using BusinessLogic.Classes.Result; using BusinessLogic.Entities; using BusinessLogic.Extensions; using BusinessLogic.Interfaces.Persistence; using BusinessLogic.Interfaces.Result; using BusinessLogic.Interfaces.Security; using BusinessLogic.Interfaces.Services; using BusinessLogic.Models; using BusinessLogic.Models.Suspense; using log4net; using System; using System.Collections.Generic; using System.Linq; namespace BusinessLogic.Suspense.JournalAllocation { public class DistinctTransactionJournalAllocationStrategy : IJournalAllocationStrategy { private readonly ILog _logger; private readonly IUnitOfWork _unitOfWork; private readonly ISecurityContext _securityContext; private readonly ITransactionJournalService _transactionJournalService; private readonly IJournalAllocationStrategyValidator _journalAllocationStrategyValidator; private readonly Guid _processId; private readonly string _creditMopCode; private readonly string _suspenseTransactionVatCode; private readonly string _suspenseTransactionFundCode; public DistinctTransactionJournalAllocationStrategy( ILog logger , IUnitOfWork unitOfWork , ISecurityContext securityContext , ITransactionJournalService transactionJournalService , IJournalAllocationStrategyValidator journalAllocationStrategyValidator) { _logger = logger; _unitOfWork = unitOfWork; _securityContext = securityContext; _transactionJournalService = transactionJournalService; _journalAllocationStrategyValidator = journalAllocationStrategyValidator; _creditMopCode = GetCreditNoteMopCode(); _suspenseTransactionVatCode = GetSuspenseTransactionVatCode(); _suspenseTransactionFundCode = GetSuspenseTransactionFundCode(); _processId = Guid.NewGuid(); } private string GetCreditNoteMopCode() { return _unitOfWork.Mops.GetAll(true).FirstOrDefault(x => x.IsAJournalReallocation()).MopCode; } private string GetSuspenseTransactionVatCode() { return _unitOfWork.Vats.GetAll(true).FirstOrDefault(x => x.IsASuspenseTransactionVatCode()).VatCode; } private string GetSuspenseTransactionFundCode() { return _unitOfWork.Funds.GetAll(true).FirstOrDefault(x => x.IsASuspenseTransactionFund()).FundCode; } public IResult Execute(List suspenseItems , List journalItems , List creditNotes) { try { Lock(suspenseItems); var suspenses = GetSuspenseItems(); Validate(suspenses, journalItems, creditNotes); Distribute(suspenses, journalItems, creditNotes); } catch (SuspenseJournalAllocationException ex) { _logger.Error(ex); return new Result(ex.Message); } catch (Exception ex) { _logger.Error(ex); return new Result("Unable to journal the items"); } finally { try { Unlock(); } catch (Exception e) { _logger.Error("Unable to unlock suspense records!", e); } } return new Result(); } private void Lock(List suspenseItems) { _unitOfWork.Suspenses.Lock(suspenseItems, _processId); _unitOfWork.Complete(_securityContext.UserId); } private void Unlock() { _unitOfWork.Suspenses.Unlock(_processId); _unitOfWork.Complete(_securityContext.UserId); } private List GetSuspenseItems() { return _unitOfWork.Suspenses .GetSuspensesBeingProcessed(_processId) .Select(x => new SuspenseWrapper(x)) .OrderByDescending(x => x.Item.Amount) .ToList(); } private void Validate(List suspenses, List journalItems, List creditNotes) { _journalAllocationStrategyValidator.Validate(new JournalAllocationStrategyValidatorArgs() { Suspenses = suspenses, JournalItems = journalItems, CreditNotes = creditNotes }); } private void Distribute(List suspenses, List journalItems, List creditNotes) { foreach (var journal in journalItems.OrderByDescending(x => x.Amount)) { var narrative = string.IsNullOrWhiteSpace(journal.Narrative) ? suspenses.FirstOrDefault(x => x.Item.Narrative != string.Empty).Item.Narrative : journal.Narrative; var importId = suspenses.FirstOrDefault(x => x.Item.ImportId.HasValue).Item.ImportId.Value; var suspenseTransactionDate = suspenses.FirstOrDefault(x => x.Item.ImportId.HasValue).Item.CreatedAt; // we only allow 1 suspense item at the moment but if that changes this is not OK // If the journal amount is greater than the remainder of the suspense amount, add a credit note(s) for the difference var suspenseAmountRemaining = suspenses.Sum(x => x.AmountRemaining); var creditNotesToJournal = new List(); if (suspenseAmountRemaining < journal.Amount) { var totalCreditNotesToAdd = journal.Amount - suspenseAmountRemaining; creditNotesToJournal = GenerateCreditNoteJournals(creditNotes, importId, totalCreditNotesToAdd, narrative); } var result = _transactionJournalService.CreateJournal( new TransferItem() { AccountReference = journal.AccountReference, Amount = journal.Amount, FundCode = journal.FundCode, MopCode = journal.MopCode, Narrative = narrative, ImportId = importId, VatCode = journal.VatCode }, new TransferItem() { AccountReference = journal.AccountReference, Amount = -(journal.Amount - creditNotesToJournal.Sum(x => x.Amount)), FundCode = _suspenseTransactionFundCode, Narrative = suspenses.FirstOrDefault(x => x.Item.Narrative != string.Empty).Item.Narrative, ImportId = importId, VatCode = _suspenseTransactionVatCode, }, creditNotesToJournal, _processId.ToString(), suspenseTransactionDate ).Data as JournalTransaction; foreach (var suspense in suspenses.Where(x => x.AmountRemaining > 0)) { if (journal.Amount >= suspense.AmountRemaining) { journal.Amount -= suspense.AmountRemaining; var a = new SuspenseProcessedTransaction() { SuspenseId = suspense.Item.Id, Amount = suspense.AmountRemaining, CreatedAt = DateTime.Now, CreatedByUserId = _securityContext.UserId, TransactionIn = result.Credit, TransactionOut = result.Debit }; suspense.Item.SuspenseProcessedTransactions.Add(a); } else if (journal.Amount < suspense.AmountRemaining) { var a = new SuspenseProcessedTransaction() { SuspenseId = suspense.Item.Id, Amount = journal.Amount, CreatedAt = DateTime.Now, CreatedByUserId = _securityContext.UserId, TransactionIn = result.Credit, TransactionOut = result.Debit }; suspense.Item.SuspenseProcessedTransactions.Add(a); journal.Amount = 0; } if (journal.Amount == 0) break; } } _unitOfWork.Complete(_securityContext.UserId); } public List GenerateCreditNoteJournals(List creditNotes, int importId, decimal totalToCredit, string narrative) { var creditTransferItems = new List(); foreach (var creditNote in creditNotes.Where(x => x.Amount > 0).OrderByDescending(y => y.Amount)) { if (totalToCredit <= 0) continue; if (creditNote.Amount >= totalToCredit) { creditNote.Amount -= totalToCredit; creditTransferItems.Add(new TransferItem() { AccountReference = creditNote.AccountReference, Amount = totalToCredit, FundCode = creditNote.FundCode, MopCode = _creditMopCode, Narrative = narrative, ImportId = importId, VatCode = creditNote.VatCode }); totalToCredit = 0; } else if (creditNote.Amount < totalToCredit) { creditTransferItems.Add(new TransferItem() { AccountReference = creditNote.AccountReference, Amount = creditNote.Amount, FundCode = creditNote.FundCode, MopCode = _creditMopCode, Narrative = narrative, ImportId = importId, VatCode = creditNote.VatCode }); totalToCredit -= creditNote.Amount; creditNote.Amount = 0; } } return creditTransferItems; } } }