Beispiel #1
0
		public void DateDiffInMonth() {
			// 10/11/2016
			DateTime start = new DateTime(2015, 8, 11);
			Console.WriteLine(start);
			DateTime end = new DateTime(2016, 10, 11);
			Console.WriteLine(end);
			int dif = MiscUtils.DateDiffInMonths(start, end);
			Console.WriteLine(dif);
		}
Beispiel #2
0
        public ReschedulingResult Result;                  // output

        public override void Execute()
        {
            NL_AddLog(LogType.Info, "Strategy Start", this.ReschedulingArguments, null, null, null);

            if (!this.ReschedulingArguments.RescheduleIn && this.ReschedulingArguments.PaymentPerInterval == null)
            {
                this.Result.Error = "Weekly/monthly payment amount for OUT rescheduling not provided";
                this.loanRep.Clear();
                return;
            }

            try {
                this.loanRep.BeginTransaction();

                GetCurrentLoanState();

                if (this.tLoan == null)
                {
                    this.Result.Error       = string.Format("Loan ID {0} not found", this.ReschedulingArguments.LoanID);
                    this.Result.BlockAction = true;
                    ExitStrategy("Exit_1");
                    return;
                }

                // check status, don't continue for "PaidOff"
                if (this.tLoan.Status == LoanStatus.PaidOff)
                {
                    this.Result.Error       = string.Format("Loan ID {0} paid off. Loan balance: {1}", this.tLoan.Id, 0m.ToString("C2", this.cultureInfo));
                    this.Result.BlockAction = true;
                    ExitStrategy("Exit_2", this.sendDebugMail);
                    return;
                }

                // input validation for "IN"
                if (this.ReschedulingArguments.RescheduleIn && (this.Result.FirstItemDate > this.Result.LoanCloseDate))
                {
                    this.Result.Error       = "Within loan arrangement is impossible";
                    this.Result.BlockAction = true;
                    ExitStrategy("Exit_3", this.sendDebugMail);
                    return;
                }

                // "IN" - check between interval boundaries
                if (this.ReschedulingArguments.RescheduleIn && (this.Result.FirstItemDate < this.Result.ReschedulingIntervalStart || this.Result.FirstItemDate > this.Result.ReschedulingIntervalEnd))
                {
                    this.Result.Error       = "Wrong re-scheduling date sent (any day on the calendar within next 30 days allowed)";
                    this.Result.BlockAction = true;
                    ExitStrategy("Exit_3a", this.sendDebugMail);
                    return;
                }

                // "OUT" - check past date
                if (this.ReschedulingArguments.RescheduleIn == false && this.Result.FirstItemDate.Date < this.Result.ReschedulingIntervalStart)
                {
                    this.Result.Error       = "Wrong re-scheduling date sent (only future date allowed)";
                    this.Result.BlockAction = true;
                    ExitStrategy("Exit_3b", this.sendDebugMail);
                    return;
                }

                this.Result.OpenPrincipal = this.tLoan.Principal;

                // if sent "default" value (0), replace by default calculated
                if (!this.ReschedulingArguments.RescheduleIn && this.ReschedulingArguments.PaymentPerInterval == 0)
                {
                    this.ReschedulingArguments.PaymentPerInterval = this.Result.DefaultPaymentPerInterval;
                }

                Log.Debug("\n==========RE-SCHEDULING======ARGUMENTS: {0}==========LoanState: {1}\n", this.ReschedulingArguments, this.tLoan);

                // check Marking loan {0} as 'PaidOff' in \ezbob\Integration\DatabaseLib\Model\Loans\Loan.cs(362)
                var calc = new LoanRepaymentScheduleCalculator(this.tLoan, DateTime.UtcNow, CurrentValues.Instance.AmountToChargeFrom);

                try {
                    if (calc.NextEarlyPayment() == 0)
                    {
                        this.Result.Error       = string.Format("Loan {0} marked as 'PaidOff'. Loan balance: {1}", this.tLoan.Id, 0m.ToString("C2", this.cultureInfo));
                        this.Result.BlockAction = true;
                        ExitStrategy("Exit_4", this.sendDebugMail);
                        return;
                    }
                    // ReSharper disable once CatchAllClause
                } catch (Exception calcEx) {
                    Log.Info("LoanRepaymentScheduleCalculator NextEarlyPayment EXCEPTION: {0}", calcEx.Message);
                }

                var lastPaidSchedule = this.tLoan.Schedule.OrderBy(s => s.Date).LastOrDefault(s => (s.Status == LoanScheduleStatus.Paid || s.Status == LoanScheduleStatus.PaidOnTime || s.Status == LoanScheduleStatus.PaidEarly));

                // if StopFutureInterest checked - add active "freeze inteval" from FirstItemDate untill NoLimitDate
                if (this.ReschedulingArguments.RescheduleIn == false && this.ReschedulingArguments.StopFutureInterest)
                {
                    //TODO : also add it to NL_InterestFreeze && deactivating.
                    LoanInterestFreeze freeze = new LoanInterestFreeze {
                        Loan             = this.tLoan,
                        StartDate        = lastPaidSchedule != null ? lastPaidSchedule.Date : this.tLoan.Date.Date, //this.Result.FirstItemDate,
                        EndDate          = this.noLimitDate,                                                        // NoLimitDate from LoanEditorController.cs - move to common area
                        InterestRate     = 0,
                        ActivationDate   = this.Result.FirstItemDate,
                        DeactivationDate = null
                    };
                    if (!this.tLoan.InterestFreeze.Contains(freeze))
                    {
                        this.tLoan.InterestFreeze.Add(freeze);
                    }

                    calc.GetState();                     // reload state with freeze consideration
                }

                decimal totalEarlyPayment = calc.TotalEarlyPayment();
                decimal P = this.Result.OpenPrincipal;
                decimal F = calc.FeesToPay;              // unpaid fees
                //decimal I = lastPaidSchedule != null ? (calc.GetInterestRate(lastPaidSchedule.Date.AddDays(1), this.Result.FirstItemDate) *P) : (calc.GetInterestRate(this.tLoan.Date.Date.AddDays(1), this.Result.FirstItemDate) * P); // unpaid interest till rescheduling start date
                decimal I = (totalEarlyPayment - P - F); // unpaid interest till first rescheduled item
                I = I < 0 ? 0 : I;                       // bugfix EZ-4236
                decimal r = ((this.ReschedulingArguments.RescheduleIn == false && this.ReschedulingArguments.StopFutureInterest)) ? 0 : this.tLoan.InterestRate;

                this.Result.ReschedulingBalance = (P + I + F);                 // not final - add to I period from rescheduling date untill new maturity date

                Log.Debug("--------------P: {0}, I: {1}, F: {2}, LoanCloseDate: {3}, totalEarlyPayment: {4}, r: {5}, ReschedulingBalance: {6}, \n lastPaidSchedule: {7}",
                          P,
                          I,
                          F,
                          this.Result.LoanCloseDate.Date,
                          totalEarlyPayment,
                          r,
                          this.Result.ReschedulingBalance,
                          lastPaidSchedule);

                // 3. intervals number

                // IN
                if (this.ReschedulingArguments.RescheduleIn)
                {
                    // add "grace" period - 14 days to maturity date
                    DateTime closeDateWithGrace = this.Result.LoanCloseDate.Date.AddDays(14);

                    this.Result.IntervalsNum = this.ReschedulingArguments.ReschedulingRepaymentIntervalType == RepaymentIntervalTypes.Month ? MiscUtils.DateDiffInMonths(this.Result.FirstItemDate, closeDateWithGrace) : MiscUtils.DateDiffInWeeks(this.Result.FirstItemDate, closeDateWithGrace);

                    // adjust intervals number +1 if needed
                    DateTime rescheduledCloseDate = this.ReschedulingArguments.ReschedulingRepaymentIntervalType == RepaymentIntervalTypes.Month ?
                                                    this.Result.FirstItemDate.AddMonths(this.Result.IntervalsNum) :
                                                    this.Result.FirstItemDate.AddDays(this.Result.IntervalsNum * 7);

                    Log.Debug("rescheduledCloseDate: {0}, Result.IntervalsNum: {1}, Result.LoanCloseDate: {2}, closeDateWithGrace: {3}", rescheduledCloseDate, this.Result.IntervalsNum, this.Result.LoanCloseDate.Date, closeDateWithGrace);

                    TimeSpan ts = closeDateWithGrace.Date.Subtract(rescheduledCloseDate.Date);

                    if (ts.Days > 0)
                    {
                        this.Result.IntervalsNum += 1;
                    }

                    Log.Debug("Adjusted intervals: rescheduledCloseDate: {0}, Result.IntervalsNum: {1}, Result.LoanCloseDate: {2}, closeDateWithGrace: {3}, dDays: {4}", rescheduledCloseDate, this.Result.IntervalsNum, this.Result.LoanCloseDate.Date, closeDateWithGrace, ts.Days);
                }

                // OUT - real intervals (k) calculation
                if (this.ReschedulingArguments.RescheduleIn == false)
                {
                    // too much payment per interval
                    if (this.ReschedulingArguments.PaymentPerInterval > this.Result.ReschedulingBalance)
                    {
                        // ReSharper disable once PossibleInvalidOperationException
                        this.message = string.Format("The entered amount accedes the outstanding balance of {0} for payment of {1}",
                                                     this.Result.ReschedulingBalance.ToString("C2", this.cultureInfo), this.ReschedulingArguments.PaymentPerInterval.Value.ToString("C2", this.cultureInfo));
                        this.Result.Error = this.message;
                        ExitStrategy("Exit_6", this.sendDebugMail);
                        return;
                    }

                    // ReSharper disable once PossibleInvalidOperationException
                    decimal m = (decimal)this.ReschedulingArguments.PaymentPerInterval;

                    // System.DivideByZeroException: Attempted to divide by zero prevent
                    decimal kDiv = (m - this.Result.ReschedulingBalance * r);
                    if (kDiv == 0)
                    {
                        kDiv = 1;
                    }

                    var k = (int)Math.Ceiling(this.Result.ReschedulingBalance / kDiv);
                    this.Result.IntervalsNum = k;

                    Log.Debug("k: {0}, P: {1}, I: {2}, F: {3}, r: {4}, oustandingBalance: {5}, m: {6}, StopFutureInterest: {7}", k, P, I, F, r, this.Result.ReschedulingBalance, m, this.ReschedulingArguments.StopFutureInterest);

                    // uncovered loan - too small payment per interval
                    if (k < 0)
                    {
                        this.Result.Error = "Chosen amount is not sufficient for covering the loan overtime, i.e. accrued interest will be always greater than the repaid amount per payment";
                        ExitStrategy("Exit_7", this.sendDebugMail);
                        return;
                    }

                    this.Result.LoanCloseDate = this.ReschedulingArguments.ReschedulingRepaymentIntervalType == RepaymentIntervalTypes.Month ? this.Result.FirstItemDate.AddMonths(k) : this.Result.FirstItemDate.AddDays(k * 7);

                    Log.Debug("new close date: {0}", this.Result.LoanCloseDate);

                    // DON'T DELETE - can be used in new calculator
                    //int n = (int)Math.Ceiling(P / (m - P * r));
                    //decimal x = 0m; = this.Result.ReschedulingBalance * r * (int)((k + 1) / 2) - P * r * (int)((n + 1) / 2);
                    //Log.Debug("n: {0}, k: {1}, P: {2}, I: {3}, F: {4}, r: {5}, oustandingBalance: {6}, m: {7}, X: {8}, closeDate: {9}, Result.IntervalsNum: {10}", n, k, P, I, F, r, this.Result.ReschedulingBalance, m, x, this.Result.LoanCloseDate, this.Result.IntervalsNum);
                }

                Log.Debug("close date: {0}, intervals: {1}", this.Result.LoanCloseDate, this.Result.IntervalsNum);

                if (this.Result.IntervalsNum == 0)
                {
                    this.Result.Error       = "Rescheduling impossible (calculated payments number 0)";
                    this.Result.BlockAction = true;
                    ExitStrategy("Exit_8", this.sendDebugMail);
                    return;
                }

                // remove unpaid (lates, stilltopays passed) and future unpaid schedule items
                foreach (var rmv in this.tLoan.Schedule.ToList <LoanScheduleItem>())
                {
                    // if loan has future items that already paid ("paid early"), re-scheduling not allowed
                    if ((rmv.Status == LoanScheduleStatus.Paid || rmv.Status == LoanScheduleStatus.PaidOnTime || rmv.Status == LoanScheduleStatus.PaidEarly) && rmv.Date > this.Result.FirstItemDate)
                    {
                        this.Result.Error = string.Format("Currently it is not possible to apply rescheduling future if payment/s relaying in the future have been already covered with early made payment, partially or entirely. " +
                                                          "You can apply rescheduling option after [last covered payment day].");
                        this.Result.BlockAction = true;
                        ExitStrategy("Exit_5", this.sendDebugMail);
                        return;
                    }
                    if (rmv.Date >= this.Result.FirstItemDate)
                    {
                        this.tLoan.Schedule.Remove(rmv);
                    }
                    if (rmv.Date <= this.Result.FirstItemDate && rmv.Status == LoanScheduleStatus.Late)
                    {
                        this.tLoan.Schedule.Remove(rmv);
                        this.tLoan.TryAddRemovedOnReschedule(new LoanScheduleDeleted().CloneScheduleItem(rmv));
                    }
                    if (rmv.Date <= this.Result.FirstItemDate && rmv.Status == LoanScheduleStatus.StillToPay)
                    {
                        this.tLoan.Schedule.Remove(rmv);
                        this.tLoan.TryAddRemovedOnReschedule(new LoanScheduleDeleted().CloneScheduleItem(rmv));
                    }
                }

                decimal balance        = P;
                decimal iPrincipal     = Decimal.Round(P / this.Result.IntervalsNum);
                decimal firstPrincipal = (P - iPrincipal * (this.Result.IntervalsNum - 1));

                //check "first iPrincipal negative" case: if first iPrincipal <= 0, remove this and reduce this.Result.IntervalsNum
                if (firstPrincipal < 0)
                {
                    Log.Debug("AAA Periods: {0}, newInstalment: {1}, close date: {2}, balance: {3}, firstItemDate: {4}, firstPrincipal: {5}, " +
                              "P: {6}, I: {7}, F: {8}, r: {9}", this.Result.IntervalsNum, iPrincipal, this.Result.LoanCloseDate, this.Result.ReschedulingBalance, this.Result.FirstItemDate, firstPrincipal, P, I, F, r);
                    this.Result.IntervalsNum -= 1;
                    firstPrincipal            = iPrincipal;
                    if ((iPrincipal * (this.Result.IntervalsNum - 1) + firstPrincipal) != balance)
                    {
                        this.Result.Error = "Failed to create new schedule.";
                        ExitStrategy("Exit_9", this.sendDebugMail);
                    }
                }

                Log.Debug("Periods: {0}, newInstalment: {1}, close date: {2}, balance: {3}, firstItemDate: {4}, firstPrincipal: {5}, " +
                          "P: {6}, I: {7}, F: {8}, r: {9}", this.Result.IntervalsNum, iPrincipal, this.Result.LoanCloseDate, this.Result.ReschedulingBalance, this.Result.FirstItemDate, firstPrincipal, P, I, F, r);

                // add new re-scheduled items, both for IN/OUT
                int position = this.tLoan.Schedule.Count;
                for (int j = 0; j < this.Result.IntervalsNum; j++)
                {
                    DateTime iStartDate     = this.ReschedulingArguments.ReschedulingRepaymentIntervalType == RepaymentIntervalTypes.Month ? this.Result.FirstItemDate.AddMonths(j) : this.Result.FirstItemDate.AddDays(7 * j);
                    decimal  iLoanRepayment = (j == 0) ? firstPrincipal : iPrincipal;
                    balance -= iLoanRepayment;

                    LoanScheduleItem item = new LoanScheduleItem()
                    {
                        Date          = iStartDate.Date,
                        InterestRate  = r,
                        Status        = LoanScheduleStatus.StillToPay,
                        Loan          = this.tLoan,
                        LoanRepayment = iLoanRepayment,
                        Balance       = balance,
                        Position      = ++position
                    };
                    this.tLoan.Schedule.Add(item);
                }

                //Log.Debug("--------------Loan modified: \n {0}", this.tLoan);

                //  after modification
                if (CheckValidateLoanState(calc) == false)
                {
                    ExitStrategy("Exit_10", this.sendDebugMail);
                    return;
                }

                Log.Debug("--------------Loan recalculated: \n {0}", this.tLoan);

                // prevent schedules with negative iPrincipal (i.e. LoanRepayment:-4.00)
                var negativeIPrincipal = this.tLoan.Schedule.FirstOrDefault(s => s.LoanRepayment < 0);
                if (negativeIPrincipal != null)
                {
                    this.Result.Error = "Negative principal in loan schedule";
                    ExitStrategy("Exit_11", this.sendDebugMail);
                    return;
                }

                // prevent "paidEarly" for newly created schedule items
                var newPaidEarly = this.tLoan.Schedule.FirstOrDefault(s => s.Date > this.ReschedulingArguments.ReschedulingDate && s.Status == LoanScheduleStatus.PaidEarly);
                if (newPaidEarly != null)
                {
                    this.Result.Error = "Wrong balance for re-scheduling calculated. Please, contact support.";
                    ExitStrategy("Exit_12", this.sendDebugMail);
                    return;
                }

                var firstRescheduledItem = this.tLoan.Schedule.FirstOrDefault(s => s.Date.Date == this.Result.FirstItemDate);
                if (firstRescheduledItem != null)
                {
                    this.Result.FirstPaymentInterest = firstRescheduledItem.Interest;
                }

                if (this.ReschedulingArguments.RescheduleIn == false)                   // OUT

                // NOT POSSIBLE WITH CURRENT CALCULATOR < DON'T DELETE
                //OffsetX(x);
                //if (CheckValidateLoanState(calc) == false)
                //	return;
                //Log.Debug("-------Loan recalculated+adjusted to X \n {0}", this.tLoan);

                // unsufficient payment per period
                {
                    LoanScheduleItem overInstalment = this.tLoan.Schedule.FirstOrDefault(s => s.AmountDue > this.ReschedulingArguments.PaymentPerInterval);
                    if (overInstalment != null)
                    {
                        // ReSharper disable once PossibleInvalidOperationException
                        this.message = string.Format("{0}ly payment of {1} not sufficient to pay the loan outstanding balance. Accrued interest: {2}, accumulated fees: {3}, first new instalment: {4}. " +
                                                     "You can choose to reduce the accumulated fees & interest by clearing them via manual payment, before setting the new payment schedule.",
                                                     this.ReschedulingArguments.ReschedulingRepaymentIntervalType,
                                                     this.ReschedulingArguments.PaymentPerInterval.Value.ToString("C2", this.cultureInfo),
                                                     overInstalment.Interest.ToString("C2", this.cultureInfo),    //I.ToString("C2", this.cultureInfo),
                                                     overInstalment.Fees.ToString("C2", this.cultureInfo),
                                                     overInstalment.AmountDue.ToString("C2", this.cultureInfo)
                                                     );
                        this.Result.Error = this.message;
                        ExitStrategy("Exit_13", this.sendDebugMail);
                        return;
                    }
                }

                if (!this.ReschedulingArguments.SaveToDB)
                {
                    ExitStrategy("Exit_14", this.sendDebugMail);
                    return;
                }

                LoanRescheduleSave();
                NL_AddLog(LogType.Info, "Strategy End", this.ReschedulingArguments, this.Result, null, null);
                // ReSharper disable once CatchAllClause
            } catch (Exception ex) {
                Log.Alert(ex, "Failed to get rescheduling data for loan {0}", this.ReschedulingArguments.LoanID);
                NL_AddLog(LogType.Error, "Strategy Faild", this.ReschedulingArguments, null, ex.ToString(), ex.StackTrace);
            }
        }
        }         // GetLates

        public DefaultsModel GetDefaults(
            int customerId,
            DateTime asOfDate,
            int minAmount,
            int lastMonthStatuses,
            List <CaisStatus> caisStatuses = null
            )
        {
            if (caisStatuses == null)
            {
                caisStatuses = GetConsumerCaisStatuses(customerId);
            }

            DateTime asOfMonth = new DateTime(asOfDate.Year, asOfDate.Month, 1, 0, 0, 0, DateTimeKind.Utc);

            DateTime lastRelevantMonth = asOfMonth.AddMonths(-lastMonthStatuses + 1);

            var numOfDefaults  = 0;
            var defaultsAmount = 0;

            foreach (CaisStatus caisStatus in caisStatuses)
            {
                DateTime thisMonth = new DateTime(
                    caisStatus.LastUpdatedDate.Year,
                    caisStatus.LastUpdatedDate.Month,
                    1,
                    0,
                    0,
                    0,
                    DateTimeKind.Utc
                    );

                if (thisMonth < lastRelevantMonth)
                {
                    continue;
                }

                int monthSinceUpdate = 1 + MiscUtils.DateDiffInMonths(thisMonth, asOfMonth);

                int useLastStatusMonths = lastMonthStatuses - monthSinceUpdate + 1;

                bool isDefaultInAccount = false;

                for (int i = 0; i < useLastStatusMonths; ++i)
                {
                    if (caisStatus.AccountStatusCodes.Length - i <= 0)
                    {
                        continue;
                    }

                    string            status        = caisStatus.AccountStatusCodes[caisStatus.AccountStatusCodes.Length - i - 1].ToString();
                    CaisAccountStatus accountStatus = AccountStatusDictionary.GetAccountStatus(status);
                    int balance = Math.Max(caisStatus.Balance, caisStatus.CurrentDefBalance);

                    if (accountStatus.IsDefault && balance > minAmount)
                    {
                        isDefaultInAccount = true;
                    }
                }                 // for

                if (isDefaultInAccount)
                {
                    numOfDefaults++;
                    defaultsAmount += caisStatus.Balance;
                }         // if
            }             // for each CAIS status

            return(new DefaultsModel {
                DefaultsAmount = defaultsAmount,
                NumOfDefaults = numOfDefaults
            });
        }         // GetDefaults
Beispiel #4
0
        }         // MaxQuarterTurnover

        private IEnumerable <FilteredAggregationResult> LastUpdatedEndHistoryTurnoversByMpType(
            List <MarketplaceTurnoverModel> inputList,
            Guid type,
            DateTime calculationTime,
            DateTime?lastExistingDataTime = null
            )
        {
            Log.Debug(
                "LastUpdatedEndHistoryTurnoversByMpType(type = '{0}', calculation time = '{1}', " +
                "last existing data time = '{2}') started.",
                type,
                calculationTime.MomentStr(),
                lastExistingDataTime.MomentStr()
                );

            List <MarketplaceTurnoverModel> ofcurrentType =
                inputList.Where(x => x.MarketplaceInternalID == type).ToList();

            if (ofcurrentType.Count < 1)
            {
                Log.Debug(
                    "LastUpdatedEndHistoryTurnoversByMpType returns empty result: " +
                    "no entries found in MarketplaceTurnover view with MP internal id '{0}'.",
                    type
                    );

                return(Enumerable.Empty <FilteredAggregationResult>());
            }             // if

            DateTime lastUpdateDate = ofcurrentType.Max(z => z.UpdatingEnd);

            DateTime periodStart = MiscUtils.GetPeriodAgo(
                calculationTime,
                lastUpdateDate,
                CurrentValues.Instance.TotalsMonthTail,
                lastExistingDataTime
                );

            DateTime periodEnd = periodStart.AddMonths(11);

            Log.Debug(
                "LastUpdatedEndHistoryTurnoversByMpType: " +
                "calculationTime = '{0}', lastUpdateDate = '{1}', period start = '{2}', period end = '{3}'.",
                calculationTime.MomentStr(),
                lastUpdateDate.MomentStr(),
                periodStart.MomentStr(),
                periodEnd.MomentStr()
                );

            List <MarketplaceTurnoverModel> histories =
                ofcurrentType.Where(z => z.TheMonth >= periodStart && z.TheMonth <= periodEnd).ToList();

            var os = new StringBuilder();

            histories.ForEach(x => os.AppendFormat(
                                  "\tMonth = {0}, turnover = {1}, MpHistoryID = {2}, MpID = {3}.\n",
                                  x.TheMonth,
                                  x.Turnover,
                                  x.CustomerMarketPlaceUpdatingHistoryID,
                                  x.CustomerMarketPlaceID
                                  ));

            Log.Debug(
                "LastUpdatedEndHistoryTurnoversByMpType: filtered MarketplaceTurnover entries are\n{0}",
                histories.Count < 1 ? "\tnothing found.\n" : os.ToString()
                );

            if (histories.Count < 1)
            {
                Log.Debug(
                    "LastUpdatedEndHistoryTurnoversByMpType returns empty result: " +
                    "no filtered entries found in MarketplaceTurnover."
                    );

                return(Enumerable.Empty <FilteredAggregationResult>());
            }             // if

            var groups = histories.GroupBy(ag => new {
                ag.CustomerMarketPlaceID,
                ag.TheMonth
            });

            var result = new List <FilteredAggregationResult>();

            foreach (var grp in groups)
            {
                MarketplaceTurnoverModel first = grp.OrderByDescending(p => p.AggID).First();

                var far = new FilteredAggregationResult {
                    Distance = (11 - MiscUtils.DateDiffInMonths(periodStart, first.TheMonth)),
                    TheMonth = first.TheMonth,
                    MpId     = first.CustomerMarketPlaceID,
                    Turnover = first.Turnover
                };

                result.Add(far);
            }             // for each group

            os.Clear();

            result.ForEach(x => os.AppendFormat(
                               "\tMonth = {0}, Distance = {1}, Turnover = {2}, MpID = {3}.\n",
                               x.TheMonth,
                               x.Distance,
                               x.Turnover,
                               x.MpId
                               ));

            Log.Debug(
                "LastUpdatedEndHistoryTurnoversByMpType: result entries are\n{0}",
                result.Count < 1 ? "\tnothing found.\n" : os.ToString()
                );

            return(result);
        }         // LastUpdatedEndHistoryTurnoversByMpType