пятница, 1 июля 2011 г.

FastReport.Net: число прописью на русском

Иногда в разрабатываемых приложениях стоит задача представить данные на печатной форме или отчете. Особенно часто эта задача возникает в приложениях бухгалтерской направленности, где существуют формы строгой отчетности. Для решения таких задач используют генераторы отчетов: FastReport.Net, Crystal Reports и другие.

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

В компании, где я работаю, используется FastReport.Net, поэтому именно на его примере посмотрим, как можно добиться нужного результата.


Как обычно, задачу можно решить несколькими способами.

Способ 1: Пишем свою функцию

Как ни странно, именно такой вариант решения я видел в приложениях, которые мне приходится поддерживать. Замечу, что этот способ подойдет тем, кто любит изобретать велосипеды :). Пишем на C# примерно следующее (нарыл в интернете):

/// 
/// Класс отображения суммы прописью.
/// 3 варианта - рубли, доллары и просто для использования
/// других любых единиц (вагоны, мешки и т.п.)
/// --------------------------------------
/// Автор - Глеб Уфимцев (dnkvpb@nm.ru)
/// 
public class NumByWords
{
    public static string RurPhrase(decimal money)
    {
        return CurPhrase(money, "рубль", "рубля", "рублей", "копейка", "копейки", "копеек");
    }

    public static string UsdPhrase(decimal money)
    {
        return CurPhrase(money, "доллар США", "доллара США", "долларов США", "цент", "цента", "центов");
    }

    public static string NumPhrase(ulong Value, bool IsMale)
    {
        if (Value == 0UL) return "Ноль";
        
        string[] Dek1 = { "", " од", " дв", " три", " четыре", " пять", " шесть", " семь", " восемь", " девять", " десять", " одиннадцать", " двенадцать", " тринадцать", " четырнадцать", " пятнадцать", " шестнадцать", " семнадцать", " восемнадцать", " девятнадцать" };
        string[] Dek2 = { "", "", " двадцать", " тридцать", " сорок", " пятьдесят", " шестьдесят", " семьдесят", " восемьдесят", " девяносто" };
        string[] Dek3 = { "", " сто", " двести", " триста", " четыреста", " пятьсот", " шестьсот", " семьсот", " восемьсот", " девятьсот" };
        string[] Th = { "", "", " тысяч", " миллион", " миллиард", " триллион", " квадрилион", " квинтилион" };
        string str = "";

        for (byte th = 1; Value > 0; th++)
        {
            ushort gr = (ushort)(Value % 1000);
            Value = (Value - gr) / 1000;
            if (gr > 0)
            {
                byte d3 = (byte)((gr - gr % 100) / 100);
                byte d1 = (byte)(gr % 10);
                byte d2 = (byte)((gr - d3 * 100 - d1) / 10);
                if (d2 == 1) d1 += (byte)10;
                bool ismale = (th > 2) || ((th == 1) && IsMale);
                str = Dek3[d3] + Dek2[d2] + Dek1[d1] + EndDek1(d1, ismale) + Th[th] + EndTh(th, d1) + str;
            }
        }
        str = str.Substring(1, 1).ToUpper() + str.Substring(2);
        return str;
    }
    
    #region Private members
    private static string CurPhrase(decimal money, string word1, string word234, string wordmore, string sword1, string sword234, string swordmore)
    {
        money = decimal.Round(money, 2);
        decimal decintpart = decimal.Truncate(money);
        ulong intpart = decimal.ToUInt64(decintpart);
        string str = NumPhrase(intpart, true) + " ";
        byte endpart = (byte)(intpart % 100UL);
        if (endpart > 19) endpart = (byte)(endpart % 10);
        switch (endpart)
        {
            case 1: str += word1; break;
            case 2:
            case 3:
            case 4: str += word234; break;
            default: str += wordmore; break;
        }
        byte fracpart = decimal.ToByte((money - decintpart) * 100M);
        str += " " + ((fracpart < 10) ? "0" : "") + fracpart.ToString() + " ";
        if (fracpart > 19) fracpart = (byte)(fracpart % 10);
        switch (fracpart)
        {
            case 1: str += sword1; break;
            case 2:
            case 3:
            case 4: str += sword234; break;
            default: str += swordmore; break;
        };
        return str;
    }
    private static string EndTh(byte ThNum, byte Dek)
    {
        bool In234 = ((Dek >= 2) && (Dek <= 4));
        bool More4 = ((Dek > 4) || (Dek == 0));
        if (((ThNum > 2) && In234) || ((ThNum == 2) && (Dek == 1))) return "а";
        else if ((ThNum > 2) && More4) return "ов";
        else if ((ThNum == 2) && In234) return "и";
        else return "";
    }
    private static string EndDek1(byte Dek, bool IsMale)
    {
        if ((Dek > 2) || (Dek == 0)) return "";
        else if (Dek == 1)
        {
            if (IsMale) return "ин";
            else return "на";
        }
        else
        {
            if (IsMale) return "а";
            else return "е";
        }
    }
    #endregion
}

Затем регистрируем этот класс в отчете:

Type ftype = typeof(NumByWords);
MethodInfo mi = ftype.GetMethod("NumPhrase");
RegisteredObjects.AddFunction(mi, "NumPhrase");

Чтобы код выше заработал, нужно подключить:

using FastReport.Utils;

А теперь используем там, где нам это нужно:

NumPhrase(1987, true) = "Одна тысяча девятьсот восемьдесят семь рублей"

Способ 2: Используем встроенную в FastReport функцию

Да-да. Если заглянуть в документацию, среди функций конвертирования можно обнаружить функцию ToWordsRu. Это функция очень интересная и гибкая. Она имеет три перегрузки:

string ToWordsRu(object value) - конвертирует числовое значение value в сумму прописью на русском

ToWordsRu(1987.25) = "Одна тысяча девятьсот восемьдесят семь рублей 25 копеек"

string ToWordsRu(object value, string currencyName) - конвертирует числовое значение value в сумму прописью на русском. Используется валюта, заданная в параметре currencyName. Возможные значения этого параметра: "RUR", "UAH", "USD", "EUR".

ToWordsRu(1987.25, "EUR") = "Одна тысяча девятьсот восемьдесят евро 25 евроцентов"

string ToWordsRu(object value, bool male, string one, string two, string many) - конвертирует целочисленное значение value в число прописью на русском. В параметре male надо указать true, если существительное - мужского рода. В параметрах one, two и many задаются словоформы для чисел 1, 2 и 5.

// слово "страница" женского рода - параметр male = false
ToWordsRu(124, false, "страница", "страницы", "страниц") = "Сто двадцать четыре страницы"
 
// слово "лист" мужского рода - параметр male = true
ToWordsRu(124, true, "лист", "листа", "листов") = "Сто двадцать четыре листа"

Если нужно просто вывести число прописью без каких-либо исчисляемых объектов, то можно сделать так:

ToWordsRu(124, true, "", "", "") = "Сто двадцать четыре"

Именно так стоит поступать при использовании, например, фиксированных бухгалтерских бланков, в которых нужно вписать только сумму прописью, а единицы уже предусмотрены самой формой ("итого:________________руб. ___ коп."). Кстати, здесь главное не забыть подставить целую часть числа, например, с помощью функции Truncate(decimal value):

ToWordsRu(Truncate(124.55), true, "", "", "") = "Сто двадцать четыре"

Как видно, во втором способе используются только встроенные возможности FastReport.Net. Тем не менее, если в разрабатываемом приложении необходимо выводить числа прописью не только в генерируемых отчетах, но и где-нибудь на форме, то придется воспользоваться кодом из первого способа.

Другие варианты решения задачи предлагайте в комментариях.

2 комментария:

  1. Здравствуйте, в программировании не силен, есть программа в ней отчеты формируются в Fastreport версия 4.15, в списке стандартных функций нет такой функции ToWordsRu

    Как можно ее туда добавить?

    ОтветитьУдалить
  2. "Затем регистрируем этот класс в отчете:" - можно вот здесь поподробней?
    Можно ли этот класс в ReportViewer подключить?


    ОтветитьУдалить