/// <summary> /// Handles the creation of a manual document matching /// </summary> /// <param name="request">request</param> /// <param name="cancellationToken">cancellationToken</param> public async Task <ManualDocumentMatchingRecord> Handle(CreateManualDocumentMatchingCommand request, CancellationToken cancellationToken) { _unitOfWork.BeginTransaction(); try { var manualDocumentMatchingRecord = _mapper.Map <ManualDocumentMatchingRecord>(request); var matchableDocumentIds = new List <MatchableSourceIdDto>(); // Getting the information for the documents that we want to match // We get mostly the rates of exchange which have been fixed during the posting of these documents foreach (var document in manualDocumentMatchingRecord.DocumentMatchings) { matchableDocumentIds.Add(new MatchableSourceIdDto() { SourceCashLineId = document.SourceCashLineId, SourceInvoiceId = document.SourceInvoiceId, SourceJournalLineId = document.SourceJournalLineId }); } var matchableDocumentsSummaryInformation = await _manualDocumentMatchingQueries.GetMatchableDocumentsSummaryInformation( request.Company, matchableDocumentIds); // Rebuilding a complete list of records to be pushed as a parameter of the "create match" SP List <DocumentMatching> documentMatchingRecords = new List <DocumentMatching>(); foreach (var documentMatchingRecord in manualDocumentMatchingRecord.DocumentMatchings) { DocumentMatching documentMatching = new DocumentMatching(); documentMatching.TransactionDocumentId = documentMatchingRecord.TransactionDocumentId; // There is a complete mismatch into the names used in the exchange with the // app (documentmatching.Amount is the full document amount), and the one // understood by the DB (documentmatching.Amount is the amount matched in the doc matching) // The right solution should be to use different classes... documentMatching.Amount = documentMatchingRecord.DocumentAmount; var matchableDocumentSummaryInformation = matchableDocumentsSummaryInformation .Where(d => d.TransactionDocumentId == documentMatchingRecord.TransactionDocumentId).FirstOrDefault(); switch ((MasterDocumentType)matchableDocumentSummaryInformation.TransactionDocumentTypeId) { case MasterDocumentType.PI: case MasterDocumentType.CN: case MasterDocumentType.CP: documentMatching.TransactionDirectionId = (int)TransactionDirection.Pay; break; case MasterDocumentType.SI: case MasterDocumentType.DN: case MasterDocumentType.CI: documentMatching.TransactionDirectionId = (int)TransactionDirection.Recieve; break; case MasterDocumentType.JL: documentMatching.TransactionDirectionId = documentMatching.DocumentAmount > 0 ? (int)TransactionDirection.Pay : (int)TransactionDirection.Recieve; break; default: // We should not be there... break; } switch ((MasterDocumentType)matchableDocumentSummaryInformation.TransactionDocumentTypeId) { case MasterDocumentType.PI: case MasterDocumentType.CN: case MasterDocumentType.CI: documentMatching.SigningFactor = -1; break; case MasterDocumentType.CP: case MasterDocumentType.SI: case MasterDocumentType.DN: case MasterDocumentType.JL: documentMatching.SigningFactor = 1; break; } documentMatching.TransactionDocumentTypeId = documentMatchingRecord.TransactionDocumentTypeId; documentMatching.DepartmentId = documentMatchingRecord.DepartmentId; documentMatching.DocumentDate = documentMatchingRecord.DocumentDate; decimal?matchedAmount = documentMatching.Amount; // Calculation of the statutory / fct ccy amounts for the document matching if (matchedAmount == matchableDocumentSummaryInformation.UnmatchedAmount) { // In the case we are fully matching the document, we set the statutory & functional matched amounts // to the value of the remaining unmatched amount. This way, we are sure that, in the case a document has // multiple matchings, the sum of the matched amounts in statutory & fct ccies are = to the total amount // in these ccies documentMatching.StatutoryCcyAmount = matchableDocumentSummaryInformation.UnmatchedAmountInStatutoryCurrency; documentMatching.FunctionalCcyAmount = matchableDocumentSummaryInformation.UnmatchedAmountInFunctionalCurrency; } else { // We are not fully matching the matchable document. // Calculate the statutory & functional currencies based on the rate of exchange stored // in the transaction document which is matched, at the time of its posting // NOTE THAT the unmatched ccies will be updated by a SP called when saving the document matching documentMatching.StatutoryCcyAmount = AmountConverter.ConvertAmountThroughIntermediateRoeToUSD( documentMatching.Amount.Value, matchableDocumentSummaryInformation.RoeDocumentCurrency, matchableDocumentSummaryInformation.RoeDocumentCurrencyType, matchableDocumentSummaryInformation.RoeStatutoryCurrency, matchableDocumentSummaryInformation.RoeStatutoryCurrencyType, 2); documentMatching.FunctionalCcyAmount = AmountConverter.ConvertAmountThroughIntermediateRoeToUSD( documentMatching.Amount.Value, matchableDocumentSummaryInformation.RoeDocumentCurrency, matchableDocumentSummaryInformation.RoeDocumentCurrencyType, matchableDocumentSummaryInformation.RoeFunctionalCurrency, matchableDocumentSummaryInformation.RoeFunctionalCurrencyType, 2); } documentMatching.IsCreditOrDebit = documentMatchingRecord.IsCreditOrDebit; if (documentMatchingRecord.LineId == 0) { documentMatching.LineId = null; } else { documentMatching.LineId = documentMatchingRecord.LineId; } documentMatching.Credit = documentMatchingRecord.Credit; documentMatching.Debit = documentMatchingRecord.Debit; documentMatching.SourceCashLineId = documentMatchingRecord.SourceCashLineId == 0 ? null : documentMatchingRecord.SourceCashLineId; documentMatching.SourceInvoiceId = documentMatchingRecord.SourceInvoiceId == 0 ? null : documentMatchingRecord.SourceInvoiceId; documentMatching.SourceJournalLineId = documentMatchingRecord.SourceJournalLineId == 0 ? null : documentMatchingRecord.SourceJournalLineId; documentMatchingRecords.Add(documentMatching); } // Saving the matchflag in the database manualDocumentMatchingRecord.DocumentMatchings = documentMatchingRecords; var matchingRecord = await _manualDocumentMatchingRepository.CreateUpdateDocumentMatching(manualDocumentMatchingRecord); manualDocumentMatchingRecord.MatchFlagCode = matchingRecord.MatchFlagCode; manualDocumentMatchingRecord.MatchFlagId = matchingRecord.MatchFlagId; // Calculating the sums of the fct & statut ccy amounts for all the documents, to check if they are = 0 or not // This will determine the need for a reval record var sumsByDepartmentAndDirection = documentMatchingRecords.GroupBy(d => new { d.DepartmentId, d.TransactionDirectionId }) .Select(g => new { g.Key.DepartmentId, g.Key.TransactionDirectionId, Amount = g.Sum(s => s.Amount * s.SigningFactor), FunctionalCcyAmount = g.Sum(s => s.FunctionalCcyAmount * s.SigningFactor), StatutoryCcyAmount = g.Sum(s => s.StatutoryCcyAmount * s.SigningFactor), }).ToList(); if (sumsByDepartmentAndDirection.Any(sum => sum.FunctionalCcyAmount != 0 || sum.StatutoryCcyAmount != 0)) { // Need to create a reval document // Note that reval documents are created (for cash by picking) at posting time of the cbp var revaluationInformation = await _transactionDocumentRepository.CreateRevaluation( request.Company, null, request.CurrencyCode, matchingRecord.MatchFlagId.Value, request.PaymentDocumentDate, request.PaymentDocumentDate, request.PaymentDocumentDate); await EnqueueMessageForAccountingDocumentCreation(revaluationInformation.TransactionDocumentId, request.Company); manualDocumentMatchingRecord.ReversalRevalJournalCode = revaluationInformation.DocumentReference; } _unitOfWork.Commit(); _logger.LogInformation("Document match created with match id {Atlas.MatchFlagId}.", manualDocumentMatchingRecord.MatchFlagId.Value); return(manualDocumentMatchingRecord); } catch { _unitOfWork.Rollback(); throw; } }