Технологическое перепроведение документов
Многие программисты и сисадмины, работающие с 1С 7.7 иногда ловят себя на мысли: как было бы здорово, если бы регистры можно было бы редактировать так же просто, как и бухгалтерские операции. Несколько лет назад эта проблема стала для меня настолько важной, что пришлось начать искать пути для её решения…
- Описание
- Подробнее
Описание
При изучении материала Коррекция регистров вспомнился мой собственный опыт корректировки регистров уже проведенных документов.
В отличие от способа Владислава Цылёва, я не использовал корректирующие документы, так как исправлять нужно было сразу большую последовательность документов. Не пользовался я также и прямыми запросами – для этого мне элементарно не хватило знаний.
Но всё по порядку:
Было это больше 5 лет назад. Началось всё с того, что начальство поручило мне автоматизировать работу вновь созданного Отдела альтернативной подписки на базе нашей уже работающей программы (1С 7.7 Самописная конфигурация, Оперативный и Бухгалтерский учет). Дело было новое и незнакомое – как работает подписка и что требуется для этого, не знали не только я, но и руководство. Просто нас с начальником созданного отдела поставили перед фактом: Всё должно работать!
В процессе решения этой задачи, на начальном этапе, пока в наших головах не сложилось правильное представление о том, что нам нужно, мне приходилось многократно переделывать структуры регистров и документов, перепроводить документы в «боевой» базе, в том числе и в закрыто» периоде – резать, что называется, по живому. Бухгалтерия была недовольна и категорически запрещала нам пересчитывать итоги. Если проводки ещё можно было скорректировать, не перепроводя документы, то с регистрами я этого делать не мог.
Постепенно в голове сформировался механизм перепроведения документов без пересчёта регистров и проводок. Идея состояла в том, что нужно перед проведением документа считать его движения в таблицу, а при проведении из этой же таблицы загрузить. Между этими действиями, с таблицей можно делать всё что угодно: менять значения ячеек, пересчитывать выборочно её строки, добавлять туда движения новых регистров или удалять то, что перестало быть необходимым.
Я поделился своими мыслями с друзьями, и мне подсказали, что нечто подобное уже предлагалось фирмой 1С на одном из дисков ИТС. После непродолжительных поисков я нашёл пример под названием «Технологическое перепроведение». На его основе был создан и внедрен в нашей организации механизм «технологического перепроведения документов». Поскольку за основу были взяты разработки 1С, то никакого авторства я за собой не оставляю.
Для заполнения движений и проводок документа используется функция глобального модуля глЗаполнитьДвижения().
Функция глЗаполнитьДвижения(Документ) Экспорт
ДвиженияРегистров=СоздатьОбъект(«СписокЗначений»);
Если ПустоеЗначение(Документ) = 1 Тогда
Возврат 0;
КонецЕсли;
Док = СоздатьОбъект(«Документ»);
Док.НайтиДокумент(Документ);
Для ИндРег = 1 По Метаданные.Регистр() Цикл
МДРегистр = Метаданные.Регистр(ИндРег);
ДвиженияРегистра = СоздатьОбъект(«ТаблицаЗначений»);
ДвиженияРегистра.НоваяКолонка(«ВидДвижения», «Строка», 3);
ДвиженияРегистра.НоваяКолонка(«НомерСтрокиДок», «Число», 5);
Для Инд = 1 По МДРегистр.Измерение() Цикл
ДвиженияРегистра.НоваяКолонка(МДРегистр.Измерение(Инд).Идентификатор, МДРегистр.Измерение(Инд));
КонецЦикла;
Для Инд = 1 По МДРегистр.Ресурс() Цикл
ДвиженияРегистра.НоваяКолонка(МДРегистр.Ресурс(Инд).Идентификатор, МДРегистр.Ресурс(Инд));
КонецЦикла;
Для Инд = 1 По МДРегистр.Реквизит() Цикл
ДвиженияРегистра.НоваяКолонка(МДРегистр.Реквизит(Инд).Идентификатор, МДРегистр.Реквизит(Инд));
КонецЦикла;
Рег = СоздатьОбъект(«Регистр.» + МДРегистр.Идентификатор);
Рег.ВыбратьДвиженияДокумента(Док.ТекущийДокумент());
Пока Рег.ПолучитьДвижение() = 1 Цикл
ДвиженияРегистра.НоваяСтрока();
Стр = ДвиженияРегистра.КоличествоСтрок();
ДвиженияРегистра.ВидДвижения = ?(Рег.Приход = 1, «+», «-«);
ДвиженияРегистра.НомерСтрокиДок = Рег.НомерСтроки();
Для Инд = 1 По МДРегистр.Измерение() Цикл
Имя = МДРегистр.Измерение(Инд).Идентификатор;
ДвиженияРегистра.УстановитьЗначение(Стр, Имя, Рег.ПолучитьАтрибут(Имя));
КонецЦикла;
Для Инд = 1 По МДРегистр.Ресурс() Цикл
Имя = МДРегистр.Ресурс(Инд).Идентификатор;
ДвиженияРегистра.УстановитьЗначение(Стр, Имя, Рег.ПолучитьАтрибут(Имя));
КонецЦикла;
Для Инд = 1 По МДРегистр.Реквизит() Цикл
Имя = МДРегистр.Реквизит(Инд).Идентификатор;
ДвиженияРегистра.УстановитьЗначение(Стр, Имя, Рег.ПолучитьАтрибут(Имя));
КонецЦикла;
КонецЦикла;
ДвиженияРегистров.Установить(МДРегистр.Идентификатор, ДвиженияРегистра);
КонецЦикла;
Операция = СоздатьОбъект(«Операция»);
Если Операция.НайтиОперацию(Документ) = 1 Тогда
ДвиженияРегистров.Установить(«СуммаОперации», Операция.СуммаОперации);
ДвиженияРегистров.Установить(«Содержание», СокрЛП(Операция.Содержание));
Проводки = СоздатьОбъект(«ТаблицаЗначений»);
Проводки.НоваяКолонка(«Дт», «Счет»,,,, 5);
Проводки.НоваяКолонка(«СубкД1»,,,,, 10);
Проводки.НоваяКолонка(«СубкД2»,,,,, 10);
Проводки.НоваяКолонка(«СубкД3»,,,,, 10);
Проводки.НоваяКолонка(«Кт», «Счет»,,,, 5);
Проводки.НоваяКолонка(«СубкК1»,,,,, 10);
Проводки.НоваяКолонка(«СубкК2»,,,,, 10);
Проводки.НоваяКолонка(«СубкК3»,,,,, 10);
Проводки.НоваяКолонка(«Количество», «Число», 5,,, 3);
Проводки.НоваяКолонка(«Сумма», «Число», 15, 2,, 6);
Проводки.НоваяКолонка(«СодержаниеПроводки», «Строка»,,,, 10);
Проводки.НоваяКолонка(«НомерЖурнала», «Строка»,,,, 4);
Операция.ВыбратьПроводки();
Пока Операция.ПолучитьПроводку() = 1 Цикл
Проводки.НоваяСтрока();
ТекСтр = Проводки.НомерСтроки;
ОперДбСчет = Операция.Дебет.Счет;
Проводки.Дт = ОперДбСчет;
Если ОперДбСчет.КоличествоСубконто() > 0 Тогда
Для ъ = 1 по ОперДбСчет.КоличествоСубконто() Цикл
Проводки.УстановитьЗначение(ТекСтр, «СубкД» + ъ, Операция.Дебет.Субконто(ъ));
КонецЦикла;
КонецЕсли;
ОперКтСчет = Операция.Кредит.Счет;
Проводки.Кт = ОперКтСчет;
Если ОперКтСчет.КоличествоСубконто() > 0 Тогда
Для ъ = 1 по ОперКтСчет.КоличествоСубконто() Цикл
Проводки.УстановитьЗначение(ТекСтр, «СубкК» + ъ, Операция.Кредит.Субконто(ъ));
КонецЦикла;
КонецЕсли;
Проводки.Количество = Операция.Количество;
Проводки.Сумма = Операция.Сумма;
Проводки.СодержаниеПроводки = Операция.СодержаниеПроводки;
Проводки.НомерЖурнала = Операция.НомерЖурнала;
КонецЦикла;
ДвиженияРегистров.Установить(«Операция», Проводки);
КонецЕсли;
ДвиженияРегистров.Установить(«Вариант», «ТехнологическоеПерепроведение»);
Возврат ДвиженияРегистров;
КонецФункции
Для демонстрации результата выполнения функции я использую разработку глПоказать() многоуважаемого zorro, которой пользуюсь уже много лет. (Выражаю огромную благодарность её автору).
Вот так в ней выглядит список ДвиженияРегистров. В качестве значений этого списка – таблицы, повторяющие структуру регистров и содержащие движения регистра документом.

Вот пример таблицы ОстаткиТоваров. Её структура копирует структуру регистра ОстаткиТоваров, кроме двух служебных колонок: ВидДвижения, и НомерСтрокиДок.
Если документ имеет бухпроводки, то они записываются в таблицу с названием Операция.
Для реквизитов СуммаОперации и Содержание в этой таблице места не нашлось, и они занимают «особое» положение в списке значений ДвижениеРегистров.
Заполнение таблицы Операция сделано для того, чтобы контролировать проводки во время технологического перепроведения, и не обрабатывать документ дважды, фактически двумя способами. Ведь если уж мы всё равно его перепроводим, зачем дополнительно править операцию?
В глобальный модуль также нужно поместить процедуру глТехнологическоеПерепроведение(), назначение которой – проводить документ «из таблицы».
Процедура глТехнологическоеПерепроведение(Конт, ДвиженияРегистров)Экспорт
Для ИндРег = 1 По Метаданные.Регистр() Цикл
МДРегистр = Метаданные.Регистр(ИндРег);
ДвиженияРегистра = ДвиженияРегистров.Получить(МДРегистр.Идентификатор);
Если ПустоеЗначение(ДвиженияРегистра) = 1 Тогда
Продолжить;
КонецЕсли;
Рег = Конт.Регистр.ПолучитьАтрибут(МДРегистр.Идентификатор);
Для Стр = 1 По ДвиженияРегистра.КоличествоСтрок() Цикл
Рег.ПривязыватьСтроку(ДвиженияРегистра.ПолучитьЗначение(Стр, «НомерСтрокиДок»));
Для Инд = 1 По МДРегистр.Измерение() Цикл
Имя = МДРегистр.Измерение(Инд).Идентификатор;
ПрисвоитьЗначениеСТипом(Рег, Имя, МДРегистр.Измерение(Инд), ДвиженияРегистра.ПолучитьЗначение(Стр, Имя));
КонецЦикла;
Для Инд = 1 По МДРегистр.Ресурс() Цикл
Имя = МДРегистр.Ресурс(Инд).Идентификатор;
Рег.УстановитьАтрибут(Имя, ДвиженияРегистра.ПолучитьЗначение(Стр, Имя));
КонецЦикла;
Для Инд = 1 По МДРегистр.Реквизит() Цикл
Имя = МДРегистр.Реквизит(Инд).Идентификатор;
ПрисвоитьЗначениеСТипом(Рег, Имя, МДРегистр.Реквизит(Инд), ДвиженияРегистра.ПолучитьЗначение(Стр, Имя));
КонецЦикла;
Если МДРегистр.ТипРегистра = «Остатки» Тогда
Если СокрЛП(ДвиженияРегистра.ПолучитьЗначение(Стр, «ВидДвижения»)) = «+» Тогда
Рег.ДвижениеПриходВыполнить();
Иначе
Рег.ДвижениеРасходВыполнить();
КонецЕсли;
Иначе
Рег.ДвижениеВыполнить();
КонецЕсли;
КонецЦикла;
КонецЦикла;
Если ТипЗначенияСтр(ДвиженияРегистров.Получить(«Операция»)) = «ТаблицаЗначений» Тогда
Опер = Конт.Операция;
ДвиженияРегистра = ДвиженияРегистров.Получить(«Операция»);
Для Стр = 1 По ДвиженияРегистра.КоличествоСтрок() Цикл
Опер.НоваяПроводка();
Опер.Дебет.Счет = ДвиженияРегистра.ПолучитьЗначение(Стр, «Дт»);
Если Опер.Дебет.Счет.КоличествоСубконто() > 0 Тогда
Для ъ = 1 по Опер.Дебет.Счет.КоличествоСубконто() Цикл
СубкД = ДвиженияРегистра.ПолучитьЗначение(Стр, «СубкД» + ъ);
Опер.Дебет.Субконто(ъ, СубкД);
КонецЦикла;
КонецЕсли;
Опер.Кредит.Счет = ДвиженияРегистра.ПолучитьЗначение(Стр, «Кт»);
Если Опер.Кредит.Счет.КоличествоСубконто() > 0 Тогда
Для ъ = 1 по Опер.Кредит.Счет.КоличествоСубконто() Цикл
СубкК = ДвиженияРегистра.ПолучитьЗначение(Стр, «СубкК» + ъ);
Опер.Кредит.Субконто(ъ, СубкК);
КонецЦикла;
КонецЕсли;
Если ПустоеЗначение(ДвиженияРегистра.ПолучитьЗначение(Стр, «Количество»)) = 0 Тогда
Опер.Количество = ДвиженияРегистра.ПолучитьЗначение(Стр, «Количество»);
КонецЕсли;
Опер.Сумма = ДвиженияРегистра.ПолучитьЗначение(Стр, «Сумма»);
Опер.СодержаниеПроводки = ДвиженияРегистра.ПолучитьЗначение(Стр, «СодержаниеПроводки»);
Если ПустоеЗначение(ДвиженияРегистра.ПолучитьЗначение(Стр, «НомерЖурнала»)) = 0 Тогда
Опер.НомерЖурнала = ДвиженияРегистра.ПолучитьЗначение(Стр, «НомерЖурнала»);
КонецЕсли;
КонецЦикла;
Опер.СуммаОперации = ДвиженияРегистров.Получить(«СуммаОперации»);
Опер.Содержание = ДвиженияРегистров.Получить(«Содержание»);
Опер.Записать();
КонецЕсли;
КонецПроцедуры
В обработки проведения документов нужно добавить следующие строки:
Процедура ОбработкаПроведения(Реж)
Если ТипЗначенияСтр(Реж) = «СписокЗначений» Тогда
Если Реж.Получить(«Вариант») = «ТехнологическоеПерепроведение» Тогда
глТехнологическоеПерепроведение(Контекст, Реж);
Возврат;
КонецЕсли;
КонецЕсли;
…
Теперь если при проведении документа «подсунуть» ему в качестве параметра список с движениями:
Док.Провести(0, ДвиженияРегистров);
, то с этими движениями он и проведётся.
Вот собственно говоря и всё. Хотя отдел альтернативной подписки успешно работает и «подписная компонента» в корпоративной базе 1С «функцикулирует», от использования технологического перепроведения мы не отказались. Периодически бывает нужно скорректировать движения документа вслед за менеджерами.
Вот пример обработки, которую я использую, если они оприходовали не тот товар (и почему такие случаи выявляются только через полгода?):
Процедура Выполнить()
Если ВыбТов2=ВыбТов1 Тогда
Сигнал(); Предупреждение(«Товары не должны быть одинаковыми!»,5);
Возврат;
КонецЕсли;
Сообщить(«Время начала обработки: » + ТекущееВремя(), «i»);
Запрос = СоздатьОбъект(«Запрос»);
ТекстЗапроса =
«Период с НачДата по КонДата;
|Товар = Регистр.ОстаткиТоваров.Товар;
|Док = Регистр.ОстаткиТоваров.ТекущийДокумент;
|Кол = Регистр.ОстаткиТоваров.Количество;
|Функция КолПриход = Приход(Кол);
|Функция КолРасход = Расход(Кол);
|Группировка Док;
|Условие(Товар = ВыбТов1);»;
Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
Предупреждение(«Не вышло?»); Возврат;
КонецЕсли;
Й=0;
Пока Запрос.Группировка(1) = 1 Цикл
Зап=0;
ДД = Запрос.Док; ДДВид=ДД.Вид();
ТД = СоздатьОбъект(«Документ.» + ДДВид);
ТД.НайтиДокумент(ДД);
Если ТД.Проведен() = 1 Тогда
ДвиженияРегистров = глЗаполнитьДвижения(ТД.ТекущийДокумент(), 1);
Состояние(«»+ТД);
Если (ДДВид=«НакладнаяПередачи») Тогда
ТЗ=ДвиженияРегистров.Получить(«ПередачаТовара»);
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку()=1 Цикл
Если ТЗ.Товар=ВыбТов1 Тогда
ТЗ.Товар=ВыбТов2;
Зап=1;
КонецЕсли;
КонецЦикла;
КонецЕсли;
Если (ДДВид=«ВозвратПоставщику») Тогда
ТЗ=ДвиженияРегистров.Получить(«Поставщики»);
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку()=1 Цикл
Если ТЗ.Товар=ВыбТов1 Тогда
ТЗ.Товар=ВыбТов2;
Зап=1;
КонецЕсли;
КонецЦикла;
КонецЕсли;
Если (ДДВид=«РасходнаяНакладная») Или (ДДВид=«ВозвратнаяНакладная») Или (ДДВид=«ПередачаТовара») Тогда
ТЗ=ДвиженияРегистров.Получить(«Оборот»);
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку()=1 Цикл
Если ТЗ.Товар=ВыбТов1 Тогда
ТЗ.Товар=ВыбТов2;
Зап=1;
КонецЕсли;
КонецЦикла;
КонецЕсли;
ТЗ=ДвиженияРегистров.Получить(«ОстаткиТоваров»);
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку()=1 Цикл
Если ТЗ.Товар=ВыбТов1 Тогда
ТЗ.Товар=ВыбТов2;
Зап=1;
КонецЕсли;
КонецЦикла;
ТЗ=ДвиженияРегистров.Получить(«Операция»);
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку()=1 Цикл
Если Лев(ТЗ.Дт,3)=«41.» Тогда
Если ТЗ.СубкД1=ВыбТов1 Тогда
ТЗ.СубкД1=ВыбТов2;
Зап=1;
КонецЕсли;
КонецЕсли;
Если Лев(ТЗ.Кт,3)=«41.» Тогда
Если ТЗ.СубкК1=ВыбТов1 Тогда
ТЗ.СубкК1=ВыбТов2;
Зап=1;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Если Зап=1 Тогда
Попытка
Й=Й+1;
Сообщить(« «+Й+«. » + ТД, «i»);
Если ТД.Провести(0, ДвиженияРегистров) = 0 Тогда
ОтменитьТранзакцию();
Сообщить(«- Документ не перепроведён » + ДД, «!»);
Прервать;
КонецЕсли;
Исключение
Сообщить(«— » + ОписаниеОшибки(), «!!!»);
Прервать;
КонецПопытки;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Сообщить(«Обработка завершена: » + ТекущееВремя() + » » + РазделительСтраниц, «i»);
Сообщить(«»);
Сигнал();
Предупреждение(«Готово!»);
КонецПроцедуры
Хочу отметить, что технологическое перепроведение проходит значительно быстрее обычного, т.к. предварительные расчеты не проводятся, а берутся сразу готовые результаты из таблицы.
Если требуется распечатать движение регистра документа, можно использовать последовательность процедур глЗаполнитьДвижения() и глПоказать().
На основе этой методики мой бывший напарник сваял обработку для интерактивной правки движений документа:
Чтобы при повторном проведении после исправления старые движения не восстанавливались, эту штуку использую совместно с обработкой Editrekv.ert Владислава Цилёва.
Ну вот наверное и всё, что я хотел сказать. Если будете использовать, то имейте ввиду, что при работе с бухпроводками процедуры глЗаполнитьДвижения() и глТехнологическоеПерепроведение () не сохраняют и не восстанавливают реквизиты Валюта, ВалСумма, Основание… Просто не используем мы их. Ну если до сюда дочитали, то добавить сами сможете 🙂

