Создание нового алгоритма расчета

Процесс написания своего алгоритма расчета на макроязыке мало чем отличается от процесса написания алгоритма расчета на языке С, как это происходило раньше. Проще всего показать порядок написания алгоритма на конкретном примере, но для начала – несколько общих замечаний.

Свои собственные алгоритмы начислений удержаний необходимо помещать в модуль usalg.s, который находится в каталоге SCRIPT/USЕR. В этом модуле есть функция UserAlgorithmCalc(&info,&r), которая отвечает за расчет алгоритмов, написанных пользователем. В эту функцию передаются два параметра: info и r. Через эти параметры передается важная информация, которую можно и нужно использовать при написании своих алгоритмов.

Параметр info представляет собой следующую структуру

{
int crow,   b3,  ikod, kod_alg, pos, mkat, krabot;
int tip_s9600, flag_propusk0, z1, z2, c;
double kod,n1,d1,d2,h[4],bz;
} CalcStr;
    

Таблица 1. Описание часто используемых полей этой структуры

ПолеОписание
crow Номер, рассчитываемой строки в матрице с1[][].
b3 Задает некоторые особенности в работе:
  • 0 - Расчет налогов за прошл.месяц

  • 2 - Расчет в «Отпуске»;

  • 3 - Ррасчет нормативного зар-ка в «Б/листе»;

  • 4 -Расчет в «Увольнении» по месяц увольнения;

  • 5 - Расчет тек.месяца для подведения окончат.баланса;

  • 6 - Расчет "за первую половину"

  • 25- Расчет одной строки в режиме «Просмотр-корр-ка ЛС».

  • 27- Расчет нормативного заработка (в расчете отпуска)

  • 1 - Остальные расчеты;

ikod Целая часть внутреннего кода рассчитываемого вида Н-У.
kod Полный внутренний код рассчитываемого вида Н-У (вместе с расширением).
kod_alg Код алгоритма рассчитываемого вида.
pos Позиция кода рассчитываемой строки в таблице входимости.
mkat Код категории для строки или для лицевого счета.
krabot Код работы (из кадровой части): основной, совместитель и т.п.
z1 Первый день действия рассчитываемой строки в рассчитываемом месяце.
z2 Последний день действия рассчитываемой строки в рассчитываемом месяце.
d1 Дата начала действия рассчитываемой строки Н-У.
d2 Дата конца действия рассчитываемой строки Н-У.
bz Базовое значение рассчитываемой строки. Если в базовом значении стоит некоторый код, значение которого необходимо найти в сетках окладов и т.п., то в bz будет занесено нужное значение из сетки.
h Массив необходимый в случае пересчетов.
n1 Результат расчета строки. Каждый алгоритм, который вычисляет какую-либо сумму для занесения в лицевой счет, должен иметь в своем теле строчку: n1=результат.

Параметр r представляет собой массив для промежуточного хранения информации по рассчитываемой строке Н-У данного лицевого счета за рассчитываемый месяц: double r[4].

r[0] – месяц начисления суммы (в абсолютном представлении);

[1] – сумма;

r[2] – рабочее время;

r[3] – номер строки лицевого счета в матрице с1[][].

Следует обратить внимание на возвращаемые алгоритмом значения, а именно значения, возвращаемого функцией UserAlgorithmCalc. Допустимы только следующие коды возврата:

Таблица 2.

КодПредопределенная константаОписание
0 Нет такого case
1code_BreakЕсть такой case
2code_MDefaultЕсть такой case и надо, чтобы в конце была сделана остановка на корректировке РВ.
3 Есть такой case и надо, чтобы системный алгоритм с таким же кодом тоже отработал.
48code_ReturnCodeЕсть такой case и надо, чтобы было возвращено значение, которое вернула функция, вызванная в алгоритме. При этом возвращенное значение должно быть помещено в структуру info в поле c. Например: if (( info.c=korrv(r,t,0,info.h,flagrow,jf)) != 1)return code_ReturnCode;.

Вставить свой алгоритм расчета можно двумя способами:

  1. Написать отдельную функцию MyFunction(&info,&r), которая содержит собственно сам алгоритм, где MyFunction – любое имя, удовлетворяющее синтаксису макроязыка и непересекающееся с уже существующими именами. А в функции UserAlgorithmCalc вставить вызов написанной функции:

    case номер_алг:
      return MyFunction(info,r);
              
    [Примечание]

    Рекомендуется для имен переменных и функций использовать «говорящие» имена, то есть такие имена, по которым было бы понятно, что выполняет функция или для чего предназначена переменная. Например, для функций, содержащих алгоритмы расчета достаточно говорящим было бы название Alg_???, где ??? – номер алгоритма реализованного в этой функции.

    В файле usalg.s есть пример именно такого алгоритма. Функция с алгоритмом MyFunction не обязательно должна иметь два параметра info и r, она может вообще не иметь параметров. Но следует учитывать, что через эти параметры в алгоритм передается важная информация (см. описание параметров info и r выше) и без них она в алгоритме будет недоступна.

  2. Написать алгоритм прямо в функции UserAlgorithmCalc:

    case номер_алг:
      сам алгоритм
              

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

Еще одним отличием между двумя способами написания новых алгоритмов является доступ параметрам, к передаваемым в функцию UserAlgorithmCalc. При написании алгоритма расчета в отдельной функции необходимые параметры получаются так: info.параметр. При написании алгоритма непосредственно в функции доступ к параметрам идет через локальные переменные (и только через них). Например, если необходимо узнать базовое значение рассчитываемой строки, при создании алгоритма первым способом надо будет написать info.bz, а по второму способу – просто bz.

Ну и, наконец, последнее отличие между двумя способами написания своих алгоритмов – завершение работы алгоритма. При выделении алгоритма в отдельную функцию все просто и понятно – необходимо написать return код_возврата. Описание всех кодов возврата смотри выше в табличке. При втором способе создания алгоритма ситуация сложнее, если необходимо вернуть код отличный от code_Break. В этом случае необходимо строго соблюдать следующий порядок действий:

  1. Присвойте переменной RetFlag необходимый код возврата.

  2. Вызватьвите оператора break.

Например:

RetFlag=code_MDefault;
break;
    

Или, если мы досрочно заканчиваем алгоритм по требованию пользователя:

if ((c=korrv(r,t,0,info.h,flagrow,jf)) != 1)
{
RetFlag=code_ReturnCode;
break;
}
    

Если выполняется стандартный выход из алгоритма, с кодом code_Break, то первый шаг можно опустить, написав сразу оператор break.

[Примечание]

Если создается новый алгоритм «с нуля», то лучше идти по первому пути (через отдельную функцию). Если же переносится уже созданный алгоритм из старого модуля r0alg.cpp, то проще (с меньшим количеством исправлений) будет идти по второму пути.

Cоздадим собственный алгоритм расчета. В качестве примера, напишем алгоритм для расчета премии, у которой процент может меняться в течение расчетного месяца. Поэтому процент будет храниться не в базовом значении, как обычно, а в поле кадровой части «procPREM», изменяемом по времени. Это позволит нам избежать размножения строчек для расчета премии в лицевом счете (при обычном подходе, при каждой смене процента пришлось бы заводить новую строку премии в лицевой счет).

[Примечание]

Т.к. в поставочном каталоге нет поля кадровой части «procPREM», то чтобы проверить работу написанного нами алгоритма необходимо завести в Настройке изменяемой кадровой части новое поле:

Таблица 3.

Название поляТипДлинаПолн. ф.Кр. ф.Псевдоним
ПроцентТ3++ProcPREM


При создании алгоритма используем первый способ, то есть оформляем алгоритм расчета (номер алгоритма – 712) в виде отдельной функции, вызов которой вставим в UserAlgorithmCalc:

case 712:
  return UserAlg712(info,r);
    

Ниже приведен текст алгоритма и пояснения к нему. Номера строк приведены для удобства дальнейших пояснений к алгоритму, поэтому, при вводе алгоритма, номера строк вводить не надо.

1 UserAlg712(&info,&r)
2 {
3   struct Struct_Alg Work;
4   var mes1=0;
5   var mes2=0;
6   var stolbec=ST_MPREM+1;
7 
8   // символы для выборки
9   // по умолчанию - по всем ненулевым
10  char StrSimv[51];
11  sprintf(StrSimv,"*");
12  Get_AlgWithParam(Work,info.ikod);   // 31.01.1995
13  // Work.Count_Par - числопа pаметpовдлякода алгоpитма
14  //-- параметр 1 - столбец таблицы входимости
15  if(Work.Count_Par > 0)
16  {
17    var i=atoi(reinterpret_cast_to_string(Work.List_Par[0]));
18    if( i > 0 && i <= 99 ) stolbec=i;
19  }
21  //-- параметр 2 - месяц начала выборки (относительно расчетного)
22  //   (по умолчанию берется за один текущий месяц)
23  if(Work.Count_Par > 1)
24  {
25    var i=atoi(reinterpret_cast_to_string(Work.List_Par[1]));
26    if( i >= (- PMES) && i <= BMES )  mes1=mes2=i;
27  }
28  
29  //-- параметр 3 - месяц конца выборки (относительно расчетного)
30  //   (по умолчанию берется за один месяц, указанный параметром 2)
31  if(Work.Count_Par > 2)
32  {
33    var i=atoi(reinterpret_cast_to_string(Work.List_Par[2]));
34    if( i >= mes1 && i <= BMES ) mes2=i;
35  }
36  //-- параметр 4 - символы выборки
37  if(Work.Count_Par >4)
38    sprintf(StrSimv,"%.50s",reinterpret_cast_to_string(Work.List_Par[3]));
39  var   n5=0.;
40  for(var m=mes1; m<=mes2; m++)
41  {
42    double r9;
43    s1001simv(StrSimv,"*",info.d1,info.d2,m,stolbec-1,r9,4,0);
44    n5+=r9;
45  }
46
47  // Заполним таблицу уже найденными значениями
48  if(FL_V_ALG && uprc != NO_SCREEN)
49  {
50    infolist.add_record("Месяцначала выборки",mes1);
51    infolist.add_record("Месяцокончаниявыборки",mes2);
52    infolist.add_record("Столбецвыборки",stolbec);
53    infolist.add_record("Символывыборки",StrSimv);
54    infolist.add_record("Сумма выборки",n5);
55  }
56  
57  // А теперь пройдемся по полю кадровой части "procPREM"
58  // И возьмем оттуда все значения процентов, действовавшие
59  // в рассчитываемом месяце
60  info.n1=0.;  // обнулили итоговую сумму
61  
62  // Дата конца действия очередного значения процента
63  // В самом начале она равна последнему дню месяца.
64  var DateEnd = CreateObject("KDate");
65  DateEnd.SetDateII(countday,mrasch);
66  
67  // Найдем дату начала действия очередного значения процента
68  char StrD[21];
69  GetKchDate("procPREM",StrD,10,DateEnd);
70  var Date=CreateObject("KDate");
71  Date.SetDate(to_string(StrD));
72  // Если вдруг дата начала пустая или выходит за рамки расчетного
73  // месяца, то установим ее равной первому числу расчетного месяца
74  if ( Date.IsEqI(INVALID_DATE_VALUE) || Date.GetAbs()<mrasch)
75  {
76    Date.SetDateII(1,mrasch)
77    sprintf(StrD,"%02d.%02d.%4d",Date.GetDay(),Date.GetMonth(),Date.GetYear());
78  }
79
80  // Ну и просматриваем все значения процентов, действовавшие в
81  // рассматриваемом месяце
82  char StrProc[21];
83  while ( 1 )
84  {
85     // находим очередное значение процента
86     double Proc=atof(GetKchValue("procPREM",StrProc,20,DateEnd));
87
88      // определяем коэффициент неполноты для очередного значения процента
89      // (неполнота по нормативным дням)
90      double KoefNepoln = 1.;
91      nepoln(Date.GetDouble(),DateEnd.GetDouble(),info.d1,info.d2,KoefNepoln,2);
92      // записываем, если надо, в таблицу найденные значения
93      if(FL_V_ALG && uprc != NO_SCREEN)
94      {
95        infolist.add_record("------------------------","---------------------");
96        infolist.add_record("Начало действия процента",StrD);
97      sprintf(StrD,"%02d.%02d.%4d",DateEnd.GetDay(),DateEnd.GetMonth(),DateEnd.GetYear());
98        infolist.add_record("Конец  действия процента",StrD);
99        infolist.add_record("Коэффициент",KoefNepoln);
100       infolist.add_record("Процент",Proc);
101     }
102     // Увеличиваем итоговую сумму на очередную порцию премии
103     info.n1 += n5*Proc*KoefNepoln/100.;
104     // Устанавливаем дату конца очередного значения процента
105     // равной дате начала уже рассмотренного значения минус один день.
106     DateEnd.SetDateD(Date.GetDouble());
107     DateEnd.Dec();
108
109     // Если дата конца вышла за пределы расчетного месяца - выходим
110     if ( DateEnd.IsEqI(INVALID_DATE_VALUE) || DateEnd.GetAbs()<mrasch )
111     break;
112
113     // Находим дату начала для очередного значения премии
114     GetKchDateP("procPREM",StrD,10,Date);
115     Date.SetDate(to_string(StrD));
116     // Если вдруг дата начала пустая или выходит за рамки расчетного
117     // месяца, то установим ее равной первому числу расчетного месяца
118     if ( Date.IsEqI(INVALID_DATE_VALUE) || Date.GetAbs()<mrasch )
119     {
120       Date.SetDateII(1,mrasch);
121       sprintf(StrD,"%02d.%02d.%4d",Date.GetDay(),Date.GetMonth(),Date.GetYear());
122     }
123   }
124   // выводим в таблицу окончаительный результат
125   if(FL_V_ALG && uprc != NO_SCREEN)
126   {
127     infolist.add_record("------------------------","---------------------");
128     infolist.add_record("Результат",info.n1);
129   }
130   return code_MDefault;
131 }
  

Как видно отличий от подобного алгоритма, написанного на языке С, очень мало:

  1. Функции не описываются типы переменных и тип возвращаемого значения. Это связано с тем, что для передачи параметров и возврата значения из функции используется один и тот же тип переменных var, который в зависимости от действий с данной переменной автоматически преобразуется к нужному типу (строка, целое число и т.п.), если, такое преобразование возможно. Дополнительно для параметров функции можно указать, как передаются параметры. Передача параметров возможно двумя способами: как «параметр-значение» и как «параметр-переменная» (по ссылке). Отличия этих двух способов заключается в том, что если внутри тела функции производить какие-либо действия над параметрами, переданными в функцию (изменять их), то при передаче параметра как «параметра-значения» эти изменения не будут доступны после выхода из тела функции. Если же параметр передан как «параметр-переменная», то все изменения, выполненные с данным параметром, сохранятся и после выхода из тела функции. При передаче параметра как «параметра-переменной» перед именем переменной в заголовке функции добавляется знак & (амперсант). При вызове же функций передача параметра как «параметра-переменной» ничем не отличается от передачи параметра как «параметра-значения». Есть одно отличие: в качестве «параметра-переменной» нельзя передавать константы, арифметические выражения, значения функций, одним словом, этот параметр должен быть lvalue (см. Приложение 3 2.3.6)

  2. Возможность использования зарегистрированных в программе классов. Например, в строке 64 происходит создание переменной, связанной с классом KDate, а в строке 65 – инициализация созданной переменной значениями countday (количество дней в расчетном месяце) и mrasch (абсолютное значение расчетного месяца).

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

[Примечание]

Текст описанного здесь алгоритма можно найти в поставочном файле usalg.s (см. описание файлов, написанных на макроязыке в Приложении 2).

Обратите внимание на одну интересную возможность, которой не стоит пренебрегать при написании своих собственных алгоритмов. Благодаря строчкам 48-55, 93-101 и 125-129 пользователь при расчете строки по приведенному выше алгоритму может увидеть, из чего складывается конечный результат. Для этого, находясь в режиме корректировки суммы, при расчете строки ему, необходимо нажать клавишу F5/ После этого откроется таблица с расшифровкой итоговой суммы (см.рис. 4).