Процесс написания своего алгоритма расчета на макроязыке мало чем отличается от процесса написания алгоритма расчета на языке С, как это происходило раньше. Проще всего показать порядок написания алгоритма на конкретном примере, но для начала – несколько общих замечаний.
Свои собственные алгоритмы начислений удержаний необходимо помещать в
модуль 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. Описание часто используемых полей этой структуры
Параметр r
представляет собой массив для промежуточного
хранения информации по рассчитываемой строке Н-У данного лицевого счета за
рассчитываемый месяц: double r[4]
.
r[0]
– месяц начисления суммы (в абсолютном
представлении);
[1]
– сумма;
r[2]
– рабочее время;
r[3]
– номер строки лицевого счета в матрице
с1[][]
.
Следует обратить внимание на возвращаемые алгоритмом значения, а
именно значения, возвращаемого функцией UserAlgorithmCalc
.
Допустимы только следующие коды возврата:
Таблица 2.
Код | Предопределенная константа | Описание |
---|---|---|
0 | Нет такого case | |
1 | code_Break | Есть такой case |
2 | code_MDefault | Есть такой case и надо, чтобы в конце была сделана остановка на корректировке РВ. |
3 | Есть такой case и надо, чтобы системный алгоритм с таким же кодом тоже отработал. | |
48 | code_ReturnCode | Есть такой case и надо, чтобы было возвращено
значение, которое вернула функция, вызванная в алгоритме. При этом
возвращенное значение должно быть помещено в структуру
info в поле c . Например: if ((
info.c=korrv(r,t,0,info.h,flagrow,jf)) != 1)return
code_ReturnCode; . |
Вставить свой алгоритм расчета можно двумя способами:
Написать отдельную функцию
MyFunction(&info,&r)
, которая содержит собственно
сам алгоритм, где MyFunction
– любое имя, удовлетворяющее
синтаксису макроязыка и непересекающееся с уже существующими именами.
А в функции UserAlgorithmCalc
вставить вызов написанной
функции:
case номер_алг: return MyFunction(info,r);
Рекомендуется для имен переменных и функций использовать
«говорящие» имена, то есть такие имена, по которым было бы понятно,
что выполняет функция или для чего предназначена переменная.
Например, для функций, содержащих алгоритмы расчета достаточно
говорящим было бы название |
В файле usalg.s
есть пример именно такого
алгоритма. Функция с алгоритмом MyFunction
не обязательно
должна иметь два параметра info
и r
, она
может вообще не иметь параметров. Но следует учитывать, что через эти
параметры в алгоритм передается важная информация (см. описание
параметров info
и r
выше) и без них она в
алгоритме будет недоступна.
Написать алгоритм прямо в функции
UserAlgorithmCalc
:
case номер_алг: сам алгоритм
Отдать какое-либо предпочтение одному из этих способов сложно. Все зависит от конкретной ситуации и привычек того, кто пишет алгоритм. У написания алгоритма через функцию есть дополнительное преимущество: этот алгоритм можно использовать в разных местах без повтора кода. Второй способ вставки алгоритма будет более привычен разработчикам старой закалки, так как раньше именно так вставлялись новые алгоритмы.
Еще одним отличием между двумя способами написания новых алгоритмов
является доступ параметрам, к передаваемым в функцию
UserAlgorithmCalc
. При написании алгоритма расчета в отдельной
функции необходимые параметры получаются так: info.параметр
.
При написании алгоритма непосредственно в функции доступ к параметрам идет
через локальные переменные (и только через них). Например, если необходимо
узнать базовое значение рассчитываемой строки, при создании алгоритма первым
способом надо будет написать info.bz
, а по второму способу –
просто bz
.
Ну и, наконец, последнее отличие между двумя способами написания своих
алгоритмов – завершение работы алгоритма. При выделении алгоритма в
отдельную функцию все просто и понятно – необходимо написать return
код_возврата
. Описание всех кодов возврата смотри выше в табличке.
При втором способе создания алгоритма ситуация сложнее, если необходимо
вернуть код отличный от code_Break
. В этом случае необходимо
строго соблюдать следующий порядок действий:
Присвойте переменной RetFlag
необходимый код
возврата.
Вызватьвите оператора break
.
Например:
RetFlag=code_MDefault; break;
Или, если мы досрочно заканчиваем алгоритм по требованию пользователя:
if ((c=korrv(r,t,0,info.h,flagrow,jf)) != 1) { RetFlag=code_ReturnCode; break; }
Если выполняется стандартный выход из алгоритма, с кодом
code_Break
, то первый шаг можно опустить, написав сразу
оператор break
.
Если создается новый алгоритм «с нуля», то лучше идти по первому
пути (через отдельную функцию). Если же переносится уже созданный алгоритм
из старого модуля |
Cоздадим собственный алгоритм расчета. В качестве примера, напишем алгоритм для расчета премии, у которой процент может меняться в течение расчетного месяца. Поэтому процент будет храниться не в базовом значении, как обычно, а в поле кадровой части «procPREM», изменяемом по времени. Это позволит нам избежать размножения строчек для расчета премии в лицевом счете (при обычном подходе, при каждой смене процента пришлось бы заводить новую строку премии в лицевой счет).
Т.к. в поставочном каталоге нет поля кадровой части «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 }
Как видно отличий от подобного алгоритма, написанного на языке С, очень мало:
Функции не описываются типы переменных и тип возвращаемого
значения. Это связано с тем, что для передачи параметров и возврата
значения из функции используется один и тот же тип переменных
var
, который в зависимости от действий с данной
переменной автоматически преобразуется к нужному типу (строка, целое
число и т.п.), если, такое преобразование возможно. Дополнительно для
параметров функции можно указать, как передаются параметры. Передача
параметров возможно двумя способами: как «параметр-значение» и как
«параметр-переменная» (по ссылке). Отличия этих двух способов
заключается в том, что если внутри тела функции производить какие-либо
действия над параметрами, переданными в функцию (изменять их), то при
передаче параметра как «параметра-значения» эти изменения не будут
доступны после выхода из тела функции. Если же параметр передан как
«параметр-переменная», то все изменения, выполненные с данным
параметром, сохранятся и после выхода из тела функции. При передаче
параметра как «параметра-переменной» перед именем переменной в
заголовке функции добавляется знак & (амперсант). При вызове же
функций передача параметра как «параметра-переменной» ничем не
отличается от передачи параметра как «параметра-значения». Есть одно
отличие: в качестве «параметра-переменной» нельзя передавать
константы, арифметические выражения, значения функций, одним словом,
этот параметр должен быть lvalue
(см. Приложение
3 2.3.6)
Возможность использования зарегистрированных в программе
классов. Например, в строке 64 происходит создание переменной,
связанной с классом KDate
, а в строке 65 – инициализация
созданной переменной значениями countday
(количество дней
в расчетном месяце) и mrasch
(абсолютное значение
расчетного месяца).
Других отличий нет. То есть если бы данный алгоритм был написан на языке С, то он выглядел бы точно так же, за исключением отличий описанных выше.
Текст описанного здесь алгоритма можно найти в поставочном файле
|
Обратите внимание на одну интересную возможность, которой не стоит пренебрегать при написании своих собственных алгоритмов. Благодаря строчкам 48-55, 93-101 и 125-129 пользователь при расчете строки по приведенному выше алгоритму может увидеть, из чего складывается конечный результат. Для этого, находясь в режиме корректировки суммы, при расчете строки ему, необходимо нажать клавишу F5/ После этого откроется таблица с расшифровкой итоговой суммы (см.рис. 4).