Разделить сумму за диапазон дат на сумму за месяц / год - PullRequest
1 голос
/ 26 сентября 2019

Я в основном пытаюсь сделать то же самое, что и здесь , только я хочу сделать это в C #, а не в SQL.

У меня есть класс:

public class AmountPerPeriod
{
    public int Id { get; set; }
    public DateTime Startdate { get; set; }
    public DateTime Enddate { get; set; }
    public decimal Ammount { get; set; }
}

В этом примере, скажем, я заполняю список AmountPerPeriod элементов:

var lstAmmountPerPeriod = new List<AmountPerPeriod>()
            {
                new AmountPerPeriod
                {
                    Id = 1,
                    Startdate = new DateTime(2019, 03, 21),
                    Enddate = new DateTime(2019, 05, 09),
                    Ammount = 10000
                },
                new AmountPerPeriod
                {
                    Id = 2,
                    Startdate = new DateTime(2019, 04, 02),
                    Enddate = new DateTime(2019, 04, 10),
                    Ammount = 30000
                },
                new AmountPerPeriod
                {
                    Id = 3,
                    Startdate = new DateTime(2018, 11, 01),
                    Enddate = new DateTime(2019, 01, 08),
                    Ammount = 20000
                }
            };

Я хочу, чтобы мой вывод был классом List of AmountPerMonth, который выглядит следующим образом:

public class AmountPerMonth
{
    public int Id { get; set; }
    public int Year { get; set; }
    public int Month { get; set; }
    public decimal Ammount { get; set; }
}

Как будто я должен был попробовать, и у меня есть рабочий метод, который я чувствую, как сложный путь.Этот метод, обеспечивающий правильный результат, выглядит следующим образом:

var result = new List<AmountPerMonth>();

    foreach (var item in lstAmmountPerPeriod)
    {
        if (item.Startdate.Year == item.Enddate.Year && item.Startdate.Month == item.Enddate.Month)
        {
            result.Add(new AmountPerMonth
            {
                Ammount = item.Ammount,
                Id = item.Id,
                Month = item.Startdate.Month,
                Year = item.Startdate.Year
            });
        }
        else
        {
            var numberOfDaysInPeriod = (item.Enddate - item.Startdate).Days+1;
            var amountPerDay = item.Ammount / numberOfDaysInPeriod;
            var periodStartDate = item.Startdate;

            bool firstPeriod = true;

            while (periodStartDate.ToFirstDateOfMonth() <= item.Enddate.ToFirstDateOfMonth())
            {
                if (firstPeriod)
                {
                    result.Add(new AmountPerMonth
                    {
                        Ammount = ((periodStartDate.ToLastDateOfMonth()-periodStartDate).Days+1)*amountPerDay,
                        Id = item.Id,
                        Month = periodStartDate.Month,
                        Year = periodStartDate.Year
                    });
                }
                else if (periodStartDate.Month != item.Enddate.Month)
                {
                    result.Add(new AmountPerMonth
                    {
                        Ammount = ((periodStartDate.ToLastDateOfMonth()-periodStartDate.ToFirstDateOfMonth()).Days+1) * amountPerDay,
                        Id = item.Id,
                        Month = periodStartDate.Month,
                        Year = periodStartDate.Year
                    });
                }
                else
                {
                    result.Add(new AmountPerMonth
                    {
                        Ammount = ((item.Enddate - periodStartDate.ToFirstDateOfMonth()).Days+1) * amountPerDay,
                        Id = item.Id,
                        Month = periodStartDate.Month,
                        Year = periodStartDate.Year
                    });
                }

                periodStartDate = periodStartDate.AddMonths(1);

                firstPeriod = false;
            }
        }
    }


// assert using fluentassertions
result.Count.Should().Be(7);
result.First().Ammount.Should().Be(2200);
result.Last().Ammount.Should().BeApproximately(2318.84M, 2);

// list with result basicly should contain:
// ID |month |year   |ammount
// ---|------|-------|--------
// 1  |3     | 2019  | 2200.00
// 1  |4     | 2019  | 6000.00
// 1  |5     | 2019  | 1800.00
// 2  |4     | 2019  |30000.00
// 3  |11    | 2018  | 8695.65
// 3  |12    | 2018  | 8985.51
// 3  |1     | 2019  | 2318.84

Как я уже говорил, должен быть намного более простой способ, даже возможно с использованием LINQ.У кого-нибудь есть предложения?

Заранее спасибо

Ответы [ 2 ]

2 голосов
/ 26 сентября 2019

С LINQ гораздо проще.

var output =
    from app in lstAmmountPerPeriod
    let days = (int)app.Enddate.Date.Subtract(app.Startdate.Date).TotalDays + 1
    from day in Enumerable.Range(0, days)
    let daily = new AmountPerPeriod()
    {
        Id = app.Id,
        Startdate = app.Startdate.AddDays(day),
        Enddate = app.Startdate.AddDays(day),
        Ammount = app.Ammount / days
    }
    group daily.Ammount by new
    {
        daily.Id,
        daily.Startdate.Year,
        daily.Startdate.Month
    } into gds
    select new AmountPerMonth()
    {
        Id = gds.Key.Id,
        Year = gds.Key.Year,
        Month = gds.Key.Month,
        Ammount = gds.Sum(),
    };

Это дает мне:

output

1 голос
/ 26 сентября 2019

Вот еще один способ сделать это, основное отличие здесь в том, что я использую цикл for, чтобы постоянно обновлять «первый день» до первого дня в периоде или до первого дня текущего месяца,и от «последнего дня» до последнего дня текущего месяца или до последнего дня периода, в зависимости от того, что меньше.

Я также добавил это как статический метод в класс AmountPerMonth, который принимает AmountPerPeriod и возвращает List<AmountPerMonth>.Кроме того, я переопределил метод ToString, чтобы вывести строку, аналогичную приведенной в вашем вопросе, поэтому вывод выглядит так же:

public class AmountPerMonth
{
    public int Id { get; set; }
    public int Year { get; set; }
    public int Month { get; set; }
    public decimal Amount { get; set; }

    public static List<AmountPerMonth> FromPeriod(AmountPerPeriod period)
    {
        if (period == null) return null;
        var amtPerDay = period.Amount / ((period.EndDate - period.StartDate).Days + 1);
        var result = new List<AmountPerMonth>();

        for (var date = period.StartDate; date <= period.EndDate; 
             date = date.AddMonths(1).ToFirstDateOfMonth())
        {
            var lastDayOfMonth = date.ToLastDateOfMonth();
            var lastDay = period.EndDate < lastDayOfMonth
                ? period.EndDate
                : lastDayOfMonth;
            var amount = ((lastDay - date).Days + 1) * amtPerDay;

            result.Add(new AmountPerMonth
            {
                Id = period.Id,
                Year = date.Year,
                Month = date.Month,
                Amount = amount
            });
        }

        return result;
    }

    public override string ToString()
    {
        return $"{Id,-3} |{Month,-6}| {Year,-6}| {Amount:0.00}";
    }
}

Мы можем использовать этот метод в качестве аргумента для SelectMany изВаши образцы данных для создания нашего списка и вывода результатов:

static void Main(string[] args)
{
    var lstAmmountPerPeriod = new List<AmountPerPeriod>()
    {
        new AmountPerPeriod
        {
            Id = 1,
            StartDate = new DateTime(2019, 03, 21),
            EndDate = new DateTime(2019, 05, 09),
            Amount = 10000
        },
        new AmountPerPeriod
        {
            Id = 2,
            StartDate = new DateTime(2019, 04, 02),
            EndDate = new DateTime(2019, 04, 10),
            Amount = 30000
        },
        new AmountPerPeriod
        {
            Id = 3,
            StartDate = new DateTime(2018, 11, 01),
            EndDate = new DateTime(2019, 01, 08),
            Amount = 20000
        }
    };

    var amountsPerMonth = lstAmmountPerPeriod.SelectMany(AmountPerMonth.FromPeriod);

    Console.WriteLine("ID |month |year   |amount");
    Console.WriteLine("---|------|-------|--------");
    Console.WriteLine(string.Join(Environment.NewLine, amountsPerMonth));

    GetKeyFromUser("\n\nDone! Press any key to exit...");
}

Вывод

enter image description here

Примечание: Эти методы расширения были использованы в коде выше:

public static class Extensions
{
    public static DateTime ToFirstDateOfMonth(this DateTime input)
    {
        return new DateTime(input.Year, input.Month, 1, input.Hour, 
            input.Minute, input.Second, input.Millisecond, input.Kind);
    }

    public static DateTime ToLastDateOfMonth(this DateTime input)
    {
        return new DateTime(input.Year, input.Month, 
            DateTime.DaysInMonth(input.Year, input.Month), input.Hour,
            input.Minute, input.Second, input.Millisecond, input.Kind);
    }
}
...