Понедельник, 13.05.2024, 13:27

Главная
Примочки к 1С
Ссылки на 1С ресурсы
  • Специалист
  • Клуб професионалов 1С
  • Статистика
    Главная » FAQ [ Добавить вопрос ]


    Иcпoльзoвaниe пepeключaтeлeй имeeт pяд ocoбeннocтeй, oтличaющиx иx oт paбoты c дpyгими элeмeнтaми yпpaвлeния. Этo oбъяcняeтcя тeм, чтo пepeключaтeль являeтcя, c oднoй cтopoны, нaбopoм oтдeльныx элeмeнтoв yпpaвлeния, a c дpyгoй cтopoны,eдиным цeлым. Hecкoлькo вcтaвлeнныx пepeключaтeлeй дoлжны oбpaзoвaть лoгичecкyю гpyппy, в кoтopoй в кaждый мoмeнт вpeмeни мoжeт быть выбpaн тoлькo oдин элeмeнт yпpaвлeния.
    Пpeждe вceгo, cлeдyeт yчитывaть, чтo в гpyппe пepeключaтeлeй пepвый являeтcя "глaвным". Пpи этoм eмy oбязaтeльнo нyжнo пocтaвить пpизнaк "Пepвый в гpyппe". B этoм cлyчae eгo знaчeниe бyдeт измeнятьcя пpи выбope oднoгo из элeмeнтoв гpyппы. Знaчeниeм бyдeт являтьcя пopядкoвый нoмep выбpaннoгo в дaнный мoмeнт пepeключaтeля из дaннoй гpyппы. К знaчeнию мoжнo oбpaщaтьcя в мoдyлe пo идeнтификaтopy зaдaннoмy для пepeключaтeля имeющeгo пpизнaк "Пepвый в гpyппe". Для ocтaльныx пepeключaтeлeй знaчeниe фaктичecки нe cyщecтвyeт.
    Для пpaвильнoй paбoты пepeключaтeля нeoбxoдимo чтoбы вce пepeключaтeли в гpyппe pacпoлaгaлиcь пoдpяд в пopядкe oбxoдa. Кpoмe тoгo, нeoбxoдимo, чтoбы пepeключaтeль, имeющий пpизнaк "Пepвый в гpyппe", pacпoлaгaлcя в пopядкe oбxoдa cpeди элeмeнтoв гpyппы пepвым. Ecли мeждy пepeключaтeлями в пopядкe oбxoдa бyдyт pacпoлaгaтьcя дpyгиe элeмeнты yпpaвлeния или пepeключaтeль c пpизнaкoм "Пepвый в гpyппe" нe бyдeт cтoять пepвым, тo пepeключaтeли нe бyдyт oбpaзoвывaть лoгичecкyю гpyппy и, cooтвeтcтвeннo, нe бyдyт paбoтaть пpaвильнo.
    He cмoтpя нa тo, чтo дpyгиe пepeключaтeли кpoмe пepвoгo нe имeют знaчeния, для ниx мoжeт быть yкaзaн идeнтификaтop. Этo пoзвoляeт имeть дocтyп к oтдeльным пepeключaтeлям гpyппы чepeз oбъeкт "Фopмa" для yпpaвлeния дocтyпнocтью и дpyгими cвoиcтвaми элeмeнтa yпpaвлeния.


    Для вычисления мат.функций можно воспользоваться VBScript. Доступны следущие:
    Abs - абсолютное значение
    Atn - арктангенс
    Cos - косинус
    Exp - экспонента (число е в степени)
    Fix - отбрасывает дробную часть числа
    Int - целая часть числа (в 1с уже есть)
    Log - натуральный логарифм (тоже есть)
    Rnd - случайное число
    Sgn - знак числа
    Sin - синус
    Sqr - квадратный корень
    Tan - тангенс
    Hex - перевод из 10-тичной в 16-ричную
    Oct - в 8-ричную 
    Пример вызова:
    // синус
    function sin(value,sc=0)
      try 
      if sc=0 then
      sc=createObject("MSScriptControl.ScriptControl");
      endif;
      sc.language="VBscript";
      except
      return getEmptyValue();
      endtry;
      return sc.eval("sin("+value+")");

    endFunction

    Возведение в степень ( value1^value2) можно реализовать так:
    sc.eval(""+value1+"^"+value2);


    // Присвоить значение переменной по ее имени.
    // в глоб.модуль:
    Функция Присвоить(Чему,Что) Экспорт
          Чему = Что;
          Возврат "";
    КонецФункции
    // варианты вызова:
    // Шаблон("[Присвоить("+ИмяПеременной+",ПеременнаяСоЗначением)]");
    // Шаблон("[Присвоить("+ИмяПеременной+","+ИмяПеременнойСоЗначением+")]");
    // Пример: пусть у нас на форме есть 5 числовых реквизитов К1, К2, ... К5
    Для н=1 по 5 Цикл
      Шаблон("[Присвоить("+("К"+н)+","+(н)+")]");
    КонецЦикла;

    Если в качестве параметров ф-ии Присвоить() могут передаваться ТаблицаЗначений или СписокЗначений, то нужно её изменить на:
    Функция Присвоить(Чему,Что)
      Если Найти(ТипЗначенияСтр(Что),"Значений")>0 Тогда
      Чему=СоздатьОбъект(ТипЗначенияСтр(Что));
      Что.Выгрузить(Чему);
      Иначе
      Чему = Что;
      КонецЕсли;
      Возврат "";
    КонецФункции


    В глобальном модуле доступны все переменные, функции и реквизиты вызывающей формы! 
    При этом даже не требуется передавать Контекст формы!
    Пример: 
    // помещаем в глобальный модуль процедуру
    //_____________________________________________________________________________
    Процедура глТест() Экспорт
      сообщить(Шаблон("[ПеременнаяМодуля]")); // получаем переменную модуля
      сообщить(Шаблон("[РеквизитФормы]")); // получаем переменную модуля формы 
      сообщить(Шаблон("[ФункцияМодуля()]")); // вызываем функцию модуля формы 
    КонецПроцедуры //глТест()  

    // создадим внешнюю обработку
    // не забудьте добавить на форму реквизит "РеквизитФормы" тип "Строка",30
    // текст модуля внешней обработки:
    Перем ПеременнаяМодуля;
    //_____________________________________________________________________________
    Функция ФункцияМодуля()
      Сообщить("Сработала функция модуля");
    КонецФункции 
    //_____________________________________________________________________________
    Процедура Сформировать()
      глТест(); // вызываем глоб процедуру и смотрим ...
    КонецПроцедуры
    //_____________________________________________________________________________
    ПеременнаяМодуля="Это значение ПеременнойМодуля";
    РеквизитФормы="Это значение РеквизитаФормы";


    Если нужно сохранить в файл сообщения, которые выводятся через Сообщить() можно поступить следующим образом:

    В модуле делаем процедуру:

    Процедура Сообщить(Текст,Флаг = "")

         Если Константа.Авто = 1 Тогда

                   ДобавитьТекстВФайл(Текст,Флаг);
         Иначе
               Message(Текст,Флаг);
      КонецЕсли;
    КонецПроцедуры

    Заметьте, что для вызова "родной" процедуры нужно использовать другой язык. Это еще одна хитрость в этом методе.


    Примечание: такое переопределение работает только в локальном контексте и только в той части модуля, которая стоит после переопределения. Хотя можно переопределить и на весь модуль, если воспользоваться объявлением "Далее".


    Процедура ПриРедактированииНовойСтроки()
         Если Товар.Выбран()=1 тогда
         Сообщить("Скопировали строку");
         КонецЕсли;
    КонецПроцедуры

    В запросе, где есть группировка Месяц, после выполнения возвращается Запрос.Месяц, например, = "Июль 02". Чтобы сконвертировать" в формат "01.07.02" воспользуемся недокументированным методом Запрос.ЗначениеГруппировки("Месяц")). Метод вернет дату начала месяца в формате даты, т.е. вида 'дд.мм.гг'.
     Пример:
    //*******************************************
    // НачДата, КонДата - реквизиты формы типа "Дата"
    //*******************************************
    Процедура Сформировать()
      ТЗ = "
      |С НачДата по КонДата;
      |Группировка Месяц Все;";
      Запрос = СОздатьОбъект("Запрос");
      Если Запрос.Выполнить(ТЗ) = 0 Тогда
      Возврат;
      КонецЕсли;
      Пока Запрос.Группировка(1) = 1 Цикл
      Сообщить("Запрос.Месяц = "+Запрос.Месяц);
      Сообщить(" = "+Запрос.ЗначениеГруппировки("Месяц"));
      КонецЦикла;
    КонецПроцедуры
    //*******************************************

    Результат работы такой:
    Запрос.Месяц = Январь 02
    = 01.01.02
    Запрос.Месяц = Февраль 02
    = 01.02.02
    Запрос.Месяц = Март 02
    = 01.03.02
    Запрос.Месяц = Апрель 02
    = 01.04.02
    Запрос.Месяц = Май 02
    = 01.05.02
    Запрос.Месяц = Июнь 02
    = 01.06.02
    Запрос.Месяц = Июль 02
    = 01.07.02
    Запрос.Месяц = Август 02
    = 01.08.02

    Получить список принтеров:
    Процедура
     Сформировать()
         wshNetwork=createObject("WScript.Network");
         oPrinters=wshNetwork.EnumPrinterConnections();
         i=0;
         while i<oPrinters.count()-1 do
              message("Порт "+oPrinters.item(i)+" = "+oPrinters.item(i+1));
              i=i+2;
         enddo;
    КонецПроцедуры



    Получить имя принтера "по умолчанию":

    // Получить имя "принтера по умолчанию":
    Процедура Сформировать()
         scrptCtrl=createobject("MSScriptControl.ScriptControl");
         scrptCtrl.language="vbscript";
         scrptCtrl.addcode("
         |Function GetDefaultPrinter()
         |GetDefaultPrinter=vbNullString
         |Set objWMIService=GetObject(""winmgmts:"" _
         |& ""{impersonationLevel=impersonate}!\\.\root\cimv2"")
         |Set colInstalledPrinters=objWMIService.ExecQuery _
         |(""Select * from Win32_Printer"")
         |For Each objPrinter in colInstalledPrinters
         |If objPrinter.Attributes and 4 Then
         |GetDefaultPrinter=objPrinter.Name
         |Exit For
         |End If
         |Next
         |End Function");
         Сообщить(scrptCtrl.run("GetDefaultPrinter"));
    КонецПроцедуры

    Проблема: "Емкость диска 40Гб свободно 24Гб выдает -17,14Гб."
    ИМХО это ошибка в движке. Не получить нормальное значение простым способом. Какие-то у них корявки внутри с преобразованием UINT в long и обратно. При возврате из функции теряется один разряд - самый старший. Так что восстановить нормальное значение не получится. Единственное, для чего пригодна эта функция - проследить, что на диске есть еще как минимум 4 Гб. свободного места.


    Для корректного определения свободного места на диске можно воспользоваться VBScript:
    fso=createObject("scripting.fileSystemObject");
    message(fso.getDrive(диск).freeSpace);

    Таблица.Записать(путь+"отчет.xls","XLS");
    ЗагрузитьВнешнююКомпоненту(
    "V7Plus.dll");
    Почта=СоздатьОбъект("AddIn.V7Mail");
    Почта.Подключиться();
    Почта.НовоеСообщение();
    Почта.ДобавитьАдрес("general@nalog.ru");
    Почта.ДобавитьФайл(путь+"отчет.xls");
    Почта.Послать();
    Почта.Отключиться();

    Процедура ПереброскаВПочтовика()
         myOlApp = CreateObject("Outlook.Application");
         myItem = myOlApp.CreateItem(0); //olMailItem=0
         //Адрес
         myRecipient = myItem.Recipients.Add("axm2000@mail.ru");
         myRecipient.Type=1;
         //Тема
         myItem.Subject = "Счет № "+НомерДок+" от "+ДатаДок;
         //Тело
         myItem.Body ="Счет находится в прикрепленном файле"
         //Аттачменты
         myAttachments = myItem.Attachments();
         myAttachments.Add( "C:\\1cFiletmp.xls", 1, 1, "Счет № "+НомерДок+" от "+ДатаДок);
         ФС.УдалитьФайл("C:\\1cFiletmp.xls");
         //myItem.Display();
         myItem.Send();
    КонецПроцедуры

    Предварительно напиши часть кода для печати счета в файл (догадайся какой). Все. Если есть соединение то отправит сразу, нет поместит в исходящие.
    Это для MS Outlook. Outlook Express механизма ОЛЕ не поддерживает. Никакой внешней библиотеки не надо, по крайне мере для MS Outlook.


    Как при отправке письма через Outlook указать обратный адрес:

    OLE_Outlook=СоздатьОбъект("Outlook.Application");
    Mail = OLE_Outlook.CreateItem(0);
    .....
    Mail.ReplyRecipientNames="kto-to@gte-to.tam" //обратный адрес

    Согласно документации, процесс инициализации РБД - необратимый, но иногда возникает потребность удалить всякое упоминание о том, что база данных когда-то была распределенной.Что для этого необходимо сделать:
    В первую очередь, в файле 1SSYSTEM.DBF вручную очистить 3-х символьное поле DBSIGN (содержащее код ИБ), и, в принципе, этого достаточно.
    Для возврата ИБ в первозданное состояние нужно дополнительно:
    Удалить файлы 1SDBSET.DBF, 1SDWNLDS.DBF, 1SUPDTS.DBF и соответствующие индексные файлы (.CDX) .
    В файле 1SSYSTEM.DBF обнулить 36-ти символьную строку DBSETUUID: 00000000-0000-0000- 0000-000000000000.

    "В таблице _1SDBSET есть поле DBSTATUS, оно может принимать следующие значения:
    P - Центральная
    M - Текущая
    N - Периферийная (непроинициирована)
    C - Периферийная
    В периферийной базе меняешь эту таблицу соответствущим образом и все Ок."

    1) Делаем выгрузку из ПБ;
    2) Изменяем конфу в ЦБ и пытаемся загрузить выгрузку -> получаем: "Изменения конфигурации не загружались в ИБ из которой пришел файл переноса";
    3) Делаем выгрузку из ЦБ;
    4) Просмотрщиком открываем файл 1Cv77Dld.id (выгрузки ЦБ) и наблюдаем строчку похожую на:
        {"Download
        ID",B32CA7C5-4FC6-431D-ABF8-2A5E6F7658F9,"KL",3BB40AD2-5DDA-4E39-83BB-ADB5C65A081F,"ЦБ",8BAA3284-2B8C-450E-868F-781896A54BC4,"9|KL"}
        (ага! запоминаем цифру 9);
    5) Распаковываем файл 1Cv77Chs.dat, прибывший из ПБ;
    6) Ищем строчку похожую на {"8|ЦБ"}}, (у меня 4-я сверху);
    7) Меняем 8 на 9 и запаковываем обратно;
    8) Теперь в ЦБ все замечательно загружается!

    Подводя итоги:
    1) Номер выгрузки из ЦБ в файле 1Cv77Dld.id всегда должен соответствовать
    номеру выгрузки из ПБ в файле 1Cv77Chs.dat.
    2) ВНИМАНИЕ!!! Такой фокус не рекомендуется проделывать при структурных изменениях конфигурации!



    Дополнения:
    ...
    3) Делаем выгрузку из ЦБ .Обязательно загружаем этот архив на ПБ, иначе
    часть документов из ЦБ не выгрузится в ПБ;
    ...
    6) Ищем строчку похожую на {"8|ЦБ"}}, (за ключевым словом
    Acknowledgements, у меня 4-я сверху);

    Схема данных и принципы работы системы.

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

    Основная таблица, содержащая описаня баз данных, участвующих в обмене - _1SDBSET. Ниже приведен перечень ее основных полей.
    DBSIGNКод базы данных
    DBDESCRОписание
    DBSTATUSСтатус базы. M-центральная, C-периферийная
    DBUUIDGUID базы. Уникальный идентификатор базы, присваемый при создании
    В принципе, достаточно. Остальные поля настроечные - в них хранятся имена файлов обмена, признаки установленного автообмена, адреса и все такое. Всеми этими параметрами можно спокойно управлять с конфигуратора. Следует отметить, что в центральной базе в этой таблице хранится перечень всех баз данных информационного пространства, в периферийной - только себя и центральной.

    Следующая таблица, играющая немаловажную роль в работе механизма - _1SSYSTEM Это таблица, в которой хранятся данные об общих настройках базы, таких как точка актуальности, дата рассчитанных бухгатерских итогов, etc. В частности, УРБД касаются такие поля:
    DBSIGNКод этой базы
    DBSETUUIDGUID информационного пространства
    Вот и все, касаемо настройки базы данных. Удалите данные из таблицы _1SDBSET - база станет центральной. Удалите поле DBSIGN в таблице _1SSYSTEM, а поле DBSETUUID забейте ноликами вместо чисел - она станет еще и нераспределенной (вопреки предупреждению, выдаваемому системой при распределении базы данных). Манипулируя этими полями, с распеределенным информационным пространством можно делать практически что угодно - переподчинить базу другой базе, переподчинить базу другому информационному пространству.

    Таблица, в которой буферизируются изменения - _1SUPDTS. Очень полезная таблица, применять ее можно в задачах, лежащих за областью применения УРБД. Например, одно из моих решений в области аналитических систем для пополнения своей базы данных из базы 1С пользуется именно этой таблицей. В базе-источнике 1С заведена фиктивная периферийная база данных, изменения, которые отражены в этой таблице, обрабатываются уже моим механизмом, для которого важно иметь информацию об измененных с последнего импорта данных объектах.
    DBSIGNКод базы, для синхронизации с которой записывется изменение
    TYPEIDID типа объекта
    OBJIDID объекта
    DELETEDПризнак физического удаления объекта
    DWNLDIDID сессии УРБД
    При любом изменении объекта система добавляет в таблицу запись для каждой базы данных, в которую должны эти изменения отправиться. Если объект физически удален, в поле DELETED пишется флажок D. В поле TYPEID записывается идентификатор типа объекта (это число, которым идентифицирован объект в конфигураторе), в поле OBJID - идентификатор самого объекта. При выполнении сеанса УБРД все записи из буфера выгружаются в текстовый файл в определенном формате. В поле DWNLDID записывается идентификатор сессии для записей, в которых этот идентификатор не проставлен. Таким образом, в каждую выгрузку уходят все записи, подтверждение приема которых не поступало. При получении подтверждения сесии, идентификатор которой записан в это поле, или любой другой последующей сессии, запись из буфера удаляется.

    Последняя, и самая неинтересная таблица - _1SDWNLDS. В ней собрана информация о незакрытых (неподтвержденных) сессиях обмена УРБД в разрезе баз данных. При приходе первого же подтверждения приема данных записи обо всех сесиях, подтвержденной и предыдущих, удаляются.

    Структура пакета обмена данными.

    Пакет обмена данными - zip-архив, в котором содержатся 2 или 3(если изменялась конфигурация в центральной базе) файла. Файл 1Cv77Dld.id несет в себе информацию о сессии обмена. Содержит единственную строчку
    {"Download ID",A37F7532-5939-42F1-BEC8-3FEABB70A128,"EKC",CE395095-F690-42B0-B954-0B99208FC947,"ECM",D76EE5A2-B06E-4E03-8B05-F81138819F59,"2184|EKC"}
    A37F7532-5939-42F1-BEC8-3FEABB70A128GUID сессии обмена данными
    "EKC"код базы-отправителя
    CE395095-F690-42B0-B954-0B99208FC947GUID базы-отправителя
    "ECM"код базы-получателя
    D76EE5A2-B06E-4E03-8B05-F81138819F59GUID базы-получателя
    "2184|EKC"ID сессии обмена данными
    Эта же информация повторяется в файле с данными. Назначение этого файла для меня до сих пор загадка, видимо он служит для того, чтобы при попытке обработать старый файл быстро, не открывая большого файла с данными, выругаться, что файл уже принимался системой. Файл 1Cv77Chs.dat несет в себе информацию, которая подлежит синхронизации. Состоит из нескольких узлов. В первом узле, без названия, повторяется информация с файла 1Cv77Dld.id. Во втором - "Distributed data" - расшифровывается эта информация. Не знаю зачем, видимо для улучшения читаемости. Важен подузел этого узла Acknowledgements, который несет подтверждение приема предыдущих выгрузок. Далее идет информация об измененных объектах системы, а именно:"Constants", "References","Documents", "Accounts", "Template Operations" (константы, справочники, документы, счета и типовые операции, соотв). После узлов, несущих информацию об измененных объектах, идут узлы с информацией об удаленных "Deleted References","Deleted Documents","Deleted Accounts","Deleted Template Operations" Информация в файле структурирована имеет древовидную структуру, где ветки отделены знаками {}, а листья "". Путем довольно несложных операций по замене/вставке символов легко преобразуется в XML, с которым можно работать из любого языка программирования, либо поддерживающего OLE, либо имеющего собственный анализатор XML.

    Технология работы

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

    Исключительные ситуации и подводные камни

    Коллизии

    Коллизии, или конфликты распределенной обработки данных есть везде, где есть оная обработка. Их можно минимизировать, их можно успешно разрешать, но от них никуда не денешься. Порой они могут приносить достаточно много проблем. В УРБД принят метод разрешения коллизий с приоритетом центральной базы данных. Таким образом, при конфликте изменения, сделанные в периферийной базе, затираются, и принимаются изменения в центральной. Это несколько неправильно, особенно неправильно то, что этот метод прошит жестко и не поддается настройке штатными средствами. На мой взгляд, более логичным было бы сделать приоритетными изменения, сделанные в базе, откуда родом объект, (я молчу про пользовательскую настройку метода разрешения конфликта для каждого типа объектов). В принципе, возможна реализация пользовательского менеджера разрешения конфликтов. Но это уже выходит за рамки статьи.

    Сбои настроек

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

    Надежность автообмена

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

    Ошибки в работе механизма миграции

    Механизм выгрузок/загрузок информации УРБД - самый надежный механизм, который я видел реализованным в 1С. Но иногда и он дает сбои. На моей практике я замечал порядка десяти дублирований данных, и столько же - недоставаний их. Практика у меня обширная, как по времени, так и по количеству распределенных баз, которые случалось организовывать и обслуживать, поэтому приведенную статистику можно считать ничтожной в процентном отношении. Но забывать про нее тоже нельзя. Посему я рекомендую проводить сверку на предмет количества записей в таблицах периферийной и центральной баз данных. В регулярные регламентные работы эту можно не включать, но будет гораздо приятней, если этот сбой заметите Вы, а не разгневанный пользователь.

    Казусы бизнес-логики

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

    Резюме

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


    Известно, что Активизировать() и АктивизироватьОбъект() не работает в процедуре, вызываемой из формулы реквизита,
    а работает только в предопределенных процедурах. Это, непонятно для чего введённое, ограничение можно обойти.
    К примеру, такая задача:

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

    Перем МожноЗакрыть;
    //******************************************************************************
    // 
    Процедура ПриЗакрытии()
         Если МожноЗакрыть=0 тогда
              Активизировать("Код");
              СтатусВозврата(0);
         КонецЕсли;            
         
    МожноЗакрыть=1;
    КонецПроцедуры
     // ПриЗакрытии
    //******************************************************************************
    // вызов этой процедуры осуществляем из формулы нужного реквизита
    Процедура ПриВводеКода()
         Если ПустоеЗначение(Код)=0 Тогда
              // здесь получаем введенный штрихкод
              Код=""; // очищаем поле
              
    МожноЗакрыть=0;
              Форма.Закрыть(0);
         КонецЕсли;
    КонецПроцедуры
     // ПриВводеКода
    //*******************************************
    МожноЗакрыть=1;


    обЭксел = СоздатьОбъект("Excel.Application"); //создаем объект
    НашФайл = обЭксел.Workbooks.Open(СокрЛП(ИмяФайла)); //Открываем файл
    НашЛист = НашФайл.Sheets(1); //Устанавливаем нужный лист
    Знач1 = НашЛист.Cells(1,1); // Считываем значение, здесь: из первой ячейки первой строки
    //Если нужно считать несколько значений то организуем цикл
    Для 
    i = 1 По Знач1 Цикл
         Знач = НашЛист.Cells(i,1).Value;
    КонецЦикла;
    ОбЭксел.WorkBooks.close();
    ОбЭксел.Quit();

    db=CreateObject("ADODB.Connection");
    rs=CreateObject("ADODB.Recordset");
    db.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\temp\temp.xls;Extended Properties=""Excel 8.0;""";
    db.Open();
    rs.ActiveConnection = db;
    rs.CursorType = 3;
    rs.LockType = 2;
    rs.Source = "Select * from [Лист1$]";
    rs.Open();
    Пока
     rs.Eof()=0 Цикл
         Сообщить(rs.Fields(0).Value);
         //обрабатываем Recordset
         rs.MoveNext();
    КонецЦикла;
    rs.Close();
    db.Close();

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

    Эксель = СоздатьОбъект("Excel.Application");
    .....
    Эксель.DisplayAlerts = 0;
    ..... а здесь делаешь че-нить пакостное
    Эксель.DisplayAlerts = 1;

    Формат числа при выгрузке в Excel
    При выгрузке данных в Excel числа типа 9237642437 отображаются 9,23764Е+11. Есть такая фича у Excel: если перед числом (или другим значением) поставить апостроф ('), эксель это понимает как то, что ему подсовывают текст, а апостроф отображаться не будет, т.е. напиши так: '9237642437

    Для того, чтобы открыть файл *.mxl в Excel
    Необходимо внести в реестр следующую информацию:
    REGEDIT4

    [HKEY_CURRENT_USER\Software\Microsoft\Office\10.0\Excel\Converters]
    "Moxel"="1С:Предприятие (*.mxl),...\\bin\\mxl2xl.dll, *.mxl"
    где:
    <...> это пусть к каталогу 1С-Предприятия
    <10.0> это номер версии Экселя - XP. Для 2000 это будет 9.0, 97 - 8.0, 95 - 7.0

    Как определить, когда закончились данные на листе при загрузке данных из Excel
    SpecialCells(11) - "последняя" ячейка
    Например, если нужен номер строки последней ячейки, то LastRow=ExcelApp.Cells(1,1).SpecialCells(11).Row;

    Обработка ошибочных значений
    При обработке через OLE ячеек, содержащих ошибочные значения (#ДЕЛ/0!;#ЗНАЧ!;#ЧИСЛО! и т.п.) 1С зависает при обращении к свойству Value - не помогает даже конструкция Попытка-Исключение. Обойти эту неприятность можно очень просто - проверять перед обращением к Value свойство Text:

    СписокОшибокЁкселя=СоздатьОбъект("СписокЗначений");
    СписокОшибокЁкселя.ДобавитьЗначение("#ПУСТО!");
    СписокОшибокЁкселя.ДобавитьЗначение("#ДЕЛ/0!");
    СписокОшибокЁкселя.ДобавитьЗначение("#ЗНАЧ!");
    СписокОшибокЁкселя.ДобавитьЗначение("#ССЫЛКА!");
    СписокОшибокЁкселя.ДобавитьЗначение("#ИМЯ?");
    СписокОшибокЁкселя.ДобавитьЗначение("#ЧИСЛО!");
    СписокОшибокЁкселя.ДобавитьЗначение("#Н/Д");
    //Для англ. версии
    СписокОшибокЁкселя.ДобавитьЗначение("#NULL!");
    СписокОшибокЁкселя.ДобавитьЗначение("#DIV/0!");
    СписокОшибокЁкселя.ДобавитьЗначение("#VALUE!");
    СписокОшибокЁкселя.ДобавитьЗначение("#REF!");
    СписокОшибокЁкселя.ДобавитьЗначение("#NAME?");
    СписокОшибокЁкселя.ДобавитьЗначение("#NUM!");
    СписокОшибокЁкселя.ДобавитьЗначение("#N/A");    
    //...
    ТекстЯчейки=Ячейка.Text;
    Если СписокОшибокЁкселя.НайтиЗначение(ТекстЯчейки)=0 Тогда
         Сообщить(Ячейка.Value);                
    Иначе
         Сообщить(ТекстЯчейки);                
    КонецЕсли;

    Процедура ACCESS()
         dbe=CreateObject("DAO.DBEngine.36");
         wksp=dbe.Workspaces(0);
         db=0;
         // в ковычках имя фаила с расширением MDB
         Если ФС.СуществуетФайл(КаталогПользователя()+"sr.mdb")=0 Тогда
              // создание файла базы данных с русским порядком сортировки
              db=wksp.CreateDataBase(КаталогПользователя()+"mars_sr.mdb",";LANGID=0x0419;CP=1251;COUNTRY=0");
              // DDL - запрос на создание таблицы
              // описание языка DDL (подмножество SQL) смотрите в хелпе по MS Access
              // не пытайтесь делать это с другими форматами - DDL через DAO поддерживается только для MDB
              //Создадим еще таблицу
              db.Execute("CREATE TABLE   BANKS
              |(REC_IND INTEGER,
              |BANKCODE INTEGER,
              |BANKNAME  TEXT  ,
              |ModifiedFlg BIT,
              |ChangeSeqFlg BIT);"
              );
              // индекс на нужные поля
              db.Execute("CREATE INDEX REC_IND ON BANKS (REC_IND);");
         Иначе
              // просто открываем базу данных в разделенном режиме
              db=wksp.OpenDataBase(КаталогПользователя()+"sr.mdb");
              // и очищаем имеющиеся данные
              db.Execute("DELETE * FROM BANKS;");
        КонецЕсли;
        // rs-это как раз сама таблица, куда мы будем добавлять записи
        rs=db.OpenRecordset("BANKS");
        // добавление записи
        rs.AddNew();
        // присвоение значений полям
        rs.REC_IND            =     "1";
        rs.BANKCODE       =     "34";
        rs.BANKNAME       =    "Инвест";
        rs.ModifiedFlg        =     "1";
        // запомним запись
        rs.Update();
        // и так далее ...
        db.Close();  // закрытие базы данных
    КонецПроцедуры

    Акцесс = СоздатьОбъект("ADODB.CONNECTION");
    Попытка
         СтрокаПодключения="Driver={Microsoft Access Driver (*.mdb)};Dbq=C:\mybase.mdb;Uid=Admin;Pwd=";
         Акцесс.Open(СтрокаПодключения);
    Исключение
         Сообщить("Все плохо:"+ОписаниеОшибки());
         Возврат;
    КонецПопытки;
    Команда = СоздатьОбъект("ADODB.Command");
    Команда.ActiveConnection=Акцесс;
    ТекстСелект = "SELECT * FROM tblCustoms";
    НаборЗаписей = СоздатьОбъект("ADODB.RecordSet");
    Команда.CommandText=ТекстСелект;
    Попытка
         НаборЗаписей=Команда.Execute;
    Исключение
         Сообщить("Обломись:"+ОписаниеОшибки());
    КонецПопытки;
    Попытка
         НаборЗаписей.MoveFirst();
    Исключение
     //нет записей в рекордсете
         НаборЗаписей.Close();
         Возврат;
    КонецПопытки;
    Пока
     НаборЗаписей.EOF()=0 Цикл
         ИНН = НаборЗаписей.Fields("INN").Value;
         //ну и т.д.
         НаборЗаписей.MoveNext();
    КонецЦикла;
    НаборЗаписей.Close();


    Для тех, у кого в базе Акцесс создана рабочая группа (есть файлик mdw), строка подключения будет такая:
    СтрокаПодключения="Driver= Microsoft Access Driver (*.mdb)};systemDB=C:\wg.mdw;Dbq=C:\mybase.mdb;Uid=Admin;Pwd=";

    [ИТС:Методическая поддержка 1С:Предприятия 7.7] >>> [Особенности использования формул полей ввода диалогов форм] - последний абзац:
    > Если в формуле поля ввода выполняется обращение к функции, вызывающее появление _м_о_д_а_л_ь_н_о_г_о_ /* выделено мной */
    > окна (например, вызов функции "Предупреждение" или "Вопрос"), то после закрытия модального окна
    > активным остается то поле, формула которого выполнялась.

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

    Процедура
     ПриОткрытии() СтатусВозврата(0) КонецПроцедуры


    Эффект будет таким же как и выше, т.е. фокус ввода останется на том же самом реквизите.
    Например, для внешней обработки применение этой фичи возможно следующим образом:

    Процедура ПриОткрытии()
         Если форма.параметр="НеИзменятьФокусВвода" тогда
              СтатусВозврата(0); возврат;
         КонецЕсли;
         // дальше "родной" код
    КонецПроцедуры


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

    ОткрытьФормуМодально("Отчет#","НеИзменятьФокусВвода",РасположениеФайла());

    Аналогичным образом можно воспользоваться этим и для других форм.
    Лично мне это было нужно в случае именно ввода НОВЫХ элементов справочников, НОВЫХ документов,
    и ОСОБЕННО(!!!) - при вводе НОВЫХ СТРОК в документе... при проверке значения, введенного в очередную колонку НОВОЙ строки документа.

    _IdToStr(<?>)
    Синтаксис:
    _IdToStr(<Ид>)
    Назначение:
    Возвращает строку - результат преобразования 10-тичного значения в 36-ричное.
    Параметры:
    <Ид> - выражение со значением типа число.

    _StrToID(<?>)
    Синтаксис:
    _StrToID(<Строка>)
    Назначение:
    Возвращает число - результат преобразования 36-ричного значения в 10-тичное.
    Параметры:
    <Строка> - выражение со значением типа строка.

    _GetPerformanceCounter()
    Синтаксис:
    _GetPerformanceCounter()
    Назначение:
    Возвращает число миллисекунд (1000-чных долей секунды) прошедших с момента включения компьютера.
    Замечание:
    Разница между значениями двух замеров позволяет определить количество миллисекунд прошедших между замерами.

    ЗначениеФункции() Англоязычный синоним: FunctionValue(<?>)
    Синтаксис:
    ЗначениеФункции(<НомФункции>)
    Назначение:
    Метод объекта "Запрос". Возвращает значение функции для текущей группировки.
    Параметры:
    <НомФункции> - выражение, содержащее порядковый номер функции в запросе.


    ЗначениеГруппировки(<?>)

    Синтаксис:
    ЗначениеГруппировки()
    Назначение:
    Метод объекта "Запрос". Возвращает значение текущей группировки.
    Параметры:
    <НазваниеГруппировки> - выражение, содержащее название группировки в запросе.



    Атрибут объекта Форма МногострочнаяЧасть. Англоязычный синоним MultyColumn.
    Значение атрибута МногострочнаяЧасть представляет собой ссылку на элемент диалога документа - табличную часть. К атрибуту МногострочнаяЧасть применим метод Видимость().
    Пример:
    Форма.МногострочнаяЧасть.Видимость((Форма.МногострочнаяЧасть.Видимость()+1)%2);

    Атрибут контекста модуля формы КонтекстПодбора. Англоязычный синоним ContextOfPermanentChoose.
    Значение атрибута КонтекстПодбора содержит контекст формы подбора, открытой последним вызовом метода ОткрытьПодбор(). С помощью значения этого контекста можно произвольно манипулировать формой подбора, пока она открыта. Пока форма открыта, тип значения данного параметра равен 100 (см. ТипЗначения), если закрыта -0.
    Пример:
    Если ТипЗначения(КонтекстПодбора)=100 Тогда
    КонтекстПодбора.Форма.Закрыть();
    КонецЕсли;



    В каждой форме можно задать СВОЮ обработку ожидания, которая будет действовать до тех пор, пока открыта форма. Вызов необходимо осуществлять след. образом:
    Форма.ОбработкаОжидания("НужнаяПроцедура",ИнтервалВызова);

    ВНИМАНИЕ: недокументированные возможности могут не работать в более поздних версиях!

    Процедура глМаксимизироватьОкно() Экспорт
        // вызывать в конце процедуры "ПриОткрытии" из модуля формы
        WSHShell = СоздатьОбъект("WScript.Shell");
        WSHShell.SendKeys("%");
        WSHShell.SendKeys("{LEFT}{DOWN}{DOWN}{DOWN}{DOWN}{DOWN}{ENTER}");
    КонецПроцедуры



    Замечание:
    Если у вас этот код не работает, значит не установлен <Windows Scripting Host> или установлена старая версия.
    "Что вам нужно для начала: Первое, что надо сделать - убедиться, что Windows Scripting Host у вас есть.
    В состав W2K он входит изначально.
    Если вы используете Windows 98 или Internet Information Server 4.0,
    или если вы устанавливали Option Pack для Windows NT 4 и для Windows 95, он у вас точно есть.
    В случае Windows 95 все усложняется, и Windows Scripting Host придется скачивать с сайта Microsoft (msdn.microsoft.com/scripting).
    Напишите в командной строке "wscript".
    Если появилось диалоговое окно с надписью, отличной от "File not found", все в порядке."

    Воспользуйтесь след.кодом:
    О=СоздатьОбъект("Операция");
    О.Новая();
    О.ДатаОперации=ДатаОперации; // здесь нужная дата
    О.Содержание=СодержаниеОперации;
    Если
     ДатаГод(О.ДатаОперации) <> ДатаГод(РабочаяДата()) Тогда
         О.Документ.УстановитьНовыйНомер("");
    КонецЕсли;

    Есть такой глюк (фича) у 1С - в Конфигураторе делаешь общий журнал обычным, цепляешь один документ к этому журналу , сохраняешься, после назад возвращаешь. Всё - теперь по <Insert> будет вводиться сразу только тот документ.

    // Заводим общий реквизит документов "ВидДокументаКлиент"
    // В Глобальном модуле. 
    Функция ПолучитьВнутрКод(Клиент) Экспорт
         СписокЗн = СоздатьОбъект("СписокЗначений");
         СписокЗн.ИзСтрокиСРазделителями(ЗначениеВСтрокуВнутр(Клиент));
         ИД = СписокЗн.ПолучитьЗначение(7);
         Возврат Сред(ИД,2,9);
    КонецФункции
    // Вызывается из документов ПриЗаписи()
    Процедура глУстановитьРеквизитыОтбора(Конт) Экспорт
         Конт.ВидДокументаКлиент=Конт.Вид();
         Если глЕстьРеквизитШапки("Клиент",Конт.Вид()) = Да Тогда
              Если ПустоеЗначение(Конт.Клиент) = 0 Тогда
                   Конт.ВидДокументаКлиент = СокрЛП(Конт.Вид())+СокрЛП(ПолучитьВнутрКод(Конт.Клиент));
              КонецЕсли;
         КонецЕсли;
    КонецПроцедуры
    // В журнале
    УстановитьОтбор("ВидДокументаКлиент",ВыбВидДокумента.ПолучитьЗначение(ТС)+СокрЛП(ПолучитьВнутрКод(ВыбКлиент)));


    // Вызов процедуры на примере документа.
    // модуль формы документа
    Процедура ПриОткрытии()
         Если ТипЗначенияСтр(Форма.Параметр)="СписокЗначений" тогда
              СЗ=Форма.Параметр;                        
              
    Команда=СЗ.Получить("Команда");
              Если "Печать"=Команда тогда  
                   
    Печать();  
                   
    СтатусВозврата(0);
                   Возврат;
              КонецЕсли;     
         
    КонецЕсли;     
    КонецПроцедуры
    // Сам вызов (из любого места конфигурации):
    СЗ=СоздатьОбъект("СписокЗначений");
    СЗ.ДобавитьЗначение("Печать","Команда");
    ОткрытьФормуМодально(
    Док.ТекущийДокумент(),СЗ);

    //******************************************************************************
    // Получим представление цвета из реестра
    Функция глПолучитьСистемныйЦвет( псИдентификаторЦвета ) Экспорт
    // ИСПОЛЬЗОВАНИЕ:
    // м_СерыйЦвет = глПолучитьСистемныйЦвет( "Window" );
    //РасшАтрибут.ЦветФона = м_СерыйЦвет;
    //Если ПустоеЗначение(ЗначениеАтрибута) = 1 Тогда
    // РасшАтрибут.ЦветФона = 255; // красный......
    //КонецЕсли;

    // идентификаторы цветов:
    // ActiveBorder - Рамка вокруг активного окна.
    //ButtonHilight, ButtonLight - Выделение трехмерных элементов.
    //AppWorkSpace - Фон окна приложения MDI (приложение, использующее многооконный интерфейс).
    //Background - Рабочий стол.
    //ButtonAlternateFace - Кнопка.
    //ButtonShadow - Тень, "отбрасываемая" кнопкой.
    //ButtonText - Текст надписи на поверхности кнопки.
    //TitleText - Текст заголовка окна, кнопки изменения размера, кнопки полосы просмотра.
    //GrayText - Текст серого цвета.
    //Hilight - Фон выбранного элемента в органе управления.
    //HilightText - Текст для выбранного органа управления.
    //InactiveBorder - Рамка вокруг неактивного окна.
    //InactiveTitle - Заголовок неактивного окна.
    //InactiveTitleText - Текст заголовка для неактивного окна.
    //InfoWindow - Фон элемента подсказка.
    //InfoText - Текст элемента подсказка.
    //Menu - Фон меню.
    //MenuText - Текст меню.
    //Scrollbar - Полоса просмотра.
    //Window - Фон окна.
    //WindowFrame - Рамка окна.
    //WindowText - Текст в окне.
    //ButtonFace,MenuBar,MenuHilight

    // НЕ НАШЕЛ КЛЮЧ РЕЕСТРА
    //BTNHIGHLIGHT - Выбранная кнопка.
    //3DDKSHADOW - Темная тень для трехмерных элементов.
    //3DFACE, BTNFACE - Поверхности трехмерных элементов.
    //3DHILIGHT, 3DHIGHLOIGHT, Выделение трехмерных элементов.
    //3DLIGHT - Свет для трехмерных элементов.
    //COLOR3DSHADOW,BTNHIGHLIGHT - Тень для трехмерных элементов.
    //ACTIVEBORDER - Рамка вокруг активного окна.
    //ACTIVECAPTION - Заголовок активного окна.


    ЗначВозврата = -1;
    СтрокаЦвета = "";
    Попытка
    СтрокаЦвета = глWScript_Shell.RegRead("HKEY_CURRENT_USER\Control Panel\Colors\"+псИдентификаторЦвета);
    Исключение
    Сообщить(ОписаниеОшибки()+"[200612011227]");
    Возврат ЗначВозврата;
    КонецПопытки;
    // Маска цвета будет такой: "212 208 200" (RGB), надо только разложить
    СтрокаЦвета = СтрЗаменить(СтрокаЦвета," ",",");
    СписЦветов = глСтрокаВСписок(СтрокаЦвета);
    Если СписЦветов.РазмерСписка() <> 3 Тогда
    Возврат ЗначВозврата;
    КонецЕсли;
    _Красный = Число(СписЦветов.ПолучитьЗначение(1));
    _Зеленый = Число(СписЦветов.ПолучитьЗначение(2));
    _Синий = Число(СписЦветов.ПолучитьЗначение(3));
    ЗначВозврата = Макс(0,_Синий)*65536+Макс(0,_Зеленый)*256+Макс(0,_Красный);

    Возврат ЗначВозврата;
    КонецФункции // глПолучитьСистемныйЦвет()

    Copyright MyCorp © 2024