Думаю, что каждый 1С-ник, дорабатывающий конфигурацию под заказчика, рано или поздно сталкивается с задачей доработки существующего отчета, чтобы он выводил какие-то дополнительные данные, которые на момент постановки задачи не хранятся в ресурсах регистров. Далее идет пример задачи, с которой столкнулся я. Вопрос, что такая задача не должна возникать в принципе - это бок при проектировании системы и т.д., опускаем. Все дальнейшие рассуждения есть личное мнение автора, если вы знаете другие пути или считаете, что я неправ в своих рассуждениях-выводах, то прошу писать об этом в комментариях, буду добавлять полезную информацию в статью.
Вводная: регистр ВзаиморасчетыСКонтрагентами, измерения: Фирма, Контрагент, Договор (измерения условные, в данной задаче они не суть важны);
ресурсы: на момент постановки задачи один, СуммаВзаиморасчетов - в этом ресурсе хранится сумма взаиморасчетов в гривне. Существует отчет: Ведомость по взаиморасчетам, заказчик обращается с задачей: нужно получить данные по взаиморасчетам в управленческой валюте (долларе). Пересчет в валюту осуществляется по курсу на момент операции.
При получении подобной задачи на горизонте нарисовывается два пути решения - 1) доработка только отчета и в него запихивать дополнительную логику по расчету валютных сумм 2) доработать регистр, добавить в него измерение и доработать отчет. В каждом из этих путей есть свои плюсы и минусы. В первом решении - плюс, изменения касаются только отчета, зачастую такое решение может выйти менее трудоемким, чем второе, минусы: геморрная реализация, замедление работы отчета. Во втором решении - плюсы: решение легкое, методологически поддерживаемое 1С, позволяет закрыть потенциальные новые задачи по отображеннию данных из нового ресурса в других отчетах; минусы: решение объемное и рисковое с точки зрения количества объектов доработки: нужно добавить в регистр новое измерение, добавить в модуль проведения всех документов, двигающих регистр, алгоритм расчета недостающего ресурса; сложность с первичным заполнением данных регистра - в 1С 7.7 без вариантов, только перепроведение, на это зачастую никто не идет, чтобы не поплыли итоги или нужно прогонять процедуру на тестовой базе и сравнивать обороты до и после. В 8-ке можно гибче решить эту проблему и не "перепроводить" документы, а служебной обработкой заполнить недостающее измерение.
В случае моей задачи, я принял решение идти по второму варианту, так как сразу были предпосылки к тому, что доработка отчета по взаиморасчетам в валюте - это только первая ласточка, дальше, как минимум, еще один отчет по дебиторской задолженности. Ну, и запрос получающий курсы валют на дату операции был небыстрым. Короче, решил попробовать "по науке" и пойти 2-м путем.
Ключевой момент 2-го пути - заполнение регистра без перепроведения документов. В случае регистров сведений, не привязанных к регистратору, все просто и быстро - сформировали запросом таблицу значений и загрузили ее в регистр. В случае регистра накопления такой метод не работает, требуется отбор по регистратору. Во вложении пример обработки, которая позволяет перебрать записи регистра накопления и пересчитать ресурс. Ключевой момент! СПАСИБО Magister: перед началом перебора регистра накопления необходимо отключить использование итогов, а по завершению обработки, включить (см. комментарий №9), иначе обновление регистра идет очень долго! Из-за незнания этого мне пришлось идти по рисковому пути прямых запросов в базу. Дальнейший текст по прямым запросам может представлять разве что академический интерес или же он может понадобиться, если вам нужна сверхскорость - с отключенным расчетом итогов, пересчет регистра занял бы у меня 5-6 часов, update прошел за 10 минут) или же нет возможности выполнить эту операцию монопольно (во время отключения итогов, всем пользователям базы будут недоступны запросы, использующие виртуальные таблицы Остатки, Обороты и т.д.), что фактически означает, что они не смогут работать).
Не сумев решить быстро эту задачу средствами 1С, пришел к решению воспользоваться прямым запросом на sql сервере (Как заметилlunshttp://v8.1c.ru/predpriyatie/questions_licence.htm 64 вопрос - дальнейшие действия противоречат лицензионной политике 1С, поэтому методика может применяться исключительно "втихаря" и на свой страх и риск - обратиться в службу техподдержки 1С за помощью в решении проблемы, порожденной использованием этого решения, вы не сможете).
Воспользовался обработкой //infostart.ru/public/16282/ - получил названия полей на сервере.
Маленькое лирическое отступление: язык запросов 1С 8.х синтаксически чистый SQL на русском языке, в данной ситуации нужно сделать update с использованием секции from после которой находится результат select'а (Выбрать). Задача, написать запрос, который получает курсы валюты на дату операций. При последней смене работы мне на двух независимых собеседованиях дали это задание - очень неплохой пример сложного запроса, имеющий простую, понятную формулировку и практическое применение, выкладываю два варианта решения этого задания. Один из них потом использовался в update.
Задание: написать средствами 1С запрос к регистру взаиморасчеты, который бы выводил данные не только по сумме в гривне, но и в валюте, расчитывая ее на основании курсов валют на дату операции. Варианты решений используют временные таблицы для наглядности и упрощения запросов, первый и третий временный запрос одинаковые.
1 вариант с использованием Имеющие (having):
ТекстЗапроса1 = "
//первый врем. запрос - выбираем различные дни операций из взаиморасчетов
|Выбрать Различные
| НАЧАЛОПЕРИОДА(Рег.Период) КАК ПериодДень
|Поместить тзДни
|ИЗ
| РегистрНакопления.ВзаиморасчетыСКонтрагентами КАК Рег
|ГДЕ Рег.СуммаВзаиморасчетовУпр = 0
|;
//второй врем. запрос - собственно определение курса и даты курса, ближайшей, к дате операции
|Выбрать
| Д.ПериодДень
| ,Курс2.Период
| ,Курс2.Курс
|Поместить тзКурсы
|ИЗ
| тзДни КАК Д
|Левое соединение РегистрСведений.КурсыВалют КАК Курс1
|ПО Курс1.Валюта = &Доллар
| И Д.ПериодДень >= Курс1.Период
|ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК Курс2
|ПО Курс2.Валюта = &Доллар
| И Д.ПериодДень >= Курс2.Период
|СГРУППИРОВАТЬ ПО Д.ПериодДень, Курс2.Период, Курс2.Курс Имеющие Курс2.Период = Максимум(Курс1.Период)
//|Упорядочить ПО Д.ПериодДень, Курс2.Период
|;
//собственно расчет валютной суммы
|Выбрать
| Рег.Регистратор
| ,Максимум(К.Курс) КАК Курс
| ,Выразить(Рег.СуммаВзаиморасчетов/К.Курс КАК Число(15, 2)) КАК СуммаВзаиморасчетовУпр1
|
|ИЗ
| РегистрНакопления.ВзаиморасчетыСКонтрагентами КАК Рег
| ЛЕВОЕ СОЕДИНЕНИЕ тзКурсы КАК К
| ПО НачалоПериода(Рег.Период) = К.ПериодДень
|ГДЕ Рег.СуммаВзаиморасчетовУпр = 0
| И Рег.СуммаВзаиморасчетов <> 0
|Сгруппировать ПО Рег.Регистратор
|";
Вариант 2 с использованием подзапроса:
ТекстЗапроса2 = "
//аналогично первому варианту решения
|Выбрать Различные
| НАЧАЛОПЕРИОДА(Рег.Период) КАК ПериодДень
|Поместить тзДни
|ИЗ
| РегистрНакопления.ВзаиморасчетыСКонтрагентами КАК Рег
|ГДЕ Рег.СуммаВзаиморасчетовУпр = 0
|;
//собственно расчет курса - то, что отличается от первого варианта решения
|Выбрать
| Подзапрос.ПериодДень
| ,Курс2.Период
| ,Курс2.Курс
|Поместить тзКурсы
|ИЗ
| (Выбрать
| Д.ПериодДень
| ,Максимум(Курс1.Период) КАК ДатаКурса
| ИЗ тзДни КАК Д
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК Курс1
| ПО Курс1.Валюта = &Доллар
| И Д.ПериодДень >= Курс1.Период
| СГРУППИРОВАТЬ ПО Д.ПериодДень) КАК Подзапрос
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК Курс2
| ПО Курс2.Валюта = &Доллар
| И Курс2.Период = Подзапрос.ДатаКурса
|;
// аналогично первому варианту решения
|Выбрать
| Рег.Регистратор
| ,Максимум(К.Курс) КАК Курс
| ,Выразить(Рег.СуммаВзаиморасчетов/К.Курс КАК Число(15, 2)) КАК СуммаВзаиморасчетовУпр1
|
|ИЗ
| РегистрНакопления.ВзаиморасчетыСКонтрагентами КАК Рег
| ЛЕВОЕ СОЕДИНЕНИЕ тзКурсы КАК К
| ПО НачалоПериода(Рег.Период) = К.ПериодДень
|ГДЕ Рег.СуммаВзаиморасчетовУпр = 0
|Сгруппировать ПО Рег.Регистратор";
Возвращаемся к нашей первоначальной задаче:
Заходим в mssql management studio, coздаем новый запрос (внимание, все дальнейшие действия делаем на свой страх и риск):
Ниже пример запроса обновления нового ресурса, в нем _AccumReg264 - таблица взаиморасчетов, _Fld268 - ресурс СуммаВзаиморасчетов, _Fld10132 - новый ресурс СуммаВзаиморасчетовУпр (предварительно создается в конфигураторе) в который мы заносим пересчитанные значения, _Reference66 - справочник Валют, из него берем id валюты по наименованию (поле _Description) = 'USD' (можно по коду или любому другому ключевому полю)
_InfoReg69 - регистр сведений Курсы валют, _Fld70RRef - поле Валюта (ссылка на справочник Валюты), _Period - дата курса, _Fld71 - курс валюты.
Перед любым update я сначала запускаю select с таким же окончанием, как у update и полем = формуле update для того, чтобы убедиться, что я собираюсь менять то, что нужно. Потом select комментирую и добавляю шапку update. Процедура сначала делается на тестовой базе, а только потом на боевой. Методику можно применять только тогда, когда вы четко понимаете, что делается, copy-paste здесь не работает. (Update реализован по варианту 2 вышеуказанных запросов 1С - так быстрее)
update
_AccumReg264
set
_Fld10132 = Cast(_Fld268/regCurr1._Fld71 as Numeric(15,2))
--select
-- vz._Period
-- ,vz._Fld268 as SumVz
-- ,vz._Fld10132 as SumVzUprDo
-- ,Cast(_Fld268/regCurr1._Fld71 as Numeric(15,2)) as SumVzUpr
-- ,regCurr1._Period as DateKurs
-- ,regCurr1._Fld71 as Kurs
from
_AccumReg264 as vz
Left Join
(select
Vz1._Period as Период
,Max(regCurr._Period) as ДатаКурса
from _AccumReg264 as Vz1
Left join _InfoReg69 as regCurr
on Vz1._Period >= regCurr._Period
left join
_Reference66 as Curr
on regCurr._Fld70RRef = Curr._IDRRef
Where Curr._Description = 'USD'
group by Vz1._Period) as Подзапрос1
on vz._Period = Подзапрос1.Период
Left join
_InfoReg69 as regCurr1
on Подзапрос1.ДатаКурса = regCurr1._Period
and regCurr1._Fld70RRef in (select _IDRRef
from _Reference66 as Curr1
Where Curr1._Description = 'USD')
Update изменяет базовую таблицу регистра, осталось необходимым пересчитать итоги в виртуальных таблицах (Остатки, Обороты и ОстаткиИОбороты). Так как эту операцию можно сделать средствами 1С, то быстро убегаем из ms sql management studio и в родной стихии запускаем обработку из нескольких строчек:
Сообщить("Старт пересчета регистра Взаиморасчеты: " + ТекущаяДата());
МенеджерРегистра = РегистрыНакопления.ВзаиморасчетыСКонтрагентами;
МенеджерРегистра.ПересчитатьИтоги();
Сообщить("Окончание пересчета регистра Взаиморасчеты: " + ТекущаяДата());
Пересчет в моем случае длился достаточно долго (около часа), желательно запускать, когда пользователи не работают, иначе заметно медленнее.
После этого осталось прописать в модуля проведения регистраторов код, рассчитывающий новый ресурс и чуть-чуть подкорректировать отчет по взаиморасчетам.
Комментарии к комментариям:
Во вложении обработка, которой я пытался решить проблему средствами 1С с добавлением кода от Magister. Подобный вариант можно использовать для пересчета значений нового ресурса средствами 1С, при этом нет необходимости перепроводить базу.