Кредитный калькулятор на C# - Часть 2 (Расчеты и сохранение расчетов)

В открытых интернет-источниках можно без проблем найти формулы для самостоятельного расчета кредита с аннуитетным и дифференцированным платежами. Ими мы и воспользуемся в нашей программе. Стоит напомнить что, хоть это и правильные формулы, расчеты по ним получатся все равно приблизительными и отличающимися от тех, которые предоставляют банки. Все от того, что банки включают в эти формулы свои дополнительные значения, например стоимость страховки.

Проведение расчетов (по стоимости покупке и сумме кредита) имеет одинаковую структуру, различается только тем, что в первом мы сперва должны посчитать сумму кредита. Тем не менее одинаковый код будет применяться по нескольку раз. Поэтому имеет смысл создание методов для этих операций.

Сперва посчитаем сумму кредита исходя из стоимости покупки и первоначального взноса. Создадим в коде формы метод CreditSumFunc() для вычисления суммы кредита:

private void CreditSumFunc() // Метод подсчета суммы кредита
{
    double ValueOfPurchase = Convert.ToDouble(pricePrice.Value);
    double InitialPayment = Convert.ToDouble(priceInitial.Value);
    if (priceInitialType.SelectedIndex == 0)
    {
        priceCreditSum.Text = (ValueOfPurchase - InitialPayment).ToString("N2");
    }
    else
    {
        priceCreditSum.Text = (ValueOfPurchase - ((ValueOfPurchase * InitialPayment) / 100)).ToString("N2");
    }
}

Код собирает значения стоимости покупки и первоначального взноса. Если первоначальный взнос указан в рублях, то сумма кредита получается из разности стоимости покупки и взноса. Если первоначальный взнос указан в процентах, то сперва взнос пересчитывается из процентов в рубли, а уже потом программа вычитает его из стоимости покупки.

Для событий при вводе суммы покупки (pricePrice_ValueChanged), изменении первоначального взноса (priceInitial_ValueChanged), изменении типа первоначального взноса добавим код:

CreditSumFunc();

Расчеты платежа и графика платежей

Аннуитетный и диффиренцированный платежи также выносим в отдельные методы:

private void PaymentScheduleAnnuitet(double SumCredit, double InterestRateYear, double InterestRateMonth, int CreditPeriod) // Метод расчета Аннуитетного платежа
{
    double Payment = SumCredit * (InterestRateMonth / (1 - Math.Pow(1 + InterestRateMonth, -CreditPeriod))); // Ежемесячный платеж
    double ItogCreditSum = Payment * CreditPeriod; // Итоговая сумма кредита

    itogPayment.Text = Payment.ToString("N2"); // Выводим в результаты ежемесячный платёж
    itogSum.Text = (ItogCreditSum).ToString("N2"); // Выводим в результаты итоговую сумму кредита

    // Заполняем график платежей
    double SumCreditOperation = SumCredit;
    double ItogCreditSumOperation = ItogCreditSum;
    double ItogPlus = 0;
    for (int i = 0; i < CreditPeriod; ++i)
    {
        double procent = SumCreditOperation * (InterestRateYear / 100) / 12;
        SumCreditOperation -= Payment - procent;
        dgvGrafik.Rows.Add();
        dgvGrafik[0, i].Value = i + 1; //номер месяца
        dgvGrafik[1, i].Value = Payment.ToString("N2"); //Ежемесячный платеж
        dgvGrafik[2, i].Value = (Payment - procent).ToString("N2"); //Платеж за основной долг
        dgvGrafik[3, i].Value = procent.ToString("N2"); //Платеж процента
        dgvGrafik[4, i].Value = SumCreditOperation.ToString("N2"); //Основной остаток
        ItogCreditSumOperation -= Payment;
        ItogPlus = Convert.ToDouble(dgvGrafik[4, i].Value);
    }
    itogOverpayment.Text = (ItogCreditSum - SumCredit + ItogPlus).ToString("N2");
}

private void PaymentScheduleDiffer(double SumCredit, double InterestRateMonth, int CreditPeriod) // Метод расчета Дифференцированного платежа
{
    double MainPayment = SumCredit / CreditPeriod; // платеж по основному долгу
    // Заполняем график платежей
    double ItogCreditSum = 0;
    double OverPaymentSum = 0;
    for (int i = 0; i < CreditPeriod; ++i)
    {
        double procentPart = SumCredit * InterestRateMonth; //подсчет процентной части ежемесячного платежа
        SumCredit -= MainPayment; //подсчет остатка основного долга (с каждым месяцем уменьшается)
        dgvGrafik.Rows.Add(); //добавляем строку в таблицу
        dgvGrafik[0, i].Value = i + 1; //номер месяца
        dgvGrafik[1, i].Value = (MainPayment + procentPart).ToString("N2"); //полный ежемесячный платеж
        dgvGrafik[2, i].Value = MainPayment.ToString("N2"); //платеж по основному долгу
        dgvGrafik[3, i].Value = procentPart.ToString("N2"); //процентная часть ежемесячного платежа
        dgvGrafik[4, i].Value = SumCredit.ToString("N2"); //Остаток по основному долгу
    }
    for (int i = 0; i < CreditPeriod; ++i) //Подсчет итоговой стоимости и переплаты по кредиту
    {
        ItogCreditSum += Convert.ToDouble(dgvGrafik[1, i].Value);
        OverPaymentSum += Convert.ToDouble(dgvGrafik[3, i].Value);
    }
    double ItogPlus = Convert.ToDouble(dgvGrafik[4, dgvGrafik.RowCount - 1].Value);
    itogSum.Text = ItogCreditSum.ToString("N2");
    itogOverpayment.Text = (OverPaymentSum + ItogPlus).ToString("N2");
    itogPayment.Text = Convert.ToString(dgvGrafik[1, 0].Value) + "..." + Convert.ToString(dgvGrafik[1, dgvGrafik.RowCount - 1].Value);
}

Для кнопки "Рассчитать стоимость" по стоимости покупки (butPriceGo) пишем следующий код:

if ((Convert.ToInt32(priceInitial.Value) > Convert.ToInt32(pricePrice.Value)) || (priceInitialType.SelectedIndex == 1 && Convert.ToInt32(priceInitial.Value) > 99))
{
    MessageBox.Show("Сумма кредита не может быть отрицательной или равной нулю.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
    return;
}
else
{
    dgvGrafik.Rows.Clear(); // Очищаем таблицу
    double SumCredit = Convert.ToDouble(priceCreditSum.Text); // Сумма кредита
    double InterestRateYear = Convert.ToDouble(priceProcent.Value); // Процентная ставка, ГОДОВАЯ
    double InterestRateMonth = InterestRateYear / 100 / 12; // Процентная ставка, МЕСЯЧНАЯ
    int CreditPeriod = Convert.ToInt32(pricePeriod.Value); // Срок кредита, переводим в месяцы, если указан в годах
    if (pricePeriodCombo.SelectedIndex == 0)
        CreditPeriod *= 12;

    if (priceAnnuitet.Checked == true) // Аннуитетный платеж
    {
        PaymentScheduleAnnuitet(SumCredit, InterestRateYear, InterestRateMonth, CreditPeriod);
    }
    else if (priceDiffer.Checked == true) // Дифференцированный платеж
    {
        PaymentScheduleDiffer(SumCredit, InterestRateMonth, CreditPeriod);
    }
    butSaveAsCSV.Enabled = true;
}

Для кнопки расчета стоимости по сумме кредита (butSumGo) пишем аналогичный код, за исключением измененных имен элементов с исходными данными, поэтому здесь я его приводить не буду.

Кнопки "Очистить расчеты" также имеют один и тот же код. Создадим общий для них метод:

private void ClearFunc() // Метод очистки расчетов
{
    pricePrice.Value = pricePrice.Minimum;
    priceInitial.Value = priceInitial.Minimum;
    priceInitialType.SelectedIndex = 0;
    priceProcent.Value = priceProcent.Minimum;
    pricePeriod.Value = pricePeriod.Minimum;
    pricePeriodCombo.SelectedIndex = 0;
    sumCreditSum.Value = sumCreditSum.Minimum;
    sumProcent.Value = sumProcent.Minimum;
    sumPeriod.Value = sumPeriod.Minimum;
    sumPeriodCombo.SelectedIndex = 0;
    itogSum.Clear();
    itogOverpayment.Clear();
    itogPayment.Clear();
    dgvGrafik.Rows.Clear();
    butSaveAsCSV.Enabled = false;
}

В коде кнопок напишем:

ClearFunc();

Переходим к завершающему этапу - напишем код для сохранения полученных расчетов в *.csv-файл для того, чтобы пользователь мог в дальнейшем работать с готовым графиком платежей. Для этого воспользуемся следующим кодом:

SaveFileDialog saveTableAsCSV = new SaveFileDialog();
saveTableAsCSV.Filter = "Документ CSV (*.csv) |*.csv";
saveTableAsCSV.Title = "Сохранить результат расчетов";
if (saveTableAsCSV.ShowDialog() == DialogResult.OK)
{
    try
    {
        FileStream file = new FileStream(saveTableAsCSV.FileName, FileMode.Create);
        StreamWriter sw = new StreamWriter(file, Encoding.Default);
        sw.Write("Итоговая стоимость кредита:" + ";" + itogSum.Text);
        sw.WriteLine();
        sw.Write("Сумма переплаты:" + ";" + itogOverpayment.Text);
        sw.WriteLine();
        sw.Write("Ежемесячный платеж:" + ";" + itogPayment.Text);
        sw.WriteLine();
        sw.Write("Месяц:" + ";" + "Сумма платежа" + ";" + "Платеж по основному долгу" + ";" + "Платеж по процентам" + ";" + "Остаток основного долга" + ";");
        sw.WriteLine();
        for (int i = 0; i < dgvGrafik.RowCount; i++)
        {
            for (int j = 0; j < dgvGrafik.ColumnCount; j++)
            {
                sw.Write(Convert.ToDouble(dgvGrafik.Rows[i].Cells[j].Value));
                if (j < dgvGrafik.ColumnCount - 1)
                    sw.Write(";");
            }
            sw.WriteLine();
        }
        sw.Close();
    }
    catch
    {
        MessageBox.Show("Перезаписываемый файл уже используется другой программой.\nЗакройте его и повторите попытку, либо сохраните как новый файл.", "Ошибка сохранения", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

При выполнении данного кода открывается saveFileDialog для выбора места сохранения файла. Затем, если место и имя файла выбраны успешно, открывается файловый поток для создания/перезаписи и заполнения файла данными. Разделение на столбцы происходит при помощи разделителя ";", а переход на новую строку с помощью "sw.WriteLine();". Если происходит ошибка перезаписи файла (она обычно возникает, когда файл уже открыт в сторонней программе), выводится соответствующее сообщение об ошибке и выполнение кода прекращается.

На этом создание кредитного калькулятора завершено. Остается добавить (по желанию) иконку к программе и изображение для кнопок, чтобы как-то разрядить атмосферу сухих таблиц и цифр в программе. Спасибо за внимание!