// Скрипт автоматизации работой с фискальными регистраторами Вики Принт (драйвер OPOS) 
// Copyright (C)

// Печать чека. Если программа не находит данной процедуры, то выполняет просто скрипт. Оставлено для совместимости со старыми скриптами
// При успешном завершении переменная Result должна вернуть 0. Иначе - код ошибки ККМ и ResultDescription - текст ошибки
// procedure Cash_Execute(NeedConnect :boolean = true) :boolean;  

// процедура вернет серийный номер кассы
// procedure Cash_GetNumbers(NeedConnect :boolean = true);

// процедура вернет номер сессии
// procedure Cash_GetSessionNumber(NeedConnect :boolean = true) :string;

// процедура проверяет и, если необходимо - инициализирует драйвер устройства (OLЕ-объект). В случае ошибки вызовет exception 
// procedure Cash_CheckDevice;

// процедура устанавливает связь с сервером. В случае ошибки вернет false, а также установит ResultCode и Result_Description 
// procedure Cash_Connect :boolean;

// процедура разрывает связь с сервером 
// procedure Cash_Disconnect;

// процедура отрезки чека
// procedure Cash_Cut(NeedConnect :boolean = true);

// процедура отмены зависшего чека
// procedure Cash_CancelCheck(NeedConnect :boolean = true);

// процедура "продолжить печать"
// procedure Cash_RepeatCheck(NeedConnect :boolean = true);

// процедура показа параметров устройства
// procedure Cash_ShowProperties;

// процедура вносить сумму в кассу
// procedure Cash_DoSummaryIn(NeedConnect :boolean = true);
// процедура изымает суммы из кассы
// procedure Cash_DoSummaryOut(NeedConnect :boolean = true);


// процедура открывает ящик
//procedure Cash_OpenDrawer(NeedConnect :boolean = true);

// Отчет с гашением
// procedure Cash_ZReport(NeedConnect :boolean = true);
// Отчет с без гашения
// procedure Cash_XReport(NeedConnect :boolean = true);
// Отчет по секциям
// procedure Cash_SectionReport(NeedConnect :boolean = true);
// Отчет по кассирам
// procedure Cash_UsersReport(NeedConnect :boolean = true);

// Отчет по регистрам (общий отчет)
// procedure Cash_RegistersReport(NeedConnect :boolean = true);

// пароль пользователя один на всех указывается в переменной CASH_STDUserPassword, локальной или глобальной.
// Если пуст, то используется UserPassword, передаваемый программой

// все нижеперечисленные переменные могут быть определены в параметрах работы программы и проверяются перед выполнением на их наличие там.
// если в параметрах не найдено, то используются настройки по-умолчанию 

var 
      CASH_DeviceName :string =  'OPOS.FiscalPrinter.1.12';
      CASH_DeviceNameLocal :string =  'Pirit'; 
      CASH_KKMSYSPASSWORD :string = ''; 
      CASH_STDUSERPASSWORD  :string = ''; // пароль кассира. Если не указан, то берется пароль пользователя программы. ДОлжен состоять только из цифр. Системный же пароль вбивается в настройках программы на закладке розницы.
      CASH_USERNAME_MANUAL :boolean =  false; // имя кассира, указывать ли вручную, взяв его из имени пользователя. Не зависит от пароля кассира и выбора кассира в аппарате. Пока используется только в чеках.
      CASH_DiscountType :integer = 0; // 0 - скидка не отображается, 1 - детальная скидка, 2  - скидка на весь чек
      CASH_PrintStringDiscountSummary :boolean = true; // печатать строку с размером скидки  
      CASH_PrintStringPriceOut :boolean = true; // печатать строку со строкой без скидки
      CASH_PrintStringDiscountOnCheck :boolean = true; // печатать строку со скидкой на чек  

      CASH_NameBadCharList :string = '';  // символы в наименовании на замену
      CASH_NameReplaceCharList :string = ''; // символы в наименовании, которыми надо заменить плохие.   

      CASH_TypeCloseNal :integer = 0;
      CASH_TypeCloseBN :integer = 1; // подмена стандартных типов оплаты данными
      CASH_TypeCloseKredit :integer = 1;
      CASH_TypeCloseSert :integer = 3;
     
      CASH_CutType :integer = 0;  //0- не отрезать (не контролировать); 1-полная отрезка; 2-неполная отрезка
      
      CASH_OPENDRAWER_NAL :boolean = false; // открывать ли ящик в нале

      CASH_TrimNameGoods :boolean = false; // Обрезать ли наименование товара по длине строки
      
      // Длина строки. 0 - без обрезания и учета длины 
      // <0 - взять автоматически средствами драйвера      
      CASH_StringLength :integer = -1;
      
      Cash_Protocol3 :boolean = true;
      
      CASH_SHOWNDS :boolean = true; // отображать ли НДС
      CASH_SHOWNDSSUMMARY :boolean = false;//посылать сумму НДС на аппарат (поддерживается не всеми аппаратами, теми, что не поддерживают, игнорируется, похоже)
      CASH_NDS0TYPE :integer = 1; // тип НДС0
      CASH_NDS10TYPE :integer = 2;
      CASH_NDS18TYPE :integer = 3;
      CASH_NDS20TYPE :integer = 4;
      CASH_CANDIVIDEDIVNUM :boolean = true; // можно ли делить количество и цену для фасованных товаров
      
      //CASH_SESSIONREGISTER :integer = 53; // 21 или 27, или 28, или 53 // раньше использовал 21, но потом стал давать 0

      CASH_SHOWADVANCEDERRORTEXT :boolean = true; // выводить ли в cash_checkerror дополнительный хинт с ошибкой
      CASH_PRINTCLIENT :boolean = false; // выводить ли имя и инн покупателя (атрибуты 1227 и 1228)
      
      CASH_SHOWPROPERTIES_COMMANDLINE :string = ''; // запускать командную строку в процедуре вывода свойств.
      
      CASH_STR_TO_OEM :boolean = false; // конвертировать ли строки в OEM и обратно?  
     
      CASH_SAVEMARKS :integer = 1;  // сохранять ли марку в кассе
      
      //CASH_216_IN_ROW :boolean = false; // false = функцию 216  (тэг 1084=1085+1086) задавать перед циклом ввода товаров, один раз, как в АТОЛ, 
                               // true - построчно, как в примере (см. в конце скрипта вместе со ссылкой на пример) 
      
      Cash_Logging :boolean = true; // Сохранять лог операции своими средствами (для тестирования)
      
      CASH_1008_STRING :string = '';
      
      VarReceptSeria: string = '';
      VarReceptNumber: string = '';
      VarReceptDate: TDateTime = 0;
      
      //GISMT_REQID: string;
      //GISMT_REQTIMESTAMP: string;
      

const       
      FNN = 'NN';       
      FName = 'name';
      FMARKS_BARCODES = 'MARKS_BARCODES';
      FQuantity = 'kol';
      FPrice = 'cena';
      FPriceOut = 'prodcena';
      FPriceOut1 = 'rs_cena';
      FSection = 'section';
      FNDS = 'NDS';
      FSUMMARY = 'SUMMA';
      FFloatBarcode = 'FloatBarcode';
      FDivNum = 'DivNum';
      
const 
    CashLogging = true; // Сохранять лог операции (для тестирования
    
    WaitTimeout = 0; // время ожидания проверки марки (сек)
    CASH_CHECKMARK_SLEEPS = 2000; // интервал ожидания в мс между запросами ожидания ответа от кассы при проверке марки

{

var
    CardsPrintCutLineLength :integer; // сколько надо пропустить, чтобы линия отреза была там, где надо
    CardsPrintCutLine :boolean; // Надо ли резать (некоторые аппараты не поддерживают. по-умолчанию - true
    CardsPrintCutLineSleep :integer; // на месте отреза сделать слип, в секундах
}    


var
  IsInvoice :boolean = false; // true - идет печать из накладной
  DATASET :TDataset = nil; // датасет с товарами. Набор полей смотрите в чеке розницы
  DatasetMarks :tDataset = nil;
  Summary :Currency = 0; // сумма по чеку
  PaySummary :Currency = 0; // Внесено денег
  DiscountSummary :currency = 0; // сумма надбавки/скидки
  Discount   :double = 0; // процент надбавки/скидки
  DiscountIsLiveneed   :double = 0; // процент надбавки/скидки для ЖНВЛС
  DiscountMG   :double = 0; // процент надбавки/скидки от наценки
  DiscountIsLiveneedMG   :double = 0; // процент надбавки/скидки от наценки для ЖНВЛС
  DiscountSummary :double = 0; // сумма надбавки/скидки
  ClientName :string = ''; // имя контрагента
  ClientID :integer = 0; // код контрагента
  AddInfo :string = ''; // доп. инфо к контрагенту
  Doc_Reason :string = ''; // код основания в чеке
  Doc_Reason_Name :string = ''; // наименование основания в чеке
  Doc_Reason_FILTERED :string = ''; // код основания, по которому отфильтрован чек
  Doc_Reason_FILTERED_Name :string = ''; // наименование отфильтрованного основания в чеке
  DOC_TYPE_CLOSE :integer = 0; //
  SummaNotNullField :double = 0; // Сумма строк с ненулевым полем, установленным согласно настройкам
  EMAIL :string = ''; // телефон или емейл покупателя, для онлайн-касс
  user_name :string = ''; // имя кассира
  user_id :integer = 0; // ID кассира  
  IsReturn :boolean = false; // Это чек возврата, используется при пробитии чека
  Number_DOC :integer = 0; // Номер документа

  Storage_ID :integer = 0; // Номер склада

  VarActionNal :boolean = false;
  VarActionBN :boolean = false;
  VarActionKredit :boolean = false;
  VarActionSert :boolean = false;

  VarActionDiskont :boolean = false;
  VarActionDiskont1 :boolean = false;
  VarActionPension :boolean = false;
  VarActionVIP :boolean  = false;
  VarActionInsurance :boolean  = false;

  CurrentSection :integer =0; // секция для всего товара
  
  VarDevice :Variant = null; // устройство печати. Если не указано, то определять самостоятельно. Может быть использовано приложением для получения от этого скрипта и передачи другим скриптам

  ResultSerialNumber :string = '';   // серийный номер, возвращаемый процедурой Cash_GetSerialNumber и Cash_execute (если этого требуют настройки программы)  
  ResultSessionNumber :string = '';  // номер сессии, возвращаемый процедурой Cash_GetSessionNumber и Cash_execute (если этого требуют настройки программы)
  ResultDocumentNumber :integer = 0; // номер следующего документа  

  ResultRegisterListNames :TStrings = nil; 
  ResultRegisterListValues :TSTrings = nil;  

  TaxType :integer = 0;
  
  strval,strval1 :string;
  
var
  // Возвращаемые параметры, могут быть использованы вызывающим скриптом или программой  
  ResultCode : integer =0; // результат, код ошибки, если не 0
  // Переменная ResultDescription возвращает текст кода Result
  ResultDescription :string = '';
  ResultCodeExtended :integer = 0; // расширенная ошибка согласно стандарту OPOS

// сконвертирует строку, если требуется конвертация строк, 
function ConvertStr1(Value :string) :string;
begin
  if CASH_STR_TO_OEM then result := ConversionString(Value,ctAnsi2OEM) 
  else result := Value
end;
// обратная конвертация
function ConvertStr2(Value :string) :string;
begin
  if CASH_STR_TO_OEM then result := ConversionString(Value,ctOem2ANSI) 
  else result := Value
end;

function StrToHex1(Value: string): string;
var i: Integer;
begin
  Result := Value;
  for i := 1 to length(Result) div 2-1 do 
    Result := copy(Result,1,i*2+i-1) + '$'+ copy(Result,i*2+i,length(Result)); // пробелы между знаками поставим
  result := '$'+Result; // первый '$'
end;
  
procedure Cash_InitVars;
begin
   CASH_DeviceName :=  appinifile.readstring('advanced','CASH_DEVICENAME',dbinifile.readstring('advanced','CASH_DEVICENAME',CASH_DEVICENAME));
   CASH_DeviceNameLocal :=  appinifile.readstring('advanced','CASH_DEVICENAMELOCAL',dbinifile.readstring('advanced','CASH_DEVICENAMELOCAL',CASH_DEVICENAMELOCAL));
   CASH_KKMSYSPASSWORD := Appinifile.readstring('advanced','CASH_KKMSYSPASSWORD',DBINIFILE.readstring('advanced','CASH_KKMSYSPASSWORD',appinifile.readstring('common','KKMSYSPASSWORD',CASH_KKMSYSPASSWORD)));
   CASH_STDUSERPASSWORD := Appinifile.readstring('advanced','CASH_STDUSERPASSWORD',DBINIFILE.readstring('advanced','CASH_STDUSERPASSWORD',Appinifile.readstring('common','KKMUSERPASSWORD',CASH_STDUSERPASSWORD)));
   CASH_DiscountType  := appinifile.readinteger('advanced','CASH_DISCOUNTTYPE',dbinifile.readinteger('advanced','CASH_DISCOUNTTYPE',CASH_DiscountType));
   CASH_PrintStringDiscountSummary := appinifile.readbool('advanced','CASH_PrintStringDiscountSummary',dbinifile.readbool('advanced','CASH_PrintStringDiscountSummary',CASH_PrintStringDiscountSummary));   
   CASH_PrintStringPriceOut := appinifile.readbool('advanced','CASH_PrintStringPriceOut',dbinifile.readbool('advanced','CASH_PrintStringPriceOut',CASH_PrintStringPriceOut));
   CASH_PrintStringDiscountOnCheck := appinifile.readbool('advanced','CASH_PrintStringDiscountOnCheck',dbinifile.readbool('advanced','CASH_PrintStringDiscountOnCheck',CASH_PrintStringDiscountOnCheck));  
   CASH_NameBadCharList := appinifile.readstring('advanced','CASH_NameBadCharList',dbinifile.readstring('advanced','CASH_NameBadCharList',CASH_NameBadCharList));
   CASH_NameReplaceCharList  := appinifile.readstring('advanced','CASH_NameReplaceCharList',dbinifile.readstring('advanced','CASH_NameReplaceCharList',CASH_NameReplaceCharList));   
   CASH_TypeCloseNal := appinifile.readinteger('advanced','CASH_TypeCloseNal',dbinifile.readinteger('advanced','CASH_TypeCloseNal',CASH_TypeCloseNal));
   CASH_TypeCloseBN := appinifile.readinteger('advanced','CASH_TypeCloseBN',dbinifile.readinteger('advanced','CASH_TypeCloseBN',CASH_TypeCloseBN));
   CASH_TypeCloseKredit := appinifile.readinteger('advanced','CASH_TypeCloseKredit',dbinifile.readinteger('advanced','CASH_TypeCloseKredit',CASH_TypeCloseKredit));
   CASH_TypeCloseSert := appinifile.readinteger('advanced','CASH_TypeCloseSert',dbinifile.readinteger('advanced','CASH_TypeCloseSert',CASH_TypeCloseSert));
   CASH_CutType := appinifile.readinteger('advanced','CASH_CutType',dbinifile.readinteger('advanced','CASH_CutType',CASH_CutType));
   CASH_TrimNameGoods := appinifile.readbool('advanced','CASH_TrimNameGoods',dbinifile.readbool('advanced','CASH_TrimNameGoods',CASH_TrimNameGoods));
   CASH_StringLength := appinifile.readinteger('advanced','CASH_StringLength',dbinifile.readinteger('advanced','CASH_StringLength',CASH_StringLength));
   CASH_OPENDRAWER_NAL := appinifile.readbool('advanced','CASH_OPENDRAWER_NAL',dbinifile.readbool('advanced','CASH_OPENDRAWER_NAL',CASH_OPENDRAWER_NAL));
   CASH_Protocol3 := appinifile.readbool('advanced','CASH_Protocol2',dbinifile.readbool('advanced','CASH_Protocol3',CASH_Protocol3));
   CASH_SHOWNDS := appinifile.readbool('advanced','CASH_SHOWNDS',dbinifile.readbool('advanced','CASH_SHOWNDS',CASH_SHOWNDS));
   CASH_SHOWNDSSUMMARY := appinifile.readbool('advanced','CASH_SHOWNDSSUMMARY',dbinifile.readbool('advanced','CASH_SHOWNDSSUMMARY',CASH_SHOWNDSSUMMARY));
   CASH_NDS0TYPE := appinifile.readinteger('advanced','CASH_NDS0TYPE',dbinifile.readinteger('advanced','CASH_NDS0TYPE',CASH_NDS0TYPE));
   CASH_NDS10TYPE := appinifile.readinteger('advanced','CASH_NDS10TYPE',dbinifile.readinteger('advanced','CASH_NDS10TYPE',CASH_NDS10TYPE));
   CASH_NDS18TYPE := appinifile.readinteger('advanced','CASH_NDS18TYPE',dbinifile.readinteger('advanced','CASH_NDS18TYPE',CASH_NDS18TYPE));
   CASH_NDS20TYPE := appinifile.readinteger('advanced','CASH_NDS20TYPE',dbinifile.readinteger('advanced','CASH_NDS20TYPE',CASH_NDS20TYPE));
   CASH_CANDIVIDEDIVNUM := appinifile.readbool('advanced','CASH_CANDIVIDEDIVNUM',dbinifile.readbool('advanced','CASH_CANDIVIDEDIVNUM',CASH_CANDIVIDEDIVNUM));
   CASH_USERNAME_MANUAL := appinifile.readbool('advanced','CASH_USERNAME_MANUAL',dbinifile.readbool('advanced','CASH_USERNAME_MANUAL',CASH_USERNAME_MANUAL));
   //CASH_SESSIONREGISTER  := appinifile.readinteger('advanced','CASH_SESSIONREGISTER',dbinifile.readinteger('advanced','CASH_SESSIONREGISTER',CASH_SESSIONREGISTER));
   CASH_SHOWADVANCEDERRORTEXT := appinifile.readbool('advanced','CASH_SHOWADVANCEDERRORTEXT',dbinifile.readbool('advanced','CASH_SHOWADVANCEDERRORTEXT',CASH_SHOWADVANCEDERRORTEXT));
   CASH_PRINTCLIENT := appinifile.readbool('advanced','CASH_PRINTCLIENT',dbinifile.readbool('advanced','CASH_PRINTCLIENT',CASH_PRINTCLIENT));
   CASH_SHOWPROPERTIES_COMMANDLINE := Appinifile.readstring('advanced','CASH_SHOWPROPERTIES_COMMANDLINE',DBINIFILE.readstring('advanced','CASH_SHOWPROPERTIES_COMMANDLINE',CASH_SHOWPROPERTIES_COMMANDLINE));
   CASH_STR_TO_OEM := appinifile.readbool('advanced','CASH_STR_TO_OEM',dbinifile.readbool('advanced','CASH_STR_TO_OEM',CASH_STR_TO_OEM));
   CASH_SAVEMARKS := appinifile.readinteger('advanced','CASH_SAVEMARKS',dbinifile.readinteger('advanced','CASH_SAVEMARKS',CASH_SAVEMARKS));
   CASH_Logging := appinifile.readbool('advanced','CASH_LOGGING',dbinifile.readbool('advanced','CASH_LOGGING',CASH_LOGGING));
   CASH_1008_string := Appinifile.readstring('advanced','CASH_1008_STRING',DBINIFILE.readstring('advanced','CASH_1008_STRING',CASH_1008_STRING));
end;


procedure Cash_SaveLog(StrValue :string; LogAlways :boolean = false);
begin
  if CASH_Logging or LogAlways then 
  with TLogFile.create('cashdrvvikiprintopos.log') do
  try
    AddStr(StrValue);
  finally
    Free;
  end;
end;


function Cash_CheckError(FuncName :string; NeedExcept :boolean = true) :boolean; // Проверка ошибки драйвера. Вылетаем на exception, если была ошибка
var StrVal :string;
begin
  ResultCode := VarDevice.ResultCode;
  ResultCodeExtended := VarDevice.ResultCodeExtended;
  ResultDescription :=  VarDevice.ErrorString;
  result := resultcode=0;
  if CASH_SHOWADVANCEDERRORTEXT and (ResultCode <>0) and  (ResultCode <>114) then  CreateHintI('Проверьте, включена ли касса','Рекомендации по ошибке ('+inttostr(ResultCode)+')');
  if (ResultCode<>0) then
  begin
    StrVAl := FuncName+': ошибка: ' + inttostr(resultcode) + ':' + inttostr(resultcodeExtended) + ': ' + resultDescription;
    Cash_SaveLog(StrVal,True);
    if needexcept then raiseException(StrVal);
  end;
end;

procedure Cash_CheckDevice;
begin
  if varIsNull(VarDevice) then 
  begin
    VarDevice := CreateOleObject(CASH_DeviceName);
  end;
end;


procedure Cash_Connect(NeedSysPassword :boolean = false);
var strvalue :string;
    boolval :boolean;
    CashDefaultDevice :integer;  
begin
  Cash_CheckDevice;

  VarDevice.Open(CASH_DeviceNameLocal); CASH_CheckError('cash_connect:open');     //ra
  VarDevice.ClaimDevice(3000); Cash_CheckError('cash_connect:claimdevice'); // 3 секунды таймаут

  {как это сделать в OPOS? 
  if Needsyspassword 
  then begin
    strvalue := CASH_KKMSYSPASSWORD;
  end
  else begin
    StrValue := CASH_STDUSERPASSWORD;
    if strvalue = '' then StrValue := USERPROGRAMPASS; 
  end;
  if strvalue = '' then StrVAlue := GetPasswordDialog;
  if strvalue ='' then raiseException('Ошибка: не указан пароль');

  if strvalue<>inttostr(strtointprotected(strvalue)) then raiseException('Ошибка: Должен быть числовой пароль');
  
  VarDevice.Password := StrValue; Cash_CheckError;
  }


  { пока закомменчено. Если будет работать, то можно отключить этот код в cash_execute, это он сработает здесь уже
  if Cash_UserName_Manual and (UserProgram>'')
  then begin
     VarDevice.AttrNumber := 1021; Cash_CheckError;
     VarDevice.AttrValue := UserProgram; Cash_CheckError;
     VarDevice.WriteAttribute; Cash_CheckError;
  end;
  
  В OPOS это выглядит так, вроде ( не уверен насчет '0'):  VarDevice.SetPOSID('1',StrValue); Cash_CheckError;
  
  }
  
  { в OPOS пока непонятно
  CashDefaultDevice := AppIniFile.readinteger('common','CashDefaultDevice',0); // задается в программе
  if CashDefaultDevice > 0  then VarDevice.CurrentDeviceNUmber := CashDefaultDevice; 
  Cash_CheckError;
  }
  
  VarDevice.DeviceEnabled := true; Cash_CheckError('cash_connect:deviceenabled');
end;

procedure Cash_disconnect;
begin
  VarDevice.DeviceEnabled := false;

  { надо ли это или не обязательно  в OPOS - тестить
  Printer.ReleaseDevice;
  Printer.Close;
	Printer.Free;}
end;

procedure Cash_GetNumbers(NeedConnect :boolean = true);
var  VarArr :Array of Variant;
begin
    if needConnect then Cash_Connect;
    
    try
      VarDevice.ResetPrinter; Cash_CheckError('cash_getnumber:resetprinter'); // перевод статуса ФР в "Monitor". Открытые документы будут аннулированы или закрыты.

      SetLength(VarArr,3);
      VarArr[0] := 9;
      VarArr[1] := 0;
      VarArr[2] := '';  
      InvokeOLEMethod(VarDevice,'GetData',VarArr,[true,true,true]); Cash_checkError('cash_getnumber:serialnumber');
      //VarDevice.GetData(9,IntVal,StrVal); Cash_CheckError; // или DirectIO(194,,) ?
      resultSerialNumber := asstring(VarArr[2]);
      
      VarArr[0] := 7;
      VarArr[1] := 0;
      VarArr[2] := '';  
      InvokeOLEMethod(VarDevice,'GetData',VarArr,[true,true,true]); Cash_checkError('cash_getnumber:sessionnumber');
      //VarDevice.GetData(7,IntVal,StrVal); Cash_CheckError; // FPTR_GD_Z_REPORT  = $00000007 : // Вернуть номер последней закрытой смены
      // Нам надо номер текущей сессии. Инкрементируем?
      ResultSessionNumber := inttostr(strtointprotected(asstring(VarArr[2]))+1);  //или DirectIO(176,6,) (тогда отключить инкремент)?

      VarArr[0] := 3;
      VarArr[1] := 0;
      VarArr[2] := '';  
      InvokeOLEMethod(VarDevice,'GetData',VarArr,[true,true,true]); Cash_checkError('cash_getnumber:documentnumber');
      //VarDevice.GetData(3,IntVal,StrVal); Cash_CheckError; // или 12? FPTR_GD_RECEIPT_NUMBER  = $00000003 : // Номер чека  FPTR_GD_FISCAL_DOC  = $00000012 : // Вернуть номер следующего документа
      resultDocumentNumber := strtointprotected(asstring(VarArr[2]));
      setlength(VarArr,0);
    finally  
      if NeedConnect then Cash_disconnect;
    end;
end;

procedure Cash_Cut(NeedConnect :boolean = true);
begin
    if CASH_CutType=0 then exit; // нет отрезки
    
    if NeedConnect then Cash_Connect;
    try
      raiseException('Ручная отрезка пока не реализована');
      { как в OPOS это сделать?
      if CASH_CutType=1
      then VarDevice.Fullcut //0- не отрезать (не контролировать); 1-полная отрезка; 2-неполная отрезка
      else VarDevice.PartialCut;
      }
      
      Cash_CheckError('cash_cut');
    finally
      if NeedConnect then Cash_disconnect;
    end;
end;

procedure Cash_OpenDrawer(NeedConnect :boolean = true);
var Drawer :Variant;
    ResCode,ResCodeExtended :integer;
    ResString :string;
begin
  if NeedConnect then Cash_Connect(false);
  try
      Drawer := createoleobject('OPOS.CashDrawer');
      //надо или не надо? Drawer.DeviceEnabled := true 
      //надо или не надо? Drawer.Open('CashDrawer')
      Drawer.OpenDrawer;

      ResCode := Drawer.ResultCode;
      ResCodeExtended := Drawer.ResultCodeExtended;
      try
        ResString :=  Drawer.ErrorString; // кажется, это не работает в этом объекте, проверить
      except
        ResString := '?';
      end;
      if (ResCode<>0) or (ResCodeExtended<>0) then raiseException('Ошибка при работе с ящиком: ' + inttostr(rescode) + ':' + inttostr(rescodeExtended) + ': ' + ResString);
  finally
    if NeedConnect then Cash_disconnect;
  end;
  
end;

procedure Cash_Execute(NeedConnect :boolean = true);
var i :integer;
    pSummaryCash,pSummaryDiscount :currency;
    pSummary,pSummaryOut :currency;
    pDiscount :currency;
    pPrice,pPriceOut :currency;
    pPriceOutDivNum :currency;
    pPriceDivNum :currency;
    pQuantity :Double;
    pQNTFrac :double;
    pQuantityDivNum :integer;
    pDivNum :integer;
    IsFloatBarcode :boolean;

    pName :string;
    PNN : string;
    DetailCash :integer;
    Index :integer;
    Summ2 :currency;
    StrValue,StrVAlue1,StrValue2: string;
    EmailSeller :string;
    stringlength :integer;
    NDS :integer;
    thField :TField;
    IntValue :integer;
    INN :string;
    VarArr :array of variant;
    CashierName :string;
    
    
    MDLP_STORAGE_ID :string;
    
    NEED_CHECK_KKM_COUNT: Integer; // количество марок, которые надо проверять в ККМ. Пока тут у нас все марки, которые не ЕГАИС
    MARKING_COUNT: Integer; // всего товаров с марками
    MDLP_COUNT :integer; // количество товаров с марками для МДЛП (лекарства)
    TOBACCO_COUNT :integer; // табак
    MILK_COUNT: Integer;  //молоко
    WATER_COUNT: Integer;  // вода
    ASEPT_COUNT: Integer; // антисептики
    MIZD_COUNT: Integer; // медизделия
    BAD_COUNT: Integer;  // бады
    
    VarValue :variant;
    strQNT :string;
    pmarks_barcodes :string;
    isFrac :boolean;
    
    CDS :TClientDataset;
    
    MarkValidationResult :integer;


    WFCM :TMyWait = nil;
    
begin
   Cash_SaveLog('cash_execute');
   DetailCash := Appinifile.readinteger('common','DetailCash',dbinifile.readinteger('common','DetailCash',0));  //это стандартная настройка на закладке "розница"
   setlength(VarArr,3); 
   
   CDS := TClientDataset.Create(SelfScript);
   
   dataset.FieldDefs.update;
   for i := 0 to dataset.fieldcount-1 do
     cds.fielddefs.add(dataset.fields[i].fieldname,
                       dataset.fields[i].DataType,
                       dataset.fields[i].Size,
                       false);

   if dataset.findfield(fmarks_barcodes)=nil then
     cds.fielddefs.add(fmarks_barcodes,ftstring,1000,false);

   cds.FieldDefs.Add('tmp_ismarking',ftsmallint,0,False); // все марки не ЕГАИС
   cds.fielddefs.add('tmp_need_check_kkm',ftsmallint,0,false); // все проверяемые в ККМ
   cds.fielddefs.add('tmp_ismdlp',ftsmallint,0,false); // медикаменты
   cds.fielddefs.add('tmp_istobacco',ftsmallint,0,false);
   cds.fielddefs.add('tmp_ismilk',ftsmallint,0,false);
   cds.fielddefs.add('tmp_iswater',ftsmallint,0,false);
   cds.fielddefs.add('tmp_ismizd',ftsmallint,0,false);
   cds.fielddefs.add('tmp_isasept',ftsmallint,0,false);
   cds.fielddefs.add('tmp_isbad',ftsmallint,0,false);
   cds.fielddefs.add('tmp_quantity_divnum',ftinteger,0,false);    
   cds.fielddefs.add('tmp_check_result',ftstring,1000,false);
   cds.createdataset;
   cds.LogChanges := false; 
   

   MARKING_COUNT := 0;
   NEED_CHECK_KKM_Count := 0;
   MDLP_COUNT := 0;
   TOBACCO_COUNT := 0;
   MILK_Count := 0;
   WATER_COUNT := 0;
   MIZD_COUNT := 0;
   ASEPT_COUNT := 0;
   BAD_COUNT := 0;

   dataset.disablecontrols;
   try
     dataset.first;
     while not dataset.eof do
     begin
       cds.append;
       for i := 0 to dataset.fieldcount-1 do
         cds.fields[i].asvariant := dataset.fields[i].asvariant;

       cds.fieldbyname(fdivnum).asinteger := coalesce_integer(cds.fieldbyname(FDivNum).asinteger,1);    
       cds.fieldbyname('tmp_quantity_divnum').asinteger := round(cds.fieldbyname(fquantity).asfloat * cds.fieldbyname(fdivnum).asinteger);

       if (dataset.findfield(fmarks_barcodes)=nil) 
          and (DataSetMarks<>nil) 
          and  datasetmarks.locate('idprimary',dataset.fieldbyname('idprimary').asstring,0) 
       then 
         cds.fieldbyname(fmarks_barcodes).asstring := datasetmarks.fieldbyname('barcode').asstring;

       if (CASH_SAVEMARKS<>0) then
       begin
         if not NNInFilterEGAIS(dataset.fieldbyname(FNN).asstring) // егаисовские товары нас не интересуют 
            and not NNInFilterEGAISBEER(dataset.fieldbyname(FNN).asstring)
            and (cds.fieldbyname(fmarks_barcodes).asstring>'') then 
         begin
           inc(MARKING_COUNT);
           cds.fieldByName('tmp_ismarking').asInteger := 1;

           inc(NEED_CHECK_KKM_COUNT);
           cds.fieldbyname('tmp_need_check_kkm').asInteger := 1;
           
           if NNInFilterTobacco(dataset.fieldbyname(fnn).asstring) then
           begin 
             inc(TOBACCO_COUNT);
             cds.fieldbyname('tmp_istobacco').asinteger := 1;
           end
           else if NNInFilterMilk(dataset.fieldbyname(fnn).asstring) then
           begin 
             inc(MILK_COUNT);
             cds.fieldbyname('tmp_ismilk').asinteger := 1;
           end
           else if NNInFilterWater(dataset.fieldbyname(fnn).asstring) then
           begin 
             inc(WATER_COUNT);
             cds.fieldbyname('tmp_iswater').asinteger := 1;
           end
           else if NNInFilterMIZD(dataset.fieldbyname(fnn).asstring) then
           begin 
             inc(MIZD_COUNT);
             cds.fieldbyname('tmp_ismizd').asinteger := 1;
           end
           else if NNInFilterASEPT(dataset.fieldbyname(fnn).asstring) then
           begin 
             inc(ASEPT_COUNT);
             cds.fieldbyname('tmp_isasept').asinteger := 1;
           end
           else if NNInFilterBAD(dataset.fieldbyname(fnn).asstring) then 
           begin
             inc(BAD_COUNT);
             cds.fieldbyname('tmp_isbad').asinteger := 1;
           end
           else if NNIsNotOtherMarks(Dataset.FieldByName(fnn).asString) then
           begin
             inc(MDLP_COUNT);
             cds.fieldbyname('tmp_ismdlp').asinteger := 1;
           end;
         
           if cds.fieldbyname('tmp_quantity_divnum').asinteger>cds.fieldbyname(FDIVNUM).asinteger then 
           begin
             Cash_SaveLog('Отмена чека: имеются товары для МДЛП с количеством >1',true);
             raiseexception('Имеются товары для МДЛП с количеством >1');
           end;
         end;
       end;

       cds.post;
       dataset.next;
     end;      
     
     dataset.first;
   finally
     dataset.enablecontrols;
   end;                       

   MDLP_STORAGE_ID := asstring(BDEQueryValue('select mdlp_storage_id from agents where id = :id',[STORAGE_ID]));
   
   if (CASH_SAVEMARKS<>0) 
      and (mdlp_storage_id='') 
      and (MDLP_COUNT>0) then 
   begin  
     Cash_SaveLog('Отмена чека: имеются товары для МДЛП, но у склада не указан его код в МДЛП');
     raiseexception('Имеются товары для МДЛП, но у склада не указан его код в МДЛП');
   end;

   
   if needconnect then 
     Cash_Connect;
   try
       
       if waittimeout>0 then 
       begin
         cash_savelog('Установка таймера ожидания ответа: '+inttostr(WaitTimeout));
         VarDevice.DirectIO(233,WaitTimeout,0); Cash_checkError('cash_execute:233');
       end;

       Cash_SaveLog('cash_execute:printstate');
       // еще идет печать предыдущего документа, это возможно, если мы делим чек,
       // то повисим (до 15 секунд)
       for i := 1 to 10 do
       begin
         //VarDevice.getstatus; Cash_checkerror;
         if (VarDevice.PrinterState<>1 {FPTR_PS_MONITOR = 1} )  then sleep(3000)  else break;
       end;
       Cash_checkError('cash_execute:printerstate');


       Cash_GetNumbers(false);
       
       if CASH_stringlength <0           
       then begin
         stringlength := asinteger(VarDevice.DescriptionLength);  
         Cash_CheckError('cash_execute:descriptionlength');
       end;
       
       EmailSeller := appIniFile.readstring('cash','CashOnlineEmailSeller',DBIniFile.readstring('cash','CashOnlineEmailSeller',''));

       Cash_SaveLog('cash_execute:resetprinter');
       VarDevice.ResetPrinter; Cash_CheckError('cash_execute:resetprinter'); // перевод статуса ФР в "Monitor". Открытые документы будут аннулированы или закрыты.
 
       VarDevice.FiscalReceiptType := ifthen_integer(IsReturn,7,4); Cash_CheckError('cash_execute:fiscalreceipttype');

       CashierName := UserProgram; 
       if UserProgramID=0 then INN := '' else INN := UserProgramINN;
       
       if Cash_UserName_Manual and (CashierName>'') then
       begin
         strval := ifthen_string(INN>'',INN + '&')+ cashiername; 
         //createhint('Кассир выведен: '+strval);
         VarDevice.SetPosID('1',ConvertStr1(strval)); Cash_CheckError('cash_execute:setposid');
       end;
       

       Cash_SaveLog('cash_execute:beginfiscalreceipt');
       VarDevice.BeginFiscalReceipt(true); Cash_CheckError('cash_execute:beginfiscalreceipt'); // или false?
       //VarDevice.OpenCheck; Cash_CheckError;

       if (CASH_SAVEMARKS<>0) and (MDLP_COUNT>0) then
       begin
         // 1084       
	       VarDevice.DirectIO(216,0,ConvertStr1('mdlp')); Cash_checkError('1084-0');  
         VarDevice.DirectIO(216,1,ConvertStr1('sid'+mdlp_storage_id+'&')); Cash_checkError('1084-1');  
       end;

       
       if (CASH_SAVEMARKS<>0) and (MDLP_COUNT>0) then
       begin
         //1261
         VarDevice.DirectIO(185,1262, ConvertStr1('020')); Cash_CheckError('Cash_Execute:1262');
         VarDevice.DirectIO(185,1263, ConvertStr1(bvformatdatetime('ddmmyyyy',date(),'','.'))); Cash_CheckError('Cash_Execute:1263');
         VarDevice.DirectIO(185,1264, ConvertStr1(inttostr(ResultDocumentNumber))); Cash_CheckError('Cash_Execute:1264');

         StrValue2 := 'tm=mdlp';
         
         if VarReceptSeria>'' then
           StrValue2 := StrValue2 + '&ps='+StringReplace(VarReceptSeria,'&','&&',True,True);
         if VarReceptNumber>'' then
           StrValue2 := StrValue2 + '&dn='+StringReplace(VarReceptNumber,'&','&&',True,True); //&&781 ??
         if VarReceptDate<>0 then
           StrValue2 := StrValue2 + '&dd='+FormatDateTime('yymmdd',VarReceptDate);
         

         StrValue2 := StrValue2 + '&sid='+mdlp_storage_id+'&';
          
         VarDevice.DirectIO(185,1265, StrValue2); Cash_CheckError('Cash_Execute:1265');
         //VarDevice.DirectIO(185,1265, 'tm=mdlp&sid='+mdlp_storage_id+'&'); Cash_CheckError('Cash_Execute:1265');
       end
       else if (VarReceptSeria>'') or (VarReceptNumber>'') or (VarReceptDate<>0) then
       begin
         Cash_SaveLog('Ошибка: Указан рецепт, но нет ни одного маркированного товара, либо не указана опция сохранения марок. Сохранение чека невозможно.');
         raiseexception('Ошибка: Указан рецепт, но нет ни одного маркированного товара, либо не указана опция сохранения марок. Сохранение чека невозможно.');
       end;

       
       email := coalesce_string(email,cash_1008_string);

       if Email<>''
       then begin
          //Отправка данных о покупателе 1008
          //VarDevice.DirectIO(185,1008,ConvertStr1(EMail)); не знаю, откуда это взято
          VarDevice.DirectIO(174,0,ConvertStr1(EMail));  //В качестве параметра можно передавать или телефон (+7хххххххххх) или e-mail: test@test.com
          Cash_CheckError('Cash_execute:1008'); 
       end;

       if EmailSEller<>'' // адрес электронной почты отправителя чека 
       then begin
          //Отправка данных о продавце - 1117. пока что описания тэга 1117 нет в документации, не знаю, откуда это взято.
          VarDevice.DirectIO(185,1117,ConvertStr1(EMailSeller)); Cash_CheckError('Cash_execute:1117'); 
       end;
       
       
       if CASH_PRINTCLIENT and (Clientid<>0) then
       begin
         StrVal := asstring(BDEQueryValue('select name from agents where id = :id',[Clientid]));
         if strval>'' then
         begin 
           StrVal1 := asstring(BDEQueryValue('select inn from agents where id = :id',[Clientid]));
           //Отправка имени покупателя
           //VarDevice.DirectIO(185,1227,ConvertStr1(StrVal)); //не знаю, откуда это 
           VarDevice.DirectIO(214,0,ConvertStr1(StrVal)); //не знаю, откуда это 
           Cash_CheckError('Cash_execute:1227'); 

           if STrvAl1>'' then 
           begin
             //Отправка инн покупателя
             //VarDevice.DirectIO(185,1228,ConvertStr1(StrVal1)); не знаю, откуда это  
             VarDevice.DirectIO(214,1,ConvertStr1(StrVal1));   
             Cash_CheckError('Cash_execute:1228'); 
           end;
         end; 
       end;

       // если задание пользователя не сработает на открытой смене, то использовать VarDevice.DayOpened - true - смена открыта, false - смена закрыта. Вывести при закрытой смене, дальше - не выводить.
       // if VarDevice.DayOpened = false then // если смена закрыта
       if false and Cash_UserName_Manual and (CashierName>'')
       then begin
         VarDevice.DirectIO(185,1021,ConvertSTr1(CashierName)); 
         Cash_CheckError('cash_execute:1021');
         
         if INN>'' then 
         begin
           VarDevice.DirectIO(185,1018,ConvertStr1(INN)); 
           Cash_CheckError('cash_execute:1018');
         end;           
       end;

       // печать чека

       cds.disablecontrols;
       try
         pSummaryCash := 0;
         pSummaryDiscount := 0;      // для варианта без детализации

         cds.First;
         while not cds.eof do
         begin

           isFrac := 
              (cds.fieldbyname(fdivnum).asinteger>1)
              and (cds.fieldbyname('tmp_quantity_divnum').asinteger>0) 
              and (cds.fieldbyname('tmp_quantity_divnum').asinteger<cds.fieldbyname(fdivnum).asinteger);

           pQuantityDivNum := cds.fieldbyname('tmp_quantity_divnum').asinteger;
         
           pPrice := cds.fieldbyname(FPrice).asCurrency;

           thField := cds.findfield(fPriceOut);
           if thField=nil then thField := cds.findfield(fpriceout1);
           if thfield=nil then pPriceOut := 0 else  pPriceOut := thField.ascurrency;

           if pPriceOut < pPrice then  pPriceOut := pPrice;

           pQuantity := cds.fieldbyname(FQuantity).AsFloat;
           pQntFrac := bvroundto(frac(pQuantity),-6);
           pDivNum := coalesce_integer(cds.fieldbyname(FDivNum).asinteger,1);
           //pQuantityDivNum := bvroundto( pQuantity * pDivNum,-3); // 4-6 знаки оставим несущественными, число 2.999997 в фасовке еще допускается, округлим
           pPriceOutDivNum := bvroundto( pPriceOut / pDivNum,-4);
           pPriceDivNum := bvroundto( pPrice / pDivNum,-4);

           pmarks_barcodes := cds.fieldbyname(fmarks_barcodes).asstring; 

           IsFloatBarcode := cds.fieldbyname(FFloatBarcode).asinteger<>0; 

           if (DetailCash<>0) // печать без детализации 
              or IsFloatBarcode and (pQntFrac<>bvroundto(pQntFrac,-3)) // Предохранимся от количеств, которые не понимает касса, оставляем 1xСумма
              or not IsFloatBarcode and (pQntFrac<>0) and (
                 Not CASH_CANDIVIDEDIVNUM // нельзя делить фасованные, указано в настройках
                 or (pDivNum=1)   // товар не является делящимся
                 or (pQuantityDivNum<>bvroundto(pQuantityDivNum)) // число 2.999997 в фасовке еще допускается, но 2.999 - уже странно
                 or (pPriceOutDivNum <> bvRoundTo(pPriceOutDivNum,-2))) // проблемы с ценой фасованной, лишние знаки после запятой
           then begin
             pPriceOut := pQuantity * pPriceOut;
             pPrice := pQuantity * pPrice;
             pQuantity := 1;
           end
           else if (pQntFrac<>0) and not IsFloatBarcode and (pDivNum<>1) // невесовой товар, но делится
           then begin
                pPriceOut := pPriceOutDivNum;
                pPrice := pPriceDivNUm;
                pQuantity := pQuantityDivNum; 
           end;
           
           pPriceOut := bvroundto(pPriceOut, -2,rpTruncate);
           pPrice := bvroundto(pPrice, -2,rpTruncate);

           pSummary := pQuantity * pPrice;
           pSummaryOut := pQuantity * pPriceOut;

           pSummaryCash := pSummaryCash + pSummary;
           pSummaryDiscount := pSummaryDiscount + (pSummaryOut - pSummary);
           pDiscount := bvRoundTo(pSummaryOut-pSummary,-2);

           PNN := cds.fieldbyname(FNN).asstring;
           
           MarkValidationResult := 0;


           if (cash_savemarks<>0)
              and (cds.fieldbyname('tmp_NEED_CHECK_KKM').asinteger>0)
              and (pmarks_barcodes>'') 
           then begin
             cash_savelog('Проверка марки');
             cash_savelog('mark='+stringreplace(pmarks_barcodes,#29,'{SG}',true,true));
             
             VarDevice.DirectIO(225,0,ConvertStr1(pmarks_barcodes));  Cash_checkError('cash_execute:225');

             if (pDivNum>1) and (pQuantityDivNum<pDivNum) then
             begin
                cash_savelog('Фасовка: '+inttostr(pdivnum));
                //указываем количество товара в упаковке (тег 1294)
                //String packageSize = “3”;
                //OPOS.DirectIO(226, 0, packageSize);
                VarDevice.DirectIO(226,0,pDivnum);  Cash_checkError('cash_execute:226');
             end;
             
             cash_savelog('Кол-во: '+inttostr(pquantitydivnum));
             
             //Вызываем команду проверки КМ с параметрами валидации:
             // Планируемый статус товара(тег 2003), 1 = Штучный товар
             // Мера количества (тег 2108). 0 = Поштучно 
             // Количество товара (тег 1023) - должно потом совпадать с количеством в товаре, 
             // в данном случае 1шт
             // Режим проверки 1 = в ИСМ всегда проверяется
             //String params = “1;0;1;1” //Параметры по порядку через “;”
             //result = OPOS.DirectIO(227, validationResult, Params);
             
             
             strval := '1;0;'+inttostr(Coalesce_Integer(pQuantityDivNum,1))+';1';  
             cash_savelog('Параметры запроса: '+strval);

             //while true do
             //begin
             
               //VarDevice.DirectIO(227, MarkValidationResult, strval); Cash_checkError('cash_registersreport:DirectIO(227)');
               VarArr[0] := 227;
               VarArr[1] := 0;
               VarArr[2] := strval;  
               InvokeOLEMethod(VarDevice,'DirectIO',VarArr,[true,true,true]); 
               Cash_checkError('cash_registersreport:DirectIO(227)');
  
               {
               if (VarDevice.ResultCode = 114) 
                  and (VarDevice.ResultCodeExtended= 242) 
               then begin
                 //cash_registersreport:DirectIO(227): ошибка: 114:242: Неверный номер пакета при приеме
                 sleep(CASH_CHECKMARK_SLEEPMS);
                 if wfcm=nil then WFCM := TMyWait.create('Идет проверка марки в ККМ',selfscript,nil,true); 
                 if wfcm.checkstop then raiseexception('Проверка марок остановлена пользователем');
               end
               else begin
                 Cash_checkError('cash_registersreport:DirectIO(227)');
                 break;
               end;
             end;}
             
             if wfcm<>nil then 
             begin
               FreeAndNil(wfcm)
             end;

             MarkValidationResult := asinteger(VarArr[1]);
             cash_savelog('Проверка марки, результат: '+inttostr(MarkValidationResult));

             
             //Решено добавить КМ
             //Параметры:
             // Присвоенный статус товара (тег 2110)  = "Планируемому" (тег 2003) = 1
             // Мера количества (тег 2108). 0 = Поштучно Должно совпадать с DirectIO(227)
             //Integer confirm = 1; //Подтверждаем добавление товара
             //String params = “1;0”
             cash_savelog('Подтверждение добавления товара');
             VarDevice.DirectIO(228, 1, '1;0'); Cash_checkError('cash_registersreport:DirectIO(228)');
           end;
                                                
           if (CASH_SAVEMARKS<>0) 
              and (cds.fieldbyname('tmp_ismdlp').asinteger>0) 
              and (pmarks_barcodes>'')
              and not NNInFilterEGAIS(pNN) and not NNInFilterEGAISBEER(pNN) 
           then begin
             if NNInFilterTOBACCO(pNN) then
             begin
               raiseexception('Пока не реализовано, обратитесь в техподдержку');
             end
             else begin
             end; 
           end
           else begin
             // проверим на tag1162 в справочнике товаров (Поле для заполнения тэга 1162 для медизделий (или других?) Постановление Правительства РФ от 16.04.2020 N 521 "О внесении изменений в постановление Правительства Российской Федерации от 21 февраля 2019 г. N 174")
             strvalue := asstring(BDEQueryValue('select tag1162 from tovar where nn = :nn',[pNN]));
             if strvalue>'' then 
             begin
               cash_savelog('nntag1162='+strvalue);
               strvalue := IntToHex(STrToInt64Protected(strvalue),6);
               strvalue := StrPadLeft(strvalue,12,'0');
               StrVAlue := '450D'+StrValue;
               cash_savelog('nntag1162_hex2='+strvalue);

               for i := 1 to length(StrVAlue) div 2-1 do strvalue := copy(strvalue,1,i*2+i-1) + '$'+ copy(strvalue,i*2+i,length(strvalue)); // пробелы между знаками поставим
               strvalue := '$'+strvalue; // первый '$'

               VarDevice.DirectIO(212,0,ConvertStr1(StrValue));
               Cash_checkError('cash_execute:212-?');
               
               //if CASH_216_IN_ROW then   
               begin
        	       VarDevice.DirectIO(216,0,ConvertStr1('mdlp'));  
                 Cash_checkError('cash_execute:216-0');
        
                 VarDevice.DirectIO(216,1,ConvertStr1('sid'+mdlp_storage_id+'&'));  
                 Cash_checkError('cash_execute:216-1');
               end;
               

             end;
           end;


           if (bvroundto(pSummary, -2) > 0)
           then begin

             pname := cds.fieldbyname(FName).asstring;
             if (stringlength>0) and cash_trimnamegoods then pname := copy(pname,1,stringlength);
             
             for i := 1 to min(length(CASH_NameBadCharList), length(CASH_NameReplaceCharList)) do 
               if pos(CASH_NameBadCharList[i], pname) > 0
                 then pname := stringreplace(pname, CASH_NameBadCharList[i], CASH_NameReplaceCharList[i],true,true);

             if detailcash <> 2
             then begin                 // детализация чека
             

               if Cash_ShowNDS then 
               begin
                 NDS := cds.fieldbyname('nds').asinteger;
                 //VarDevice.Destination := 1; Cash_CheckError;
                 //VarDevice.TaxTypeNumber := ifthen_integer(NDS=20,CASH_NDS20TYPE,ifthen_integer(NDS=18,CASH_NDS18TYPE,ifthen_integer(NDS=10 ,CASH_NDS10TYPE,CASH_NDS0TYPE))); Cash_CheckError;
                 //if cash_showNDSSUMMARY then 
                 //begin
                 //  VarDevice.Summ := bvroundto(cds.fieldbyname(FSummary).asfloat *NDS/(100+NDS),-2);                        
                 //  Cash_CheckError;
                 //end;
               end
               else NDS := 0;
               
               //pname := ConversionString(pname,ctAnsi2OEM); если крякозябры, то нужна конверсия

               
                //PrintRecItem(const Description: WideString; 
                //Price: Currency; 
                //Quantity, VatInfo: Integer; 
                //UnitPrice: Currency; 
                //const UnitName: WideString)                

               if (cash_savemarks<>0)
                  and (cds.fieldbyname('tmp_ismarking').asinteger>0)
                  and (pmarks_barcodes>'') 
                  and (pDivNum>1) and (pQuantityDivNum<pDivNum) then
               begin
                  cash_savelog('Фасовка: '+inttostr(pdivnum));
                  //указываем количество товара в упаковке (тег 1294)
                  //String packageSize = “3”;
                  //OPOS.DirectIO(226, 0, packageSize);
                  VarDevice.DirectIO(226,0,pDivnum);  Cash_checkError('cash_execute:226-1');
                  //VarDevice.DirectIO(226,0,inttostr(pquantitydivnum)+'/'+inttostr(pDivnum));  Cash_checkError('cash_execute:226-1');
               end;

                                                 
              cash_savelog(ConvertStr1(pName)+
                           ':0'+
                           ':'+asstring(round(pQuantity * 1000))+
                           ':'+asstring( ifthen_integer(NDS=20,CASH_NDS20TYPE,ifthen_integer(NDS=18,CASH_NDS18TYPE,ifthen_integer(NDS=10,CASH_NDS10TYPE,CASH_NDS0TYPE))))+
                           ':'+asstring(  ifthen_currency(CASH_DiscountType<>0,pPriceOut,pPrice)));


              if cds.fieldbyName('tmp_ismdlp').asInteger <> 0 then
              begin

                 { может быть, здесь надо еще вставить?
                 //1260 ->
                 VarDevice.DirectIO(185,1262, ConvertStr1('020')); Cash_CheckError('Cash_Execute:1262');
                 VarDevice.DirectIO(185,1263, ConvertStr1(bvformatdatetime('ddmmyyyy',date(),'','.'))); Cash_CheckError('Cash_Execute:1263');
                 VarDevice.DirectIO(185,1264, ConvertStr1(inttostr(ResultDocumentNumber))); Cash_CheckError('Cash_Execute:1264');
                 VarDevice.DirectIO(185,1265, 'tm=mdlp&sid='+mdlp_storage_id+'&'); Cash_CheckError('Cash_Execute:1265');
                 //1260 <-
                 }
  
                 // -> тэг 1260
                 {
                  Список параметров по порядку:
                  (Строка [1..32]) Код товарной номенклатуры (тег 1162),
                  (Строка [1..64]) Дополнительный реквизит (тег 1191),
                  Зарезервировано, для передачи тега 1197 используется команда Dio 182,
                  (Целое число) Признак агента по предмету расчета (тег 1222),
                  (Строка [12]) ИНН поставщика (тег 1226),
                  (Строка [0..19])Телефон поставщика (тег 1171),
                  (Строка [0..256]) Наименование поставщика (тег 1225),
                  (Строка [0..256]) Адрес оператора перевода (тег 1005),
                  (Строка [12]) ИНН оператора перевода (тег 1016),
                  (Строка [0..64]) Наименование оператора перевода (тег 1026),
                  (Строка [0..19]) Телефон оператора перевода (тег 1075),
                  (Строка [0..24]) Операция платежного агента (тег 1044),
                  (Строка [0..19]) Телефон платежного агента (тег 1073),
                  (Строка [0..19]) Телефон оператора по приему платежей (тег 1074)
                  (Число)[0..3] Идентификатор ФОИВ (тег 1262)
                  (Дата)[8] Дата документа основания в формате ddmmyyyy (тег 1263)
                  (Строка)[0..32] Номер документа основания (тег 1264)
                  (Строка)[0..256] Значение отраслевого реквизита (тег 1265)
                 }
  
                 StrValue2 := 'tm=mdlp';
                 
                 if DataSet.FieldByName('IsNeedRecept').asInteger<>0 then
                 begin
                   if VarReceptSeria>'' then
                     StrValue2 := StrValue2 + '&ps='+StringReplace(VarReceptSeria,'&','&&',True,True);
                   if VarReceptNumber>'' then
                     StrValue2 := StrValue2 + '&dn='+StringReplace(VarReceptNumber,'&','&&',True,True); //&&781 ??
                   if VarReceptDate<>0 then
                     StrValue2 := StrValue2 + '&dd='+FormatDateTime('yymmdd',VarReceptDate);
                 end;
                 StrValue2 := StrValue2 + '&sid='+mdlp_storage_id+'&';
  
                 //StrValue := StrToHex1(StrValue);
  
                 StrValue :=  ';;;;;;;;;;;;;;'+
                              '020;'+
                              bvformatdatetime('ddmmyyyy',date(),'','.')+';'+
                              inttostr(ResultDocumentNumber)+';'+
                              StrValue2;
                              
                 
                 Cash_SaveLog('тэг 1260 для МДЛП: '+StrValue,True);
  
                 StrValue := ConvertStr1(StrValue);
  
                 VarDevice.DirectIO(212,0,StrVAlue);
                 Cash_checkError('cash_execute:212');
                 
                 // <- тэг 1260
              end
              else if {(GISMT_REQID>'') and (GISMT_REQTIMESTAMP>'')} (CDS.FindField('GISMT_CHECKED')<>nil) and (CDS.FieldByName('GISMT_CHECKED').asINteger<>0) then
              begin  
                 Cash_SaveLog('тэг 1260 для МДЛП: '+StrValue,True);
                 StrValue2 := 'UUID='+CDS.FieldByName('GISMT_REQID').AsSTring+'&Time='+CDS.FieldByName('GISMT_REQTIMESTAMP').AsSTring; //+'&';
                 if CDS.FieldByName('GISMT_INST').asString>'' then
                   StrValue2 := StrValue2 + '&Inst='+CDS.FieldByName('GISMT_INST').asString;
                 if CDS.FieldByName('GISMT_VERSION').asString>'' then
                   StrValue2 := StrValue2 + '&Ver='+CDS.FieldByName('GISMT_VERSION').asString;
  
                 StrValue :=  ';;;;;;;;;;;;;;'+
                              '030;'+
                              bvformatdatetime('ddmmyyyy',date(),'','.')+';'+
                              inttostr(ResultDocumentNumber)+';'+
                              StrValue2;
                              
                 StrValue := ConvertStr1(StrValue);
  
                 VarDevice.DirectIO(212,0,StrVAlue);
                 Cash_checkError('cash_execute:212');
              end;

               
               VarDevice.PrintRecItem(
                  ConvertStr1(pName),
                  0,               
                  round(pQuantity * 1000), 
                  ifthen_integer(NDS=20,CASH_NDS20TYPE,ifthen_integer(NDS=18,CASH_NDS18TYPE,ifthen_integer(NDS=10,CASH_NDS10TYPE,CASH_NDS0TYPE))),
                  ifthen_currency(CASH_DiscountType<>0,pPriceOut,pPrice),
                  '');
               Cash_CheckError('cash_execute:printrecitem');

              
               //VarDevice.Name := pName; Cash_CheckError;

               //VarDevice.Quantity := pQuantity; Cash_CheckError;
               //VarDevice.Price := ifthen_currency(CASH_DiscountType<>0,pPriceOut,pPrice); Cash_CheckError;

               //thField := cds.findfield(fsection);
               //if thfield<>nil then IntValue :=  coalesce_integer(thField.asinteger,CurrentSection,1) else IntValue := CurrentSection;
               //VarDevice.Department := IntValue; Cash_CheckError;
               
               // это вариант скидки по протоколу 3
               //if {(DiscountType=1) and} CASH_protocol3 // всегда этот атрибут задавать, ибо запомнит от последнего значения и так и будет воспроизводить
               //then begin 
               //  VarDevice.DiscountType := 0; Cash_CheckError; // скидка от суммы
               //  VarDevice.DiscountValue := ifthen_currency(cash_DiscountType<>0,pDiscount,0);  Cash_CheckError;
               //end;

               
               // вариант скидки по протоколу 2x
               if (cash_DiscountType=1) and not CASH_protocol3 and (pDiscount <> 0) then
               begin
                 //VArDevice.Summ := pDiscount; Cash_CheckError;
                 //VarDevice.Destination := 1; Cash_CheckError; // 0 - для всего чека, 1 - для последней операции
                 //VarDevice.SummDiscount; Cash_CheckError;
               end;
               if (pDiscount <> 0) and CASH_PrintStringDiscountSummary        
               then begin     
                 //VarDevice.Caption := 'В том числе скидка: '+bvCurrtostrF(pDiscount)+#13+'Цена без скидки: '+bvCurrToStrF(pPriceOut);
                 StrValue := 'Скидка: ';
                 strvalue1 := bvcurrtostrf(pdiscount);
                 if stringlength>0 then StrValue1 := StrPad(strvalue1,stringlength-length(strvalue),' ',true);
                 VarDevice.PrintRecMessage(ConvertStr1(StrVAlue+StrVAlue1)); Cash_CheckError('cash_execute:printrecmessage'); // есть еше PreLine и PostLine
                 //VarDevice.Caption := strvalue+ strvalue1; Cash_CheckError;
                 //VarDevice.PrintString; Cash_CheckError;
               end;

               if (pDiscount <> 0) and CASH_PrintStringPriceOut        
               then begin     
                 //VarDevice.Caption := 'В том числе скидка: '+bvCurrtostrF(pDiscount)+#13+'Цена без скидки: '+bvCurrToStrF(pPriceOut);
                 StrValue := 'Цена без скидки: ';
                 strvalue1 := bvcurrtostrf(pPriceOut);
                 if stringlength>0 then StrValue1 := StrPad(strvalue1,stringlength-length(strvalue),' ',true);
                 VarDevice.PrintRecMessage(ConvertStr1(StrVAlue+StrVAlue1)); Cash_CheckError('cash_execute:printrecmessage-1'); // есть еше PreLine и PostLine
                 //VarDevice.Caption := strvalue+ strvalue1; Cash_CheckError;
                 //VarDevice.PrintString; Cash_CheckError;
               end;

               {
               if Cash_ShowNDS
               then begin
                 VarDevice.Destination := 1; Cash_CheckError;
                 VarDevice.TaxTypeNumber := ifthen_integer(cds.fieldbyname('nds').asinteger=18,CASH_NDS18TYPE,
                                          ifthen_integer(cds.fieldbyname('nds').asinteger=10,CASH_NDS10TYPE,CASH_NDS0TYPE)); 
                 Cash_CheckError;
                 
               end;
               }
             end;
           end;
           cds.next;
         end;

         if (DetailCash = 2) // Без детализации
         then begin
           if (pSummaryCash > 0) then
           begin
             VarDevice.PrintRecItem(
                ConvertStr1(pName),
                0,               
                //ifthen_currency(CASH_DiscountType<>0,pPriceOut,pPrice), 
                1000, 
                0, //ifthen_integer(NDS=20,CASH_NDS20TYPE,ifthen_integer(NDS=18,CASH_NDS18TYPE,ifthen_integer(NDS=10 ,CASH_NDS10TYPE,CASH_NDS0TYPE)),
                ifthen_currency(CASH_DiscountType<>0, bvroundto(pSummaryCash + pSummaryDiscount, -2,rpTruncate), bvroundto(pSummaryCash, -2,rpTruncate)),
                '');

             //VarDevice.Quantity := 1; Cash_CheckError;
             
             //thField := cds.findfield(fsection);
             //if thfield<>nil then IntValue :=  coalesce_integer(thField.asinteger,CurrentSection,1) else IntValue := CurrentSection;
             //VarDevice.Department := IntValue; Cash_CheckError;

             //VarDevice.Price := ifthen_currency(CASH_DiscountType<>0, bvroundto(pSummaryCash + pSummaryDiscount, -2,rpTruncate), bvroundto(pSummaryCash, -2,rpTruncate)); 
             Cash_CheckError('cash_execute:printrecitem(1)');

             //if {(DiscountType=1) and} CASH_protocol3 // всегда программировать это значение, даже если пусто
             //then begin 
             //  VarDevice.DiscountType := 0; Cash_CheckError;
             //  VarDevice.DiscountValue := ifthen_currency(cash_DiscountType<>0, bvRoundto(pSummaryDiscount,-2),0);  Cash_CheckError;
             //end;

             //if (CASH_DiscountType=1)and ( bvroundto(pSummaryDiscount, -2) <> 0) then
             //begin
             //  VArDevice.Summ := bvroundto(pSummaryDiscount, -2); Cash_CheckError;
             //  VarDevice.Destination := 1; Cash_CheckError; // 0 - для всего чека, 1 - для последней операции
             //  VarDevice.SummDiscount; Cash_CheckError;
             //end;
             
             
             if (pSummaryDiscount <> 0) and CASH_PrintStringDiscountSummary        
             then begin     
               //VarDevice.Caption := 'В том числе скидка: '+bvCurrtostrF(pDiscount)+#13+'Цена без скидки: '+bvCurrToStrF(pPriceOut);
               StrValue := 'Скидка: ';
               strvalue1 := bvcurrtostrf(pSummaryDiscount);
               if stringlength>0 then StrValue1 := StrPad(strvalue1,stringlength-length(strvalue),' ',true);
               VarDevice.PrintRecMessage(ConvertStr1(StrVAlue+StrVAlue1)); Cash_CheckError('cash_execute:printrecmessage(0)'); // есть еше PreLine и PostLine
               //VarDevice.Caption := strvalue+ strvalue1; Cash_CheckError;
               //VarDevice.PrintString; Cash_CheckError;
             end;

             if (pDiscount <> 0) and CASH_PrintStringPriceOut        
             then begin     
               //VarDevice.Caption := 'В том числе скидка: '+bvCurrtostrF(pDiscount)+#13+'Цена без скидки: '+bvCurrToStrF(pPriceOut);
               StrValue := 'Итого без скидки: ';
               strvalue1 := bvcurrtostrf(pSummaryCash);
               if stringlength>0 then StrValue1 := StrPad(strvalue1,stringlength-length(strvalue),' ',true);
               VarDevice.PrintRecMessage(ConvertStr1(StrVAlue+StrVAlue1)); Cash_CheckError('cash_execute:printrecmessage(1)'); // есть еше PreLine и PostLine
               //VarDevice.Caption := strvalue+ strvalue1; Cash_CheckError;
               //VarDevice.PrintString; Cash_CheckError;
             end;
             
           end;
         end;

         if (CASH_DiscountType=2) and (bvroundto(pSummaryDiscount, -2) <> 0) 
         then begin
           //VarDevice.Summ := bvroundto(pSummaryDiscount, -2); Cash_CheckError;
           //VarDevice.Destination := 0;
           //VarDevice.SummDiscount; Cash_CheckError;
         end;
         
         if (pSummaryDiscount <> 0) and CASH_PrintStringDiscountOnCheck        
         then begin     
           StrValue := 'Итого скидка по чеку: ';
           strvalue1 := bvcurrtostrf(pSummaryDiscount);
           if stringlength>0 then StrValue1 := StrPad(strvalue1,stringlength-length(strvalue),' ',true);
           VarDevice.PrintRecMessage(ConvertStr1(StrVAlue+StrVAlue1)); Cash_CheckError('cash_execute:printerstate(2)'); // есть еше PreLine и PostLine
           //VarDevice.Caption := strvalue+ strvalue1; Cash_CheckError;
           //VarDevice.PrintString; Cash_CheckError;
         end;
       finally
         cds.enablecontrols;
       end;
       cds.first;

///////////////////////////////////////////////

       // что будет, если paysummary = 0, и что писать, если безнал?
       //if VarActionNal then Summ2 := bvroundto(Paysummary, -2) else Summ2 := 0; //bvroundto(pSummaryCash, -2 );
       Summ2 := bvroundto(Paysummary, -2);
       if summ2 = 0 then Summ2 := bvroundto(pSummaryCash, -2 );

       case DOC_TYPE_CLOSE of
         1 : Index := CASH_TypeCloseBN;
         2 : Index := CASH_TypeCloseKredit;
         3 : Index := CASH_TypeCloseSert;
         else  Index := 0;
       end;
       
       //VarDevice.TypeClose := index;

       VarDevice.PrintRecTotal(0,Summ2,inttostr(Index));
       Cash_CheckError('cash_execute:printrectotal');

       if (index=0) and CASH_OPENDRAWER_NAL then Cash_OpenDrawer(false); 

       
       if Taxtype>0 then 
       begin 
         VarDevice.DirectIO(185,1055,inttostr(TaxType)); 
         Cash_CheckError('cash_execute:1055');
         
         //VarDevice.AttrNumber := 1055; Cash_CheckError;
         //VarDevice.AttrValue := TaxType; Cash_CheckError;
         //VarDevice.WriteAttribute; Cash_CheckError; 
       end;
       

//raiseexception('test');
       
       VarDevice.EndFiscalReceipt(true); // параметр PrintHeader: true или false?
       Cash_CheckError('cash_execute:endfiscalreceipt');

       Cash_Cut(false); Cash_CheckError('cash_execute:cash_cut');
   finally
       if needconnect then cash_disconnect;
       setlength(vararr,0); 

       VarDevice := null; // Освободить VarDevice. Объект драйвера должен выгрузиться
   end;
   Cash_SaveLog('cash_execute:exit');
end;

procedure Cash_CancelCheck(NeedConnect :boolean = true);
begin
  if NeedConnect then Cash_Connect(CASH_KKMSYSPASSWORD>'');
  try
    VarDevice.ResetPrinter; 
    //VarDevice.GetStatus; Cash_CheckError;
    //if (VarDevice.CheckState <> 0) then VarDevice.CancelCheck; 
    Cash_CheckError('cash_cancelcheck');
  finally
    if NeedConnect then  Cash_disconnect;
  end;
end;

procedure Cash_RepeatCheck(NeedConnect :boolean = true);
begin
  raiseexception('Cash_RepeatCheck: функция не реализована для данного драйвера');
end;


procedure Cash_DoSummary(IsIn :boolean; NeedConnect :boolean = true);
var i :integer;
begin
  if needconnect then Cash_Connect(false);
  try
    VarDevice.ResetPrinter; Cash_CheckError('cash_dosummary:resetprinter'); // перевод статуса ФР в "Monitor". Открытые документы будут аннулированы или закрыты.
    
    VarDevice.FiscalReceiptType := ifthen_integer(IsIn,1,2);
    Cash_CheckError('cash_dosummary:fiscalreceipttype');

    VarDevice.BeginFiscalReceipt(true); Cash_CheckError('cash_dosummary:beginfiscalreceipt'); // или false?
  
    //VarDevice.mode := 1; Cash_CheckError;
    //VarDevice.SetMode; Cash_CheckError;
    //VarDevice.summ := summary; Cash_CheckError;
    //if isIn then VarDevice.CashIncome else VarDevice.CashOutcome; Cash_CheckError;
    
    VarDevice.PrintRecTotal(0,Summary,'0'); Cash_checkError('cash_dosummary:printrectotal');
    VarDevice.EndFiscalReceipt(false); Cash_CheckError('cash_dosummary:endfiscalreceipt'); // или true?
    
    Cash_Cut(false); Cash_CheckError('cash_dosummary:cash_cut(1)');
  finally
    if needconnect then cash_disconnect;
  end;
end;

procedure Cash_DoSummaryIn(NeedConnect :boolean = true);
begin
  Cash_DoSummary(true,NeedConnect);
end;

procedure Cash_DoSummaryOut(NeedConnect :boolean = true);
begin
  Cash_DoSummary(false,NeedConnect);
end;


procedure Cash_XReport(NeedConnect :boolean = true);
begin
  if NeedConnect then Cash_Connect(true);
  try   
    VarDevice.ResetPrinter; Cash_CheckError('cash_xreport:resetprinter'); // перевод статуса ФР в "Monitor". Открытые документы будут аннулированы или закрыты.
    VarDevice.PrintXReport(); Cash_CheckError('cash_xreport:printxreport');

    //VarDevice.mode := 2; Cash_CheckError;
    //VarDevice.SetMode; Cash_CheckError;
    //VarDevice.ReportType := 2; Cash_CheckError;
    //VarDevice.Report; Cash_CheckError;
  finally
    if NeedConnect then Cash_disconnect;
    VarDevice := null; // Освободить VarDevice. Объект драйвера должен выгрузиться
  end;
end;

procedure Cash_ZReport(NeedConnect :boolean = true);
begin
  if NeedConnect then Cash_Connect(true);
  try
    VarDevice.ResetPrinter; Cash_CheckError('cash_zreport:resetprinter'); // перевод статуса ФР в "Monitor". Открытые документы будут аннулированы или закрыты.
    VarDevice.PrintZReport(); Cash_CheckError('cash_zreport:printzreport');

    //VarDevice.mode := 3; Cash_CheckError;
    //VarDevice.SetMode; Cash_CheckError;
    
    //VarDevice.ReportType := 1; Cash_CheckError;
    //VarDevice.Report; Cash_CheckError;
  finally
    if NeedConnect then Cash_disconnect;
    VarDevice := null; // Освободить VarDevice. Объект драйвера должен выгрузиться
  end;
end;

procedure Cash_SectionReport(NeedConnect :boolean = true);
begin
  if NeedConnect then Cash_Connect(true);
  try
    raiseException('Отчет по секциям не реализовано в данной версии драйвера');
  finally
    if NeedConnect then Cash_disconnect;
    VarDevice := null; // Освободить VarDevice. Объект драйвера должен выгрузиться
  end;
end;

procedure Cash_UsersReport(NeedConnect :boolean = true);
begin
  if NeedConnect then Cash_Connect(true);
  try
    raiseException('Отчет по секциям не реализовано в данной версии драйвера');
  finally
    if NeedConnect then Cash_disconnect;
    VarDevice := null; // Освободить VarDevice. Объект драйвера должен выгрузиться
  end;
end;

procedure Cash_RegistersReport(NeedConnect :boolean = true);

var i :integer;
    List :Tstringlist;
    ResultVal :string; //,ResultVal1,ResultVal2,ResultVal3,ResultVal4,ResultVal5,ResultVal6 :string;
    FloatVal :double;
    BoolVal :boolean;
    IntVal :integer;
    VarArr :array of variant;
begin
  if (ResultRegisterListNames=nil) or (ResultRegisterListValues=nil) 
  then raiseException('Ошибка в Cash_RegistersReport: Не проинициализированы контейнеры для результатов функции');

  if NeedConnect then Cash_Connect(true);

  SetLength(VarArr,3);

  List := tstringlist.create;
  with TmyWait.create('Идет сбор данных по регистрам',selfscript) do  
  try

    VarDevice.ResetPrinter; Cash_CheckError('cash_registersreport:resetprinter');
      
    resultregisterlistnames.clear;
    resultregisterlistvalues.clear;


    VarDevice.TotalizerType :=  2; if not cash_checkError('cash_registersreport:totalizertype',false) then ResultVal := ResultDescription; //  за смену (FTPR_TT_DAY) 

    VarArr[0] := 0;
    VarArr[1] := 2;
    VarArr[2] := '';
    InvokeOLEMethod(VarDevice,'GetTotalizer',VarArr,[true,true,true]); if not cash_checkError('cash_registersreport:gettotalizer',false) then ResultVal := ResultDescription;
    //VarDevice.GetTotalizer(0,2,ResultVal); if not cash_checkError(false) then ResultVal := ResultDescription; 
    ResultRegisterListNames.add('Сумма продаж');  ResultRegisterListValues.add(asstring(VarArr[2]));

    VarArr[0] := 0;
    VarArr[1] := 8;
    VarArr[2] := '';
    InvokeOLEMethod(VarDevice,'GetTotalizer',VarArr,[true,true,true]); if not cash_checkError('cash_registersreport:gettotalizer-1',false) then ResultVal := ResultDescription;
    //VarDevice.GetTotalizer(0,8,ResultVal); if not cash_checkError(false) then ResultVal := ResultDescription; 
    ResultRegisterListNames.add('Сумма возвратов');  ResultRegisterListValues.add(asstring(VarArr[2]));


    progressbarpos := 50;

    BoolVal := VarDevice.DayOpened; if not cash_checkError('cash_registersreport:dayopened',false) then ResultVal := ResultDescription else ResultVal := ifthen_string(BoolVAl,'открыт','закрыт'); 
    ResultRegisterListNames.add('Фискальный день');  ResultRegisterListValues.add(ResultVal);

    BoolVal := VarDevice.Claimed; if not cash_checkError('cash_registersreport:claimed',false) then ResultVal := ResultDescription else ResultVal := ifthen_string(BoolVAl,'да','нет'); 
    ResultRegisterListNames.add('Регистратор подключен');  ResultRegisterListValues.add(ResultVal);

    ResultVal := VarDevice.DeviceDescription; if not cash_checkError('cash_registersreport:devicedescription',false) then ResultVal := ResultDescription;
    ResultRegisterListNames.add('Описание ФР');  ResultRegisterListValues.add(ResultVal);

    ResultVal := VarDevice.DeviceName; if not cash_checkError('cash_registersreport:devicename',false) then ResultVal := ResultDescription;
    ResultRegisterListNames.add('Краткое название ФР');  ResultRegisterListValues.add(ResultVal);

    ResultVal := VarDevice.ServiceObjectDescription; if not cash_checkError('cash_registersreport:serviceobjectdescription',false) then ResultVal := ResultDescription;
    ResultRegisterListNames.add('Описание объекта драйвера');  ResultRegisterListValues.add(ResultVal);

    VarArr[0] := 9;
    VarArr[1] := 0;
    VarArr[2] := '';  
    InvokeOLEMethod(VarDevice,'GetData',VarArr,[true,true,true]); Cash_checkError('cash_registersreport:getdata(9:0)');
    //VarDevice.GetData(9,IntVal,ResultVAl); if not cash_checkError(false) then ResultVal := ResultDescription;
    ResultRegisterListNames.add('Заводской номер ККМ:');  ResultRegisterListValues.add(asstring(VarArr[2]));

    
    VarArr[0] := 7;
    VarArr[1] := 0;
    VarArr[2] := '';  
    InvokeOLEMethod(VarDevice,'GetData',VarArr,[true,true,true]); Cash_checkError('cash_registersreport:getdata(7:0)');
    //VarDevice.GetData(7,IntVal,ResultVal); if not cash_checkError(false) then ResultVal := ResultDescription;
    ResultRegisterListNames.add('Номер последней закрытой сессии:');  ResultRegisterListValues.add(asstring(VarArr[2]));
  
    VarArr[0] := 3;
    VarArr[1] := 0;
    VarArr[2] := '';  
    InvokeOLEMethod(VarDevice,'GetData',VarArr,[true,true,true]); Cash_checkError('cash_registersreport:getdata(3:0)');
    //VarDevice.GetData(3,IntVal,ResultVal); if not cash_checkError(false) then ResultVal := ResultDescription;
    ResultRegisterListNames.add('Номер чека:');  ResultRegisterListValues.add(asstring(VarArr[2]));

    progressbarpos := 50;

    resultval := '';
    VarArr[0] := 177;
    VarArr[1] := 0;
    VarArr[2] := '';  
    InvokeOLEMethod(VarDevice,'DirectIO',VarArr,[true,true,true]); Cash_checkError('cash_registersreport:DirectIO(177)');
    ResultVal := asstring(VarArr[2]);
   //VarDevice.DirectIO(177,0,ResultVal); 
    if not cash_checkError('cash_registersreport:DirectIO(177)',false) then ResultVal := ResultDescription;
    ResultRegisterListNames.add('Дата окончания ресурса:');  ResultRegisterListValues.add(ResultVal);

    resultval := '';
    VarArr[0] := 193;
    VarArr[1] := 0;
    VarArr[2] := '';  
    InvokeOLEMethod(VarDevice,'DirectIO',VarArr,[true,true,true]); Cash_checkError('cash_registersreport:DirectIO(193)');
    ResultVal := asstring(VarArr[2]);
    //VarDevice.DirectIO(193,0,ResultVal); 
    if not cash_checkError('cash_registersreport:DirectIO(193)',false) then ResultVal := ResultDescription;
    ResultRegisterListNames.add('Параметры ОФД:');  ResultRegisterListValues.add(ResultVal);

    resultval := '';
    VarArr[0] := 195;
    VarArr[1] := 0;
    VarArr[2] := '';  
    InvokeOLEMethod(VarDevice,'DirectIO',VarArr,[true,true,true]); Cash_checkError('cash_registersreport:DirectIO(195)');
    ResultVal := asstring(VarArr[2]);
    //VarDevice.DirectIO(195,0,ResultVal); if not cash_checkError(false) then ResultVal := ResultDescription;
    ResultRegisterListNames.add('Дата/время открытия последней смены:');  ResultRegisterListValues.add(ResultVal);

    resultval := '';
    VarArr[0] := 204;
    VarArr[1] := 0;
    VarArr[2] := '';  
    InvokeOLEMethod(VarDevice,'DirectIO',VarArr,[true,true,true]); Cash_checkError('cash_registersreport:DirectIO(204)');
    ResultVal := asstring(VarArr[2]);
    //VarDevice.DirectIO(204,0,ResultVal); 
    if not cash_checkError('cash_registersreport:DirectIO(204)',false) then ResultVal := ResultDescription;
    ResultRegisterListNames.add('Версия драйвера OPOS:');  ResultRegisterListValues.add(ResultVal);
    

  finally  
    setlength(VarArr,0);
    if NeedConnect then Cash_disconnect;
    list.free;
    VarDevice := null; // Освободить VarDevice. Объект драйвера должен выгрузиться
    free
  end;
end;

procedure Cash_ShowProperties;
begin
  if CASH_SHOWPROPERTIES_COMMANDLINE>'' then DoCommandLine(CASH_SHOWPROPERTIES_COMMANDLINE)
  else begin
    CreateHIntE('Не реализовано в данной версии драйвера');
    exit;
    //Cash_CheckDevice;
    //VarDevice.CheckHealth(3);
    //cash_checkerror;          
    //в драйвере Атол-OPOS: "Для вызова страницы свойств драйвера следует выполнять метод CheckHealth с параметром Level, равным 3 (OPOS_CH_INTERACTIVE)".
    // если не работает, то вызвать просто RaiseException('Вывод свойств не реализован в этом драйвере');
  end;
end;


procedure Cash_PrintText(Strings :tStrings;
                         NeedConnect :boolean = true;
                         RepeatPrint :integer =1; // число повторов слипа (если поступил одинарный слип, то повторим его печать)
                         CutAfter :boolean=false; // обрезать ли слип после печати
                         CutLine :boolean =false; // Надо ли резать (некоторые аппараты не поддерживают. по-умолчанию - true
                         CutLineText :string =''; // линия отреза содержит
                         CutLineLength :integer = 0; // сколько надо пропустить, чтобы линия отреза была там, где надо
                         CutLineSleep :integer = 0 // на месте отреза сделать слип, в секундах. Требуется, если аппарат не имеет отрезки, тогда эти секунды дадут возможность оторвать руками
                         );
var 
    i :integer;
    k :integer;
    incCutLine :integer;
    NeedCutLine :boolean; // Надо резать, после того, как IncCutLIne станет равной CardsPrintCutLineLength
begin
  if needConnect then Cash_Connect;
  try
  { тест 
    ShowMessage(Strings.text+#13+
                'NeedConnect:'+asstring(NeedConnect)+#13+
                'RepeatPrint:'+asstring(RepeatPrint)+#13+
                'CutAfter:'+asstring(CutAfter)+#13+  
                'CutLine:'+asstring(CutLine)+#13+  
                'CutLineText:'+asstring(CutLineText)+#13+  
                'CutLineLength:'+asstring(CutLineLength)+#13+  
                'CutLineSleep:'+asstring(CutLineSleep));
    exit;
  }
  
    if trim(strings.text) ='' then exit;
    Cash_SaveLog('PrintTextOPOS');
    Cash_SaveLog(strings.text);
    Cash_SaveLog('PrintTextOPOS:2');

      //PrintNormal(Station: Integer; const Data: WideString): Integer;
      //Печать строки в нефискальном документе
      //Station – не используется
      //Data – строка для печати. Допустимо использование ESC-
      //последовательности для печати различными шрифтами или печати
      //штрих-кода.

      //BeginNonFiscal;
      //PrintNormal(FPTR_S_RECEIPT, “Строка 1”);
      //PrintNormal(FPTR_S_RECEIPT, “Строка 2”);
      //PrintNormal(FPTR_S_RECEIPT, “Строка X”);
      //EndNonFiscal;

    VarDevice.BeginNonFiscal; cash_checkerror('CashPrintText:beginnonfiscal');
    
  
    for k := 1 to max(1,RepeatPrint) do
    begin
      NeedCutLine := false;
      incCutLine := 0;
      Cash_SaveLog('PrintTextOPOS:3');

            
            
      for i := 0 to strings.count-1 do
      begin
        Cash_SaveLog('PrintTextOPOS:3.1.'+inttostr(i));
        VarDevice.PrintNormal(0,strings[i]);
        cash_checkerror('CashPrintText:printnormal');
        
        if pos(CutLineText,strings[i])=1
        then begin
          NeedCutLine := true;
          IncCutLine := 1;
        end
        else if NeedCutLIne then
        begin
          inc(IncCutLine);
          if IncCutLine>=CutLineLength
          then begin
            if CutLine then
            begin
              Cash_SaveLog('PrintTextOPOS:3.3.'+inttostr(i));
              // в документации по драйверу вики-принт про отрезку ничего нет пока
              //VarDevice.SetParam(VarDevice.LIBFPTR_PARAM_CUT_TYPE,VarDevice.LIBFPTR_CT_PART); Cash_checkerror;
              //VarDevice.Cut; cash_checkerror;// отрезка
              
            end;
            if CutLineSleep>0 then Sleep(CutLineSleep*1000);
            NeedCutLine := false;
            IncCutLine := 0;
          end;
        end;
      end;
  
      Cash_SaveLog('PrintTextOPOS:4');
      if CutAfter and NeedCutLine and (IncCutLine<CutLineLength)
      then begin
        for i := 1 to (CutLineLength-IncCutLine) do
        begin
          Cash_SaveLog('PrintTextOPOS:4.1.'+inttostr(i));
          VarDevice.PrintNormal(0,'');  //VarDevice.Caption := ''; 
          cash_checkerror('CashPrintText:printnormal(1)');
        end;
        if CutLine then
        begin
        end;
        if CutLineSleep>0 then Sleep(CutLineSleep*1000);
      end;
    end;
    Cash_SaveLog('PrintTextOPOS:5');
    // ? как тут это делать? vardevice.printCliche; //VarDevice.PrintHeader;   
    Cash_SaveLog('PrintTextOPOS:6');
    if CutLine then
    begin
      //VarDevice.partialcut;   cash_checkerror;
    end;

    if CutLineSleep>0 then Sleep(CutLineSleep*1000);

    VarDevice.EndNonFiscal;
    
    Cash_SaveLog('PrintTextOPOS:exit');
  finally  
    if NeedConnect then Cash_disconnect;
  end;
end;


{var resultval :string;
    IntVAl :integer;
    VarVal :string;
    VarArr :array of Variant;}
begin
  // инициализация модуля. выполняется перед выполнением каких-либо функций и для установки параметров по-умолчанию
  Cash_InitVars;
  cash_checkdevice;
  //cash_checkerror;
  // конец инициализации

  //showmessage(VarDevice.PrinterState);
  //cash_showproperties;
  
    //cash_disconnect;
    //cash_connect;
{  
    VarDevice := CreateOleObject(CASH_DeviceName);
     VarDevice.Open('Pirit');

    showmessage('devicenabled='+asstring(vardevice.deviceenabled));
    VarDevice.GetTotalizer(0,2,VarVal);
    showmessage(VarVal);
    showmessage(asstring(VarVal));
    showmessage(asstring(length(varval)));
    cash_checkerror;
    exit; 

    VarDevice.GetTotalizer(0,8,ResultVal); 
    showmessage(resultval);
    cash_checkError; 

    setlength(VarArr,3);
    VarArr[0] := 9;
    VarArr[1] := 0;
    VarArr[2] := '';  
    InvokeOLEMethod(VarDevice,'GetData',VarArr,[true,true,true]); Cash_checkError;
    //VarDevice.GetData(9,IntVal,ResultVAl); 
    showmessage(resultval);
    
    cash_checkError;
}    
{  
   // тест
  //cash_getnumbers; 
  //CreateHint('Результат : '+inttostr(ResultCode)+': '+ResultDescription);
  //createhint(asstring(resultserialnumber));
  //createhint(asstring(resultsessionnumber));
  //createhint(asstring(resultdocumentnumber));
   
   Dataset := TClientDAtaset.create(selfscript);
   with tclientdataset(Dataset) do
   begin
     fielddefs.clear;
     fielddefs.add(FName,ftstring,100,false);
     fielddefs.add(FQuantity,ftfloat,0,false);
     fielddefs.add(FPrice,ftcurrency,0,false);
     fielddefs.add(FSUMMARY,ftfloat,0,false);
     fielddefs.add(FPriceOut,ftcurrency,0,false);
     fielddefs.add(FSection,ftinteger,0,false);
     fielddefs.add(FNDS,ftinteger,0,false);
     createdataset;
     logchanges := false;

     append;
     fieldbyname(FName).asstring := 'Товар1 Товар1 Товар1 Товар1 Товар1 Товар1 Товар1 Товар1 Товар1 Товар1 Товар1 Товар1 Товар1 Товар1';
     fieldbyname(FQuantity).asfloat := 2;
     fieldbyname(FPrice).asfloat := 1.30;
     fieldbyname(FPriceOut).asfloat := 1.50;
     fieldbyname(FSummary).asfloat := fieldbyname(fquantity).asfloat * fieldbyname(fprice).asfloat;
     fieldbyname(FNDS).asinteger := 0;
     post;
     append;
     fieldbyname(FName).asstring := 'Товар2';
     fieldbyname(FQuantity).asfloat := 2;
     fieldbyname(FPrice).asfloat := 0.90;
     fieldbyname(FPriceOut).asfloat := 1.00;
     fieldbyname(FSummary).asfloat := fieldbyname(fquantity).asfloat * fieldbyname(fprice).asfloat;
     fieldbyname(FNDS).asinteger := 10;
     post;
     
     append;
     fieldbyname(FName).asstring := 'Товар3';
     fieldbyname(FQuantity).asfloat := 2;
     fieldbyname(FPrice).asfloat := 1.10;
     fieldbyname(FPriceOut).asfloat := 1.20;
     fieldbyname(FSummary).asfloat := fieldbyname(fquantity).asfloat * fieldbyname(fprice).asfloat;
     fieldbyname(FNDS).asinteger := 18;
     post;
     
     
   end;

  CASH_SHOWNDS := true;
   
  PaySummary := 100;
  EMAIL := 'borzov@solr.ru';
  IsReturn := false; 
  //CASH_UserPassword := '30';
  //CASH_SysPassword := '30'; 

  VarActionNal := true;
  VarActionBN := false;
  VarActionKredit := false; 
  VarActionSert := false;

  VarActionDiskont := false;
  VarActionDiskont1 := false;
  VarActionPension := false;
  VarActionVIP := false;
  VarActionInsurance := false;
  
  cash_execute();
  CreateHint('Результат : '+inttostr(ResultCode)+': '+ResultDescription);
}

(*
https://help.dreamkas.ru/hc/ru/community/posts/360008199718-%D0%9C%D0%B0%D1%80%D0%BA%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0-%D0%BB%D0%B5%D0%BA%D0%B0%D1%80%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D1%85-%D1%81%D1%80%D0%B5%D0%B4%D1%81%D1%82%D0%B2

Приведу просто код, думаю разобраться в нем не составит большого труда.

data := '$44$4D'; //префикс

gtin := IntToHex(StrToInt64(MarkList[i].gtin), 6); // MarkList[i].gtin это число
gtin := LeftStr('000000000000', 12 - Length(gtin)) + gtin; //Добавляем лидирующий 0 если это необходимо

sn := MarkList[i].sn; // MarkList[i].sn это серийный номер - текст

for n := 1 to 6 do
begin
data := data + '$' + gtin[n * 2 - 1] + gtin[n * 2];
end;
for n := 1 to Length(sn) do
begin
data := data + '$' + IntToHex(ord(sn[n]), 1);
end;

if DSCheck.Fields[18{Ratio}].AsInteger = 1 then // это если товар фасованный или нет, Ratio это количество дробных частей
data := data + ';mdlp'
else
data := data + ';mdlp' + IntToStr(MarkList[i].Qnt) + '/' + DSCheck.Fields[18{Ratio}].AsString + '&';

err := Printer.DirectIO(212, 0, data);

data := 'mdlp';
err := Printer.DirectIO(216, 0, data);

data := 'sid' + mdlpSID^ + '&'; // mdlpSID^ это идентификатор места деятельности
err := Printer.DirectIO(216, 1, data);
*)  

end.
