<?php // Полная загрузка сервисных книжек, создан 2024-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='\
Вполне логично. После НайтиДокумент вы имеете в памяти закешированные данные объекта и работаете с ними. И тут бабах, меняете состояние объекта в базе. Разом получаете конфликт — в базе одна информация, а в памяти другая. По хорошему в подобных случаях языку бы выдавать ошибку блокировки объекта…
Статья изобилует ошибками.
на типовой ТиС
1.
— все неверно.
Речь в данном примере идет ОБ ИНТЕРАКТИВНОМ записи и проведении документа (работает оператор). Здесь:
Документ записался. Но не провелся. Транзакция откатилась. ДОКУМЕНТ В БАЗУ НЕ ЗАПИСАН. ИЗМЕНЕН, НО НЕ ЗАПИСАН — об этом свидетельствует заначок незаписанной модификации {*} в заголовке формы документа. таким образом мы в документе видим бОльшее количество товара, но оно в базу не записано. Мы можем напечатать что угодно — но это не значит что напечатанное соответствует реальности. При печати из МОДИФИЦИРОВАННОЙ ФОРмы (если мне не изменяет память) через ВПФ или встроенную печформу — надо анализировать признак модифицированности формы (при передаче в процедуру печати группового контекста формы) или печатать АКТУАЛЬНЫЙ ДОКУМЕНТ по передаваемой ссылке на документ. и цифры в печформе будут соответсоввать реальности. или тому как вы хотите.
таким образом — данный абзац автора демонстрирует всего лишь а) возможно, невнимательность) и б) незнание особенности программной реализации кода конфигурации (полностью типовой ТиС у меня под рукой нет, но насколько мне помнится там при попытке печати модифицированного но не записанного документа выдавалось предупреждение; наставиать не буду, факт лишь то — что при незаписанных данных обеспечить печать ПРАВИЛЬНЫХ данных — это всего лишь надо знать и уметь использовать предоставляемые платформой возможности).
— более того, исходный код обработки НЕВЕРЕН. так как не обеспечивает логическую непротиворечивость данных. От реквизита Документ_1 зависит проведение документа. Вы меняете этот реквизит У ПРОВЕДЕННОГО ДОКУМЕНТА, но при этом мало того что не записываете — ДАЖЕ ЕСЛИ И ЗАПИСАЛИ — его надо еще дополнительно ОБЯЗАТЕЛЬНО ПРОВЕСТИ.
то есть в вашем случае код обработки должен выглядеть так:
Процедура Выполнить()
док2 = СоздатьОбъект(«Документ.Документ_2»);
док2.НайтиДокумент(ВыбДокумент_2);
док2.Документ_1 = ВыбДокумент_1;
док2.Записать(); //д.б.обязательно в соответстии с логикой использования документа_2
док2.Провести(); //д.б.обязательно в соответстии с логикой использования документа_2
//А ЗДЕСЬ ДАЛЬШЕ ДЕЛАЙТЕ ЧТО ХОТИТЕ С ДОКУМЕНТОМ_2
Сообщить(«1. Перед распроведением Документ 1: «+док2.Документ_1);
док2.СделатьНепроведенным();
Сообщить(«2. После распроведения Документ 1: «+док2.Документ_1);
док2.Записать();
Сообщить(«3. После записи Документ 1: «+док2.Документ_1);
док2.Провести();
Сообщить(«4. После проведения Документ 1: «+док2.Документ_1);
КонецПроцедуры
с такой поправкой — все отрабатывает как надо — в базе появляется документ2 в котором в шапке заполнен документ1
(1)
— как ты себе это представляешь?
прочитал я объект — меняю его реквизиты
в это время в другой сессии ктото прочитал этот обьект, поменял его напрочь и успел записать. а я еще все меняю и потом записываю — и все изменения второй сесии — похерятся нафиг. Это штатное поведение клюшек. Чтение — всегда грязное.
а в 8-ке — да, при попытке записи измененного объекта — выдается ошибка.
Глючок, конечно, занятный
Суть его в том, что по факту записи документа в базу после распроведения не происходит — почему так ХЗ, надо поинтереосваться у знающих людей.
а так — взять на заметку что после для избежания такого выверта надо либо как отметил выше автор(с моим уточнением) перед распроведением записать+провести документ (то есть программировать как надо по логике — а не тяп-ляп на скорую руку), либо актуализировать документ
Сообщить(«1. Перед распроведением Документ 1: «+док2.Документ_1);
док2.СделатьНепроведенным();
док2.НайтиДокумент(ВыбДокумент_2);
Сообщить(«2. После распроведения Документ 1: «+док2.Документ_1);
Я не говорил, что речь идет о типовой ТиС. Но как ни странно именно в ТиС я такую ситуацию и наблюдал. ТиС тоже есть разных версий и ее зачастую дописывают. А есть еще ТиС для Украины с которой я в основном и работаю, и она сильно отличается от российской. Я утверждаю лишь то, что если в коде модуля формы не значится ПриЗаписиПерепроводить(1), то есть возможность записать проведенный документ без его перепроведения. Какие это вызывает проблемы — указано в статье.
(7)
В статье приведен код, очищенный и подготовленный, так сказать. Ясное дело, что по нему сразу видно ошибку. Реальный же код, который выдал эту ошибку был более громоздкий и запутанный. Как минимум распроведение документа скрывалось в отдельной процедуре. Процедура эта была добавлена в процессе доработки: нужно было менять время существующих документов.
И если эту особенность не знать или не думать в эту сторону, то с отладчиком можно рехнуться))
(9) насчет рехнутьяс — это точно.
а то что распроведение было «спрятано» — ну так это и есть — смастерили костыль на скорую руку, без понимания логики ранее написанного запутанного и громоздкого кода.. 😉
Пообщался со спецами.
Условно (связано с программисткими виртуальными функциями и пр.шнягой):
Модифицировать реквизиты нужно ПОСЛЕ ПОСЛЕДНЕГО ЧТЕНИЯ объекта (или записывать модифицированное перед чтением).
если ты смодифицировал реквизиты, а потом явно или неявно перечитал в память объект — то в зависимости от кучи условий (так как разные функции чтения читают разный набор полей) может быть либо ожидаемый, либо неожидаемый результат.
Причем в зависимости от методов чтения объекта — могут получаться разные «версии» полей одного и того же объекта.
И типа в этом случае глюка:
НайтиДокумент дал объект с набором полей «версия1»
СделатьНеПоведенным для этого же объекта породил набор полей «версия2».
причем в общем случае перечень полей (а это СВОЙСТВА объекта) в разных версиях может быть разным.
и записать() использует вторую версию полей (с незаполненной шапкой Документ_1)
а провести() использует первую версию полей с заполненным шапкой Документ1
То есть вот так очень условно и очень мутно — ибо программеры человеческим языком для простых девЕлоперов пояснить не могут. 😉
СделатьНеПоведенным — вызывает чтение объекта из базы… и порождает вторую «версию» полей объекта (набор полей не обяхательно совпадает с набором полей из первой версии). и одновременно у объекта есть поле «Документ1» которое заполнено и второе, которое назаполнено.
И как себя теперь поведет система при дальнейшем использовании объекта — мы уже ХЗ.
поэтому программить надо аккуратно. Именно то о чем я говорил выше. Расставлляя костыли — смотреть чтобы костыль был поставлен в нужное место.
а в исходном варианте — как написал сам автор — костылей было столько, что еще один костыль среди этого леса поставили не там где надо…
В итоге все что нужно (как написал выше) — модифицировать ПОСЛЕ псоледнего чтения, т.е. после распроведения.
такой код — ведет себя ожидаемо и предсказуемо
//*******************************************
Процедура Выполнить()
док2 = СоздатьОбъект(«Документ.Документ_2»);
док2.НайтиДокумент(ВыбДокумент_2);
Сообщить(«1. Перед распроведением Документ 1: «+док2.Документ_1);
док2.СделатьНепроведенным();
Сообщить(«2. После распроведения Документ 1: «+док2.Документ_1);
док2.Документ_1 = ВыбДокумент_1;
Сообщить(«3. Перед записи Документ 1: «+док2.Документ_1);
док2.Записать();
Сообщить(«3. После записи Документ 1: «+док2.Документ_1);
док2.Провести();
Сообщить(«4. После проведения Документ 1: «+док2.Документ_1);
КонецПроцедуры
(11) Спасибо за проявленный интерес и предоставленный отзыв специалистов по внутреннему миру платформы.
Я правильно понял, что метод СделатьНеПроведенным() создает новый экземпляр объекта куда перечитывает из базы поля документа, которые мы в свою очередь еще не обновили? Только выходит, что ссылка на этот экземпляр не возвращается в нашу переменную и поэтому Метод Записать() не обновляет данные в базе. Ведь мы вызываем метод Записать() из первого объекта, а он уже «не связан» с данными?
Просто я себе никак не могу представить одновременное существование разных состояний одного экземпляра объекта.
(14) не, немножко не так (надо учитывать что мой пересказ — это некая мутная сказка про то что мне пытались рассказать умные люди). как-то так:
объект (1Сный док2) остается тот же самый
но
НайтиДокумент — реализуется некоей функцией чтения, которая читает из базы в поля объекта соответствующие поля базы
СделатьНепроведенным — реализуется ДРУГОЙ функцией чтения, которая тоже читает в поля объекта из базы, но не обязательно тот же самый набор полей, могут читаться ограниченный набор полей — и под этот набор создается новая область в памяти (какие-то виртуальные программистские функции я хз что это за хрень)
условно:
в памяти имеем после этих двух методов
Объект (он один)
|___>(набор полей#1) — использовался при проведении
|___>(набор полей#2) — использовался при записи
Мое объяснение — это как пересказ тупым то что он услышал от умных, то есть полный бред… то есть весьма условное.
Излагать связно, коротко и понятно — должен спец. но они страдают невозможностью доступно изложить неспециалисту узкоспециализированные вещи — поэтому весьма условные и мутные аналогии
Итог прост
1. сначала прочитай все что нужно — потом модифицируй и записывай
2. смодифицировал но не записал и перечитываешь тот же самый объект — а) смысл этого непонятен б) могут быть глюки если перечитка внутри 1С реализуется (в зависимости от разных услвоий) другим вариантом функции чтения..
бред короче несу
(14)
я тоже, если рассматривать обьект как простую линейную структуру. Но там все сложнее — объект — это куча связанных структур/списков… или например как реквизиты объекта — это ведь даже в СП они называются СВОЙСТВА и может быть одновременно один и тот же объект полученный разными МЕТОДАМИ (НайтиПоРеквизиту,ПолучитьЭлемент() итд. а разные МЕТОДЫ имеют свой набор СВОЙСТВ (прочитанных полей-реквизитов)
короче не надо меня слушать.. бредятина…
спецы ща ржут в голос наблюдая как один слепой пытается объяснить другому слепому что такое «слон»
(14)
Дмитрий.
Ваше утверждение
можно принять как объяснение происходящих процессов в движке 1С-а. Только снимается не «отметки … документа», а «отметки» об модификации «свойств» (полей документа) в оперативной памяти. И не заморачиваться — каким образом реализованы в движке эти самые «отметки».
(6)
Чисто теоретически, в 1С8 сделано правильно. По крайней мере при интерактивной работе. Чтение грязное. Но первая же попытка внести изменения в объект приводит к его блокировке. А штатное поведение клюшек работает на уровне прошлого века. Никакого защищенного/безопасного программирования и прочих серебрянных пуль 21 века 🙂
(4) Еще попытки-исключение не хватает и транзакции. Вдруг документ запишется, а при проведении возникнет ошибка.
(14) Скорее всего СделатьНеПроведённым() никакого нового экземпляра объекта не создаёт. Лезет в базу данных, помечает документ как непроведённый, удаляет его движения, пересчитывает итоги. А объект в памяти остаётся как есть. Со всеми изменениями. Больше хитростей в методе Провести. Он работает с объектом в памяти. Но с инициализацией объекта там есть конкретные косяки.
(14)
Ты не поверишь 🙂
Док1 = СоздатьОбъект(«Документ»);
Док2 = СоздатьОбъект(«Документ»);
Док1.НайтиДокумент(Док);
Док2.НайтиДокумент(Док);
Это, конечно, не «один экземпляр объекта», но кто его знает, как всё устроено внутри. При вызове СделатьНепроведенным явно не нужна вся информация о документе, поэтому незачем тащить документ в память. И незачем пользоваться объектом в памяти, потому что непроведенным делается документ в базе, а не в памяти.
А то, что 1С разрешает такой конфликт заполучить на ровном месте, это уже другой вопрос.
(21) Вы привели пример создания 2-х экземпляров объекта. Мне кажется если у одного из них попытаться либо Записать(), либо Провести() либо СделатьНеПроведенным() сработает исключение «Объект заблокирован». Но не уверен точно. Надо проверить.
(20) Такое объяснение не дает ответ на вопрос, почему метод Записать() не возвращает наши измененные значения реквизитов из объекта в памяти в запись в базе данных.
(23)
Боюсь, что ответ на вопрос, получить невозможно в принципе. Ответить могут только разработчики 1С 7.7, которые уже давно забыли что это такое. Мы можем только гадать. Предположите, что 1С пытается оптимизировать обращение с базой данных и не писать те реквизиты документа, которые не изменялись.
Проверить это предположение вы легко можете сами, если есть под рукой sql-база. Ловите профайлером запрос на изменение данных и смотрите, все ли реквизиты в него попали. Возьмётесь?
P.S.
Ну и зачем вам этот ответ? Вы подметили тонкое место, на котором могут огрести проблем изготовители бардачного кода. Отлично. Но при правильном кодировании проблемы возникнуть не должно
НачатьТранзакцию
Попытка
СделатьНепроведенным
<изменения в документе>
Записать
Провести
ЗафиксироватьТранзакцию
Исключение
ОтменитьТранзакцию
КонецПопытки
(20)
https://infostart.ru/public/15211/
(23)
Дмитрий, Владислав.
Я посмотрел трассировку действий движка 1С-а при выполнении СделатьНеПроведённым() с помощью
Да. Чтения самого документа там нету. Есть обращения (чтение/запись/удаление) к журналу документов, ссылкам документов, регистрам. Но, для осознания происходящего в тесте из данной публикации надо иметь в виду, что для работы с полями одного и того же документа используется несколько «массивов» (списков) в оперативной памяти 1С-а. И значения полей «перемещаются» и предоставляются разным алгоритмам из разных «массивов». Например (очень примерно!!!), присвоение «док2.Документ_1 = ВыбДокумент_1» помещает значение в «массив» переменных текущей функции. Алгоритм присвоения значения переменной «понимает», что речь идёт о поле документа. И помещает это же значений ещё и в другой «массив» для последующего выполнения Записать() или Провести(). И уже этот «массив» использует движок (что я уже и вижу в трассировке) для выдачи команд СУБД по переносу значений полей в буфер ввода/вывода СУБД. Этот второй «массив» очищается, меняет состав строк, пересоздаётся и т.д. в зависимости от используемых типов/видов/методов объекта 1С-а по работе с базой данных. Логично предположить, что метод СделатьНеПроведённым() очищает этот второй «массив».
(13) Кстати да. Я провел небольшое тестирование. При любом присваивании в реквизиты документа после метода СделатьНепроведенным() (причем неважно в какой реквизит) объект опять оживает, и метод Записать() записывает данные в базу.
(26)
Дмитрий.
Я сделал сравнение двух трассировок для:
1)
2)
В первом случае в таблицы БД документа ничего не пишется. Обновляется только журнал документов. Во втором случае производится обновление только таблица шапки документа (Поле1 и Поле3 — реквизиты шапки). Аналогичные действия для табличной части документа вызывают обновление только табличной части. Т.е. в кишках 1С-а существует два «флага» (признака) модифицированности для шапки и для табличной части.
Т.е. надо всегда делать как написано в (24) сообщении.
Только поменять порядок операторов: