ActiveDocument + Сервер 1C 8.3 = Разрыв соединения!




Принцип обмена данными из 1С с сайтом (на MySQL) и выдачи (публикации) этих данных по запросу.
PHP-Скрипт автоматической загрузки данных из файла данных в формате CSV в базу данных сайта работающего на WordPress.

В продолжение моей темы: 1С:Альфа-Авто Автосалон Автосервис: обмен с сайтом.
С помощью данного скрипта можно загружать в автоматическом режиме, по расписанию, данные сервисных книжек (ремонтов авто) из 1С:Альфа-Авто Автосалон Автосервис.
Также можно загружать данные в ручном режиме: для этого делается скрытая страница, где размещается специальная кнопка.
Комментарии размещенные внутри скрипта разъяснят логику и порядок действия.
Комментарии с "/////    echo" использовались для отладки.
Дополнительно создана таблица для журналирования результатов загрузки данных.
Скрипт включает в себя защиту от SQL инъекций (думаю безопасность соблюдена в полной мере).
В кратце:
1. Пишется скрипт, который запускает этот.
2. Создается регламентное задание в WordPress, по которому запускается скрипт из п.1. 
3. Этот скрипт осуществляет проверку на существование файла обмена в папке.
4. Если данные не новые, загрузка не производится.
5. Если данные новые, очищается таблица сервисных книжек.
6. Загружаются новые данные.

Собственно сам скрипт:

<?php // Полная загрузка сервисных книжек, создан 2026-01-05 12:44:55

global $wpdb2;
global $failure;
global $file_hist;

/////  echo '<H2><b>Старт загрузки</b></H2><br>';

$failure=FALSE;
//подключаемся к базе
$wpdb2 = include_once 'connection.php'; ; // подключаемся к MySQL
// если не удалось подключиться, и нужно оборвать PHP с сообщением об этой ошибке
if (!empty($wpdb2->error))
{
/////   echo '<H2><b>Ошибка подключения к БД, завершение.</b></H2><br>';
$failure=TRUE;
wp_die( $wpdb2->error );
}

$m_size_file=0;
$m_mtime_file=0;
$m_comment='';
/////проверка существования файлов выгрузки из 1С
////файл выгрузки сервисных книжек
$file_hist = ABSPATH.'/_1c_alfa_exchange/AA_hist.csv';
if (!file_exists($file_hist))
{
/////   echo '<H2><b>Файл обмена с сервисными книжками не существует.</b></H2><br>';
$m_comment='Файл обмена с сервисными книжками не существует';
$failure=TRUE;
}

/////инициируем таблицу лога
/////если не существует файла то возврат и ничего не делаем
if ($failure){
///включает защиту от SQL инъекций и данные можно передавать как есть, например: $_GET['foo']
/////   echo '<H2><b>Попытка вставить запись в лог таблицу</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>$m_mtime_file,'last_size_upload'=>$m_size_file,'comment'=>$m_comment));
wp_die();
/////    echo '<H2><b>Возврат в начало.</b></H2><br>';
return $failure;
}
/////проверка лога загрузки, что бы не загружать тоже самое
$masiv_data_file=stat($file_hist);   ////передаем в массив свойство файла
$m_size_file=$masiv_data_file[7];    ////получаем размер файла
$m_mtime_file=$masiv_data_file[9];   ////получаем дату модификации файла
////создаем запрос на получение последней удачной загрузки
////выбираем по штампу времени создания (редактирования) файла загрузки AA_hist.csv, $m_mtime_file

/////   echo '<H2><b>Размер файла: '.$m_size_file.'</b></H2><br>';
/////   echo '<H2><b>Штамп времени файла: '.$m_mtime_file.'</b></H2><br>';
/////   echo '<H2><b>Формирование запроса на выборку из лога</b></H2><br>';
////препарируем запрос
$text_zaprosa=$wpdb2->prepare("SELECT * FROM `vin_logs` WHERE `last_mtime_upload` = %s", $m_mtime_file);
$results=$wpdb2->get_results($text_zaprosa);

if ($results)
{   foreach ( $results as $r)
{
////если штамп времени и размер файла совпадают, возврат
if (($r->last_mtime_upload==$m_mtime_file) && ($r->last_size_upload==$m_size_file))
{////echo '<H2><b>Возврат в начало, т.к. найдена запись в логе.</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>$m_mtime_file,'last_size_upload'=>$m_size_file,'comment'=>'Загрузка отменена, новых данных нет, т.к. найдена запись в логе.'));
wp_die();
return $failure;
}
}
}
////если данные новые, пишем в лог запись о начале загрузки
/////echo '<H2><b>Попытка вставить запись о начале загрузки в лог таблицу</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>0, 'last_size_upload'=>$m_size_file, 'comment'=>'Начало загрузки'));

////очищаем таблицу
$clear_tbl_zap=$wpdb2->prepare("TRUNCATE TABLE %s", 'vin_history');
$clear_tbl_zap_repl=str_replace("'","`",$clear_tbl_zap);
$results=$wpdb2->query($clear_tbl_zap_repl);
/////   echo '<H2><b>Очистка таблицы сервисных книжек</b></H2><br>';
if (empty($results))
{
/////   echo '<H2><b>Ошибка очистки таблицы книжек, завершение.</b></H2><br>';
//// если очистка не удалась, возврат
$failure=TRUE;
wp_die();
return $failure;
}

////загружаем данные
$table='vin_history';         // Имя таблицы для импорта
//$file_hist Имя CSV файла, откуда берется информация     // (путь от корня web-сервера)
$delim=';';          // Разделитель полей в CSV файле
$enclosed='"';      // Кавычки для содержимого полей
$escaped='\

57 Comments

  1. sergathome

    Дёргать комом на сервере десктопные программы за гранью добра и зла, имхо.

    Reply ↓
  2. fr13

    А что мешает использовать таблчиный документ как макет, заполнить, записать в поток, из потока в двоичный данные и дальше на клиента?

    Reply ↓
  3. kiruha

    (1)

    А как нужно вызывать Word или Excel на сервере &

    Reply ↓
  4. sergathome

    (3) НИКАК 0=0. На клиенте.

    Reply ↓
  5. kiruha

    (4)

    Странно.

    А вот в SQL Server есть штатные способы взаимодействия. Тот же OPENROWSET/

    Да и во «вражеских» ERP штатно работают.

    Может проблема не в сервере ?

    Reply ↓
  6. sergathome

    (5) Проблема в том, что, запуская десктопную программу в контексте сервера, вы разрушаете контекст сервера с вероятностью отнюдь ненулевой (и производитель об этом прямо предупреждает https://support.microsoft.com/ru-ru/help/257757/considerations-for-server-side-automation-of-office). Нет, можно, конечно пить и из лужи, но вот стоит ли ?

    Reply ↓
  7. SlavaKron

    Мой комментарий напрямую не относится к теме, но лучше писать Word.Quit(0).

    Например следующий код не закрывает процесс Word:

    Word = Новый COMОбъект(«Word.Application»);
    Word.Documents.Add();
    Word.Selection.TypeText(«Привет!»);
    Word.Quit();
    Reply ↓
  8. kare

    (7)Пробовал с «0», результат был тот же. Хотя конечно же лучше делать как Вы указали.

    Reply ↓
  9. kare

    (1) Подскажите тогда каким образом получить такой макет на клиенте? макет держать в двоичных данных и передавать через временное хранилище? тогда уходит редактирование шаблона » на лету» через ActiveDocument что в данном решении не подходило.

    Reply ↓
  10. kare

    (3) Сами объекты можно создавать на клиенте предварительно получив на сервере данные для заполнения. Опять же если только это у Вас динамический шаблон без ActiveDocument.

    Reply ↓
  11. kare

    (2) Имеется ввиду вообще без WORD ? или из макета брать данные для заполнения шаблона на клиенте? тогда вопрос как передать макет из обработки с типом ActivDocument на клиент?

    Reply ↓
  12. fr13

    (11) Сделать макет с типом Табличный документ. На сервере его заполнить. Дальше у ТД есть метод Записать. Записать можно в файл, а можно сразу в потоквпамяти. Так вот запись в поток и указать тип сохранения. Дальше у потока есть метод ЗакрытьИПолучитьДвоичныеДанные. Дальше ДД в хранилище и адрес этого хранилища передать на клиент. Никакой word не нужен. Единственное что, так это версия платформы 8.3.9+, но так как у Вас ЗУП 3.1, то скорей всего это ограничение не для Вас

    Reply ↓
  13. kare

    (12) )) это здорово конечно, но а как же «Эта форма утверждена и она должна выглядеть именно так вплоть до форматирования» ?

    Reply ↓
  14. fr13

    (13) Речь была не про утвержденную форму. Это уже отдельная история. Если сильно захотеть, то можно сделать. В конце концов *docx можно распаковать и через xpath менять что угодно. Опять же, тема отдельного разговора.

    Reply ↓
  15. kare

    (14) согласен с docx, но это совсем другая история )))

    Reply ↓
  16. sergathome

    (9)https://infostart.ru/public/270277/

    ;)) всё новое — испохабленное старое

    Reply ↓
  17. sergathome

    (8) делать лучше так, как чел из 14 года советует ;))

    https://infostart.ru/public/270277/

    у него там, правда, тоже не без ошибок, но, думаю, разберётесь

    Reply ↓
  18. AlX0id

    (3) Ком не нужен.

    1. Переименовываете в .zip

    2. Распаковываете во временную папочку обычным архиватором.

    3. Заменяете в нужных файликах требуемые параметры.

    4. Запаковываете обратно.

    5. Переименовываете в .docx

    6. Профит.

    Reply ↓
  19. kare

    (17)

    Каталог = ПараметрыСеанса.ТекущийПользователь.РабочийКаталог;
    Каталог = ?(Прав(Каталог,1) = «», Каталог, Каталог+»»);
    Макет = Документы.ДоговорыКонтрагентов.ПолучитьМакет(«ActiveDocument»);
    Макет.Записать(ПолноеИмяФайла);

    здесь разве не нужен доступ к рабочему каталогу клиента из сервера? его может и не быть?

    Reply ↓
  20. kare

    (18) изящно, изначально docx должен быть?

    Reply ↓
  21. sergathome

    (19)

    у него там, правда, тоже не без ошибок, но,

    не разобрались, ага. Понятненько.

    Короче, схема такая:

    1. Через ВременноеХранилище передаём макет на клиент.

    2. На клиенте получаем из ВХ и пишем в файл.

    3. Там же, на клиенте, открываем файл как угодно.

    Синхрофазотрон ?

    Reply ↓
  22. kare

    (21) это понятно)) изначально макет есть что?

    (9) ранее писал:

    макет держать в двоичных данных и передавать через временное хранилище? тогда уходит редактирование шаблона » на лету» через ActiveDocument что в данном решении не подходило.
    Reply ↓
  23. sergathome

    (22) Да что ж вы к этому несчастному АктивДокументу привязались. Двоичные данные он и пох. Укажете при записи файла расширение ему .дос и будет он вордом открываться как миленький. Есстно нужно правильный док указать — тот же, какой при помещении в макет был.

    Reply ↓
  24. kare

    (23) если на сервере то в данном случае вызовется объект Word 32x что приведет к описанным в топе проблемам) в том вся соль что макет нужен для редактирования «на лету» если бы все было в двоичных данных топика бы здесь не появилось) Макет(AD) в том виде в котором он используется можно записать только на сервере, конечно без zip и тд и тп. Вариантов реализаций данного функционала масса. Но здесь конкретный случай. Переписывать все ПФ на двоичные данные и заполнение на клиенте это уже совсем другой выход.

    Reply ↓
  25. sergathome

    (24) Вызовется тот, который вы ему скажете. Вы правильно написали всё про 32-64, только на клиенте половины проблем не будет 😉

    Reply ↓
  26. kare

    (25) если двоичные данные .записать(**.doc) как он и был помещен туда на выходе мы получим 32х, про клиент — согласен %)

    Reply ↓
  27. sergathome

    (26) да что же вы ересь-то такую несусветную пишете 🙂 Файл — он просто вордовский файл, он не бывает 32 или 64 и откроется он тем вордом, который у вас на клиенте стоит.

    Reply ↓
  28. kare

    (27)почему же? я про сервер говорю. ActiveDocument вызывает неявно экземпляр COM (x32) и поэтому все дальнейшие манипуляции нужно делать учитывая это особенность.

    Reply ↓
  29. sergathome

    (28)

    //На клиенте получаем макет и заполняем его предварительно полученными данными
    ДвоичныеДанныеМакета = ПолучитьИзВременногоХранилища(ПолучитьМакетСКлиента(«ПисьмоНаОплатуWord»));
    ИмяВрем = ПолучитьИмяВременногоФайла(«.docx»);
    ДвоичныеДанныеМакета.Записать(ИмяВрем);
    Попытка
    Документ = ПолучитьCOMОбъект(ИмяВрем);
    Для Каждого Параметр из СтруктураПараметров.Значение Цикл
    // пример:
    Selection = Документ.Content;
    Selection.Find.Execute(«[«+Параметр.Ключ+»]»,Ложь,Истина,Ложь,,,Истина,,Ложь,Параметр.Значение,2);
    КонецЦикла;
    Документ.Application.Visible = Истина;
    Документ.Application.WindowState = 2;
    Документ.Application.WindowState = 1;
    Документ.Activate();
    Исключение
    …..
    
    &НаСервереБезКонтекста
    Функция ПолучитьМакетСКлиента(Имя)
    Возврат ПоместитьВоВременноеХранилище(Документы.ПисьмоНаОплату.ПолучитьМакет(«ПисьмоНаОплатуWordДвоичныеДанные»));
    КонецФункции

    Показать

    Какие тут особенности ? ПолучитьСОМОбъект, который есть

    Функция  ПолучитьСОМОбъект(ИмяФайла)
    Word = Новый COMОбъект(«Word.Application»);
    Word.Displayalerts = 0;
    ДокументWord = Word.Application.Documents.Open(ИмяФайла);
    Возврат ДокументWord;
    КонецФункции

    ?

    И, главное, даже если оно рухнет, по неважно даже каким причинам, то сервер не завалится.

    Reply ↓
  30. kembrik

    (13) Для этого надо использовать LaTeX

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

    Reply ↓
  31. kare

    (29) здесь двоичные данные записанные НА КЛИЕНТЕ в DOCX, капитан. Зачем мне то что я говорил (9) ?

    макет держать в двоичных данных и передавать через временное хранилище? тогда уходит редактирование шаблона » на лету» через ActiveDocument что в данном решении не подходило.

    Может сделаете тоже самое только на сервере и с doc?без двоичных данных? в чем холивар ?

    Reply ↓
  32. AlX0id

    (20)

    Да, xlsx и docx по сути упакованные xml-ки. А вот doc и xls — нет.

    Reply ↓
  33. kare

    (29) То что данное решение имеет свои плюсы так как выполняется на клиенте это факт. Но я разве ставил под сомнение это?

    Reply ↓
  34. sergathome

    (31) Здесь ваш АктивДокумент, который есть двоичные данные вордового файла, по-факту. Что же вы никак это не вкурите-то///

    АктивДокумент = ДвоичныеДанные(«ИмяФайла.doc»)

    Reply ↓
  35. kare

    (32)ну для этого надо было тогда все шаблоны сохранить из пф потом пересохранить в docx и загрузить обратно в двоичные данные) как новое решение отлично подойдет.

    Reply ↓
  36. kare

    (34) Мне не понятно к чему Вы пишите про двоичные данные и docx если в статье идет про ActiveDocument + &НаСервере ?

    Reply ↓
  37. kare

    (30) Ждем публикацию, интересно будет взглянуть.

    Reply ↓
  38. sergathome

    (36) пытался объяснить как то же самое сделать на клиенте. сдаюсь. не смог.

    Reply ↓
  39. kare

    (38) я правильно понимаю Вас? Вы рекомендуете все шаблоны переписать на клиент ручками пересохранив Макет в doc потом загрузив обратно в макет двоичные данные далее переписать везде код для работы с клиентом и организовать заполнение на клиенте? и это все из за дерганья com на серевере? риали?

    Reply ↓
  40. kare

    (39) я побоялся это описывать @sergathome . Конечно на клиенте в половины случаев все разное.

    Reply ↓
  41. sergathome

    (40) исполните вот это и вкурите уже что такое волшебный ваш ActiveDocument

    &НаКлиенте
    Процедура ОткрытьАктивДокумент()
    //На клиенте получаем макет и заполняем его предварительно полученными данными
    ДвоичныеДанныеМакета = ПолучитьИзВременногоХранилища(ПолучитьМакетСКлиента(«Макет»));
    ИмяВрем = ПолучитьИмяВременногоФайла(«.doc»);
    ДвоичныеДанныеМакета.Записать(ИмяВрем);
    
    Документ = ПолучитьCOMОбъект(ИмяВрем);
    Документ.Application.Visible = Истина;
    Документ.Application.WindowState = 2;
    Документ.Application.WindowState = 1;
    Документ.Activate();
    КонецПроцедуры
    
    &НаКлиенте
    Функция  ПолучитьСОМОбъект(ИмяФайла)
    Word = Новый COMОбъект(«Word.Application»);
    Word.Displayalerts = 0;
    ДокументWord = Word.Application.Documents.Open(ИмяФайла);
    Возврат ДокументWord;
    КонецФункции
    
    &НаСервереБезКонтекста
    Функция ПолучитьМакетСКлиента(Имя)
    // Получаем макет и преобразуем его в двоичные данные
    лМакетОболочка = ПолучитьОбщийМакет(Имя);
    ИмяВрем = ПолучитьИмяВременногоФайла(«.doc»);
    лМакетОболочка.Записать(ИмяВрем);
    лМакет = Новый ДвоичныеДанные(ИмяВрем);
    Возврат ПоместитьВоВременноеХранилище(лМакет);
    КонецФункции
    

    Показать

    Насчет рили будете смеяцо когда сервак завалится при паре сотне юзеров в базах ;)) и будет падать каждые полчаса…

    Reply ↓
  42. sergathome

    (42)

    будете смеяцо когда сервак завалится при паре сотне юзеров в базах ;)) и будет падать каждые полчаса…

    рецепт 18 работает только для docx, на минуточку…

    Reply ↓
  43. kare

    (43)

    -Владимир Владимирович, женщина может стать президентом ?

    -Нет!

    -Почему же?

    -Я же не женщина.

    можно АД сохранить в файл и передать на клиент без участия COM. Только всю логику в каждой обработке заполнения придется переделать под клиент когда она сделана под СЕРВЕР. из за того что бы не менять пару строчек кода и опять про преимущества клиента почитайте (39).

    Reply ↓
  44. kare

    (44) Опечатка (39)

    смеяцо

    153 пользователя полет нормальный.

    Reply ↓
  45. sergathome

    (45) это свежий аргумент. ушёл думать. раньше следующего понедельника не ждите.

    Reply ↓
  46. kare

    (47)будем скучать, возвращайтесь.

    Reply ↓
  47. kare

    (43)

    Насчет рили будете смеяцо когда сервак завалится при паре сотне юзеров в базах ;)) и будет падать каждые полчаса…

    я не смеюсь, серьезно. Просто я не понял : «Переделываем все на клиент !!!!!!» когда проблема решилась и больше не возвращалась.

    Reply ↓
  48. AlX0id

    (39)

    А нахрена вообще тогда ставили задачу по формированию документа в ворде? )

    Я не говорю, что документы ворда суперские и замечательные. Но если поставили такую задачу — можно решить так. И да — это, конечно же, не универсальное решение.

    Reply ↓
  49. kare

    (50) ))) Ворд жил жив и будет жив) актуш)

    Reply ↓
  50. zeegin

    (20) А вы не смотрели как это сделано в БСП 3.0.1?

    > Существенно ускорено и повышена стабильность формирования печатных форм в формате офисных документов. Команды печати в этом формате теперь доступны во всех видах клиентов в операционных системах семейства Linux, Mac OS, а также в веб-клиенте.

    http://downloads.v8.1c.ru/content//SSL/3_0_1_189/change.htm

    Теперь собственно используется Office Open XML который разбирается и заполняется на сервере как zip архив.

    Reply ↓
  51. klinval

    Конечно прикольно когда проблему описанную в моей статье кто-то решает другим способом, а потом этот другой способ тоже пробуют переделать (плюс параллельно обоснуют и докапываются до причины ошибки)…

    Я только одного не понял: а создание папок проблему уже не решает?

    C:WindowsSysWOW64configsystemprofileDesktop

    C:WindowsSystem32configsystemprofileDesktop

    Плюс можно банально отказаться от ActiveDocument и работать с двоичными данными. Пробовали?

    См. тут и тут

    Reply ↓
  52. kare

    (54)

    1)изначально были в системе до появления «сбоя».

    2)Да.

    Reply ↓
  53. kiruha

    а нельзя ли

    вместо

    Word = Новый COMОбъект(«Word.Application»);
    Word.Displayalerts = 0;
    ДокументWord = Word.Application.Documents.Open(ВременныйФайл); /
    

    изменить на

    Word = ПолучитьCOMОбъект(ВременныйФайл);
    

    тк создание COMОбъект в большом количестве приводит к невозможности его создания

    При ПолучитьCOMОбъект такого не происходит, даже когда Новый COMОбъект уже не работает

    Reply ↓
  54. kare

    (56)можно , в коде указанно.

    ДокументWord = Word.Application.Documents.Open(ВременныйФайл); // Можно попробовать через ПолучитьCOMОбъект().
    Reply ↓
  55. aspirator23

    Вроде бы все вопросы задали. Остался главный. А что это за фильм с Траволтой? -:)

    Reply ↓
  56. kare

    (58)криминальное чтиво))

    Reply ↓
  57. klinval

    (55) Нашёл свой же комментарий (тут: https://infostart.ru/public/407448/):

    Макет = Документы.ДоговорыКонтрагентов.ПолучитьМакет(«ActiveDocument»);

    Макет.Записать(ПолноеИмяФайла);

    Так вот как оказывается можно ещё сохранить! Не заметил, что у ОболочкаActiveDocument есть метод Записать. Обязательно обновите статью, т.к. многие сваливаются на методе SaveAs, а Записать() у ОболочкаActiveDocument фактически является альтернативой

    Сама статья тут: https://infostart.ru/public/270277/

    Ни у меня ни у CeHbKA эту проблему хоть и касались, но так полно и подробно как у вас и у https://infostart.ru/public/568913/ не разбиралась, поэтому по любому плюс за статью))

    Reply ↓

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *