Не пора ли получить сертификат 1С:Специалист по платформе? Как-то все нет времени, много работы, да и сертификат этот не особо нужен. Так? А может все же…
Эта публикация для всех, кто подумывает о сдаче экзамена. Здесь описаны основные принципы решения задач и приложены примеры решений (проверялось на платформе 8.3.17.1549). Цель - помочь желающим начать подготовку и сдать экзамен.
Все задачи решаются на каркасной конфигурации.
Оглавление
Решение задач оперативного учета
- Приходная накладная
- Себестоимость
- Расходная накладная (списание себестоимости по средней)
- Проблема копеек
- Блокировка данных
- Расходная накладная (списание себестоимости по партиям)
- Старая и новая методики контроля остатков
- Расходная накладная (без себестоимости по новой методике)
Решение задач бухгалтерского учета
- План счетов и регистр бухгалтерии
- Субконто
- Настройка учета себестоимости (признаки учета субконто)
- Приходная накладная
- Расходная накладная
- Отчет "Продажи"
Решение задач периодических расчетов
- План видов расчета и регистр расчета
- График работы
- Оклад, тариф, фиксированная сумма, процент
- Документ "Начисление зарплаты" (Оклад)
- Зависимость по базовому периоду (Премия)
- Использование разрезов при получении базы (Премия руководителя)
- Механизм вытеснения (командировка, больничный)
- Формирование сторно-записей (ввод командировки задним числом)
- Отчет "Перерасчет зарплаты"
- Процедура перерасчета
- Документ "Табель"
Решение задач по бизнес-процессам
- Механизм бизнес-процессов
- Описание решения
Решение задач по управляемым формам
- Условие задачи
- Описание решения
Прежде всего, экзамен по платформе – это не экзамен по программированию, а экзамен на знание механизмов платформы. Необходимо по максимуму их использовать при решении задач. Если чувствуете, что начинаете программировать что-то сложное, то скорее всего решаете задачу неправильно. Нужно показать, что понимаешь какие галочки в каких случаях надо ставить, какие объекты использовать, какие конструкции кода применять.
Оперативный учет
Задача оперативного учета – реализовать механизм учета поступления, хранения и реализации товара. Учет может вестись в разрезе складов, договоров, сроков годности и т.п.
Самое сложное – разработать правильный механизм списания себестоимости товаров.
Бухгалтерский учет
Задачи бухгалтерского учета описывают те же хозяйственные операции, что и оперативный учет. Только вместо регистров накопления необходимо использовать регистр бухгалтерии, учет в котором происходит в разрезе счетов, субконто и измерений.
Особое внимание стоит уделить настройке плана счетов.
Сложные периодические расчеты
Задачи по сложным периодическим расчетам – это задачи расчета оклада, тарифа, больничного, премии, командировки в различных ситуациях. Расчет методом отклонений, по табелю, с использованием различных механизмов платформы (вытеснение, получение базы, сторно-записи, перерасчет).
Важно понять какой из методов решения должен использоваться в задаче.
Бизнес-процессы
Бизнес-процесс – алгоритм выполнения бизнес операций, представленный в виде блок схемы, который при продвижении по маршруту (блок-схеме) создает Задачи исполнителям.
Конкретный пользователь при входе в информационную базу должен видеть список своих невыполненных задач. При нажатии кнопки «Выполнено» задача исчезает из списка невыполненных и бизнес процесс продвигается на следующий этап, который можно проследить на карте маршрута (блок-схеме).
Управляемые формы
Задачи по управляемым формам требуют навыка работы с формами элемента, списка. Нужно уметь их создавать, изменять, открывать программно с параметрами и т.п.
Решение задач оперативного учета
Компания занимается оптовой торговлей, поступление товаров отражается документом "Приходная накладная", продажа - "Расходная накладная".
Этого условия достаточно, чтобы объяснить принцип решения задач оперативного учета.
Для учета товаров используем Регистр накопления вида Остатки с измерением "Номенклатура" и ресурсами "Количество" и "Сумма".
Первым делом товар нужно купить. Документом "ПриходнаяНакладная" учитывается "Номенклатура", "Количество" и "Сумма". Вот настройка конструктора движений и простейший код операции проведения:
Процедура ОбработкаПроведения(Отказ, Режим)
//{{__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
// Данный фрагмент построен конструктором.
// При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!
// регистр ОстаткиНоменклатуры Приход
Движения.ОстаткиНоменклатуры.Записывать = Истина;
Для Каждого ТекСтрокаСписокНоменклатуры Из СписокНоменклатуры Цикл
Движение = Движения.ОстаткиНоменклатуры.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Приход;
Движение.Период = Дата;
Движение.Номенклатура = ТекСтрокаСписокНоменклатуры.Номенклатура;
Движение.Количество = ТекСтрокаСписокНоменклатуры.Количество;
Движение.Сумма = ТекСтрокаСписокНоменклатуры.Сумма;
КонецЦикла;
//}}__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
КонецПроцедуры
Код рабочий, необходимые движения "Приходной накладной" он реализует. Но его можно улучшить. Во первых весь код, находящийся внутри конструкции
//{{__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
// Данный фрагмент построен конструктором.
// При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!
//}}__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
будет утерян при повторном вызове конструктора. Вы скажете : "Ну и ладно, дальше будем вносить изменения только ручками." На практике все немного по-другому.
К примеру, сейчас мы реализовали только движения по регистру "ОстаткиНоменклатуры", а потом поняли, что для расчета себестоимости нам потребуется отдельный регистр накопления "Себестоимость" в связи с тем, что склад при расчете учитываться не должен. После добавления регистра нам потребуется дописать движения документа "Приходная накладная". Запустим повторно конструктор и потеряем весь ранее написанный код проведения. Конечно можно все движения написать вручную, но это значительно дольше и незачем. Указанную выше конструкцию просто сдвигаем вниз процедуры и все вновь сформированные конструктором движения будут появляться в ней.
Это первое улучшение, касающееся всех документов. Теперь второе.
Обратите внимание, если в табличной части "Список номенклатуры" будет несколько строк с одинаковой номенклатурой (дубли номенклатуры), то они в неизменном виде перенесутся в регистр. Это неоптимально с точки зрения хранения данных. Эти строки можно записать в одну строку регистра. Для того, чтобы это сделать, нам необходимо сгруппировать строки по номенклатуре, а количество и сумму просуммировать. Сделать это несложно через запрос. С использованием конструктора запроса с обработкой результата это занимает пару минут. Ниже представлен скорректированный код процедуры проведения документа "Приходная накладная".
Процедура ОбработкаПроведения(Отказ, Режим)
Движения.ОстаткиНоменклатуры.Записывать = Истина;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПриходнаяНакладнаяСписокНоменклатуры.Номенклатура КАК Номенклатура,
| СУММА(ПриходнаяНакладнаяСписокНоменклатуры.Количество) КАК Количество,
| СУММА(ПриходнаяНакладнаяСписокНоменклатуры.Сумма) КАК Сумма
|ИЗ
| Документ.ПриходнаяНакладная.СписокНоменклатуры КАК ПриходнаяНакладнаяСписокНоменклатуры
|ГДЕ
| ПриходнаяНакладнаяСписокНоменклатуры.Ссылка = &Ссылка
|
|СГРУППИРОВАТЬ ПО
| ПриходнаяНакладнаяСписокНоменклатуры.Номенклатура";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
Движение = Движения.ОстаткиНоменклатуры.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Приход;
Движение.Период = Дата;
Движение.Номенклатура = ВыборкаДетальныеЗаписи.Номенклатура;
Движение.Количество = ВыборкаДетальныеЗаписи.Количество;
Движение.Сумма = ВыборкаДетальныеЗаписи.Сумма;
КонецЦикла;
//{{__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
// Данный фрагмент построен конструктором.
// При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!
//}}__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
КонецПроцедуры
Документ "Приходная накладная" готов. Товар купили, теперь пора продавать. Но прежде чем продавать надо остановиться на понятии "Себестоимость".
Что такое себестоимость? Этот вопрос довольно сложный для новичков, поэтому нужно разобраться.
Давайте порассуждаем. О себестоимости говорят, когда нужно понять сколько заработала компания на продаже товаров. Можно даже написать формулу
"Прибыль" = "Сумма продаж" - "Себестоимость".
Себестоимость - сумма денег, которую мы потратили на товар. Так как проданный товар мы могли покупать давно и несколько раз по разным ценам, то взять в качестве себестоимости последнюю закупочную цену является упрощением.
Предположим, мы купили 6 шт. товара по 100 руб. - потратили 600 руб (приходная накладная №1). Потом мы продали 5 шт. (расходная накладная №1). Списываем себестоимость по формуле:
Себестоимость (сумма списания) = Сумма остатка / Количество остатка * Количество списания = 600 / 6 * 5 = 500 руб.
У нас осталось 1 шт. товара на сумму 100 руб. Мы купили еще этого же товара 2 шт. по более высокой цене 200 руб. - потратили 400 руб (приходная накладная №2). Теперь у нас 3 шт. товара на сумму 500 руб.
Давайте продадим 2 шт (расходная накладная №2). Сколько себестоимости списываем? Ответ на этот вопрос теперь зависит от способа списания себестоимости. Рассмотрим 3 варианта.
По средней:
Себестоимость = Сумма остатка / Количество остатка * Количество списания = 500 руб. / 3 шт. * 2 шт. = 166,66 руб. * 2 шт. = 333,32 руб.
По партиям начиная с первой (FIFO):
Приходная накладная №1
Себестоимость = Сумма остатка приходной накладной №1 / Количество остатка приходной накладной 1 * Количество списания = 100 руб. / 1 шт. * 1 шт. = 100 руб. * 1 шт. = 100 руб. (не хватает еще 1 шт., поэтому переходим к след. накладной)
Приходная накладная №2
Себестоимость = Сумма остатка приходной накладной №2 / Количество остатка приходной накладной 2 * Количество списания = 400 руб. / 2 шт. * 1 шт. = 200 руб. * 1 шт. = 200 руб.
Итого: 100 руб. + 200 руб. = 300 руб.
По партиям начиная с последней (LIFO):
Приходная накладная №2
Себестоимость = Сумма остатка приходной накладной №2 / Количество остатка приходной накладной 2 * Количество списания = 400 руб. / 2 шт. * 2 шт. = 200 руб. * 2 шт. = 400 руб. (количества хватило, поэтому к следующей накладной не переходим)
Как видите, в зависимости от способа списания себестоимости (учетной политики) получаем разные суммы. Теперь, когда с себестоимостью разобрались, переходим к продаже.
Расходная накладная (списание себестоимости по средней)
Для списания товара и себестоимости нам потребуется "Номенклатура" и "Количество" из табличной части документа "Расходная накладная". Ниже настройка конструктора движений документа "Расходная накладная" и сформированный им код.
Процедура ОбработкаПроведения(Отказ, Режим)
//{{__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
// Данный фрагмент построен конструктором.
// При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!
// регистр ОстаткиНоменклатуры Расход
Движения.ОстаткиНоменклатуры.Записывать = Истина;
Для Каждого ТекСтрокаСписокНоменклатуры Из СписокНоменклатуры Цикл
Движение = Движения.ОстаткиНоменклатуры.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
Движение.Период = Дата;
Движение.Номенклатура = ТекСтрокаСписокНоменклатуры.Номенклатура;
Движение.Количество = ТекСтрокаСписокНоменклатуры.Количество;
Движение.Сумма = СуммаСписания;
КонецЦикла;
//}}__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
КонецПроцедуры
Сформированный конструктором код можно использовать только как заготовку, ведь для проведения нам необходимо рассчитать себестоимость товаров. Кроме того, нужно проверить хватит ли товара.
Первым делом сдвигаем вниз конструкцию //{{__КОНСТРУКТОР конструктора движений, внутри которой будет все затираться при повторном вызове конструктора. Далее в самом начале процедуры очищаем движения документы на случай, если он перепроводится, путем записи пустого набора. Выставляем флаг "Записывать", для тех регистров, куда будем записывать. Затем с помощью конструктора запроса с обработкой результата сформируем выборку для контроля остатка и расчета себестоимости. Создание движений переносим в цикл обхода детальных записей запроса (ТекСтрокаСписокНоменклатуры меняем на ВыборкаДетальныеЗаписи), контролируем остатки и рассчитываем себестоимость. Вот что должно получиться.
Движения.ОстаткиНоменклатуры.Записать();
Движения.ОстаткиНоменклатуры.Записывать = Истина;
//{{КОНСТРУКТОР_ЗАПРОСА_С_ОБРАБОТКОЙ_РЕЗУЛЬТАТА
// Данный фрагмент построен конструктором.
// При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| РасходнаяНакладнаяСписокНоменклатуры.Номенклатура КАК Номенклатура,
| СУММА(РасходнаяНакладнаяСписокНоменклатуры.Количество) КАК КоличествоТЧ
|ПОМЕСТИТЬ ТЧ
|ИЗ
| Документ.РасходнаяНакладная.СписокНоменклатуры КАК РасходнаяНакладнаяСписокНоменклатуры
|ГДЕ
| РасходнаяНакладнаяСписокНоменклатуры.Ссылка = &Ссылка
|
|СГРУППИРОВАТЬ ПО
| РасходнаяНакладнаяСписокНоменклатуры.Номенклатура
|
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ТЧ.Номенклатура КАК Номенклатура,
| ТЧ.Номенклатура.Представление КАК НоменклатураПредставление,
| ТЧ.КоличествоТЧ КАК КоличествоТЧ,
| ЕСТЬNULL(ОстаткиНоменклатурыОстатки.КоличествоОстаток, 0) КАК КоличествоОстаток,
| ЕСТЬNULL(ОстаткиНоменклатурыОстатки.СуммаОстаток, 0) КАК СуммаОстаток
|ИЗ
| ТЧ КАК ТЧ
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиНоменклатуры.Остатки(
| &МоментВремени,
| Номенклатура В
| (ВЫБРАТЬ
| ТЧ.Номенклатура КАК Номенклатура
| ИЗ
| ТЧ КАК ТЧ)) КАК ОстаткиНоменклатурыОстатки
| ПО ТЧ.Номенклатура = ОстаткиНоменклатурыОстатки.Номенклатура";
Запрос.УстановитьПараметр("МоментВремени", МоментВремени());
Запрос.УстановитьПараметр("Ссылка", Ссылка);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
Если ВыборкаДетальныеЗаписи.КоличествоТЧ > ВыборкаДетальныеЗаписи.КоличествоОстаток Тогда
Сообщить("По номенклатуре " + ВыборкаДетальныеЗаписи.НоменклатураПредставление +
"недостаточно товара. Остаток " + ВыборкаДетальныеЗаписи.КоличествоОстаток);
Отказ = Истина;
Продолжить;
КонецЕсли;
Если не Отказ Тогда
Движение = Движения.ОстаткиНоменклатуры.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
Движение.Период = Дата;
Движение.Номенклатура = ВыборкаДетальныеЗаписи.Номенклатура;
Движение.Количество = ВыборкаДетальныеЗаписи.КоличествоТЧ;
Движение.Сумма = ВыборкаДетальныеЗаписи.СуммаОстаток/ВыборкаДетальныеЗаписи.КоличествоОстаток*ВыборкаДетальныеЗаписи.КоличествоТЧ;
КонецЕсли;
КонецЦикла;
//}}КОНСТРУКТОР_ЗАПРОСА_С_ОБРАБОТКОЙ_РЕЗУЛЬТАТА
//{{__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
// Данный фрагмент построен конструктором.
// При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!
//}}__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
В первом запросе создаем временную таблицу "ТЧ" с данными табличной части документа "Расходная накладная". Данные сгруппированы по номенклатуре, количество просуммировано для избавления от дублей номенклатуры в строках. Не забываем индексировать поля, по которым будем соединять таблицы в дальнейшем.
Во втором запросе получаем остатки товара из виртуальной таблицы "ОстаткиНоменклатуры.Остатки". Обязательно заполняем параметры виртуальной таблицы. Этими параметрами задаем отбор по табличной части и период. Важно, что в качестве периода мы передаем не просто дату документа, а момент времени, содержащий дату документа и сам документ. Ведь дата документа задается с точностью до секунды. Представьте, что за эту секунду было проведено несколько документов, влияющих на остатки. Причем несколько документов до нашей расходной накладной, а остальные после. Метод документа МоментВремени() позволяет получить точное "расположение" нашего документа на оси времени.
Так как в запросе левое соединение c виртуальной таблицей ОстаткиНоменклатуры.Остатки, то проверяем на NULL её поля (КоличествоОстаток и СуммаОстаток).
Обратите внимание на поле запроса "Номенклатура.Представление". Мы используем его при выводе ошибки. Но ведь можно использовать и просто поле "Номенклатура", содержащее ссылку. Дело в том, что в момент вывода сообщения система имея только ссылку вынуждена сделать запрос к базе данных, чтобы получить Представление для вывода в виде строки. Получается запрос в цикле, это не оптимально.
В цикле проверяем наличие товара, и если его достаточно, то формируем движения. Согласно формуле рассчитываем себестоимость:
Движение.Сумма = ВыборкаДетальныеЗаписи.СуммаОстаток/ВыборкаДетальныеЗаписи.КоличествоОстаток*ВыборкаДетальныеЗаписи.КоличествоТЧ;
Код работает, себестоимость списывается, но это еще не все. Необходимо учесть проблему копеек и установить блокировку данных. Дополнив и подправив код получаем окончательный вариант проведения документа "Расходная накладная" со списанием себестоимости по-средней.
Процедура ОбработкаПроведения(Отказ, Режим)
Движения.ОстаткиНоменклатуры.Записать();
Движения.ОстаткиНоменклатуры.Записывать = Истина;
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ОстаткиНоменклатуры");
//ЭлементБлокировки.УстановитьЗначение("Качество", Справочники.Качество.НайтиПоКоду("1"));
//ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.ИсточникДанных = СписокНоменклатуры;
ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура", "Номенклатура");
//ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Склад", "Склад");
Блокировка.Заблокировать();
//{{КОНСТРУКТОР_ЗАПРОСА_С_ОБРАБОТКОЙ_РЕЗУЛЬТАТА
// Данный фрагмент построен конструктором.
// При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| РасходнаяНакладнаяСписокНоменклатуры.Номенклатура КАК Номенклатура,
| СУММА(РасходнаяНакладнаяСписокНоменклатуры.Количество) КАК КоличествоТЧ
|ПОМЕСТИТЬ ТЧ
|ИЗ
| Документ.РасходнаяНакладная.СписокНоменклатуры КАК РасходнаяНакладнаяСписокНоменклатуры
|ГДЕ
| РасходнаяНакладнаяСписокНоменклатуры.Ссылка = &Ссылка
|
|СГРУППИРОВАТЬ ПО
| РасходнаяНакладнаяСписокНоменклатуры.Номенклатура
|
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ТЧ.Номенклатура КАК Номенклатура,
| ТЧ.Номенклатура.Представление КАК НоменклатураПредставление,
| ТЧ.КоличествоТЧ КАК КоличествоТЧ,
| ЕСТЬNULL(ОстаткиНоменклатурыОстатки.КоличествоОстаток, 0) КАК КоличествоОстаток,
| ЕСТЬNULL(ОстаткиНоменклатурыОстатки.СуммаОстаток, 0) КАК СуммаОстаток
|ИЗ
| ТЧ КАК ТЧ
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиНоменклатуры.Остатки(
| &МоментВремени,
| Номенклатура В
| (ВЫБРАТЬ
| ТЧ.Номенклатура КАК Номенклатура
| ИЗ
| ТЧ КАК ТЧ)) КАК ОстаткиНоменклатурыОстатки
| ПО ТЧ.Номенклатура = ОстаткиНоменклатурыОстатки.Номенклатура";
Запрос.УстановитьПараметр("МоментВремени", МоментВремени());
Запрос.УстановитьПараметр("Ссылка", Ссылка);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
Если ВыборкаДетальныеЗаписи.КоличествоТЧ > ВыборкаДетальныеЗаписи.КоличествоОстаток Тогда
Сообщить("По номенклатуре " + ВыборкаДетальныеЗаписи.НоменклатураПредставление +
" недостаточно товара. Остаток " + ВыборкаДетальныеЗаписи.КоличествоОстаток);
Отказ = Истина;
Продолжить;
КонецЕсли;
Если не Отказ Тогда
Движение = Движения.ОстаткиНоменклатуры.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
Движение.Период = Дата;
Движение.Номенклатура = ВыборкаДетальныеЗаписи.Номенклатура;
Движение.Количество = ВыборкаДетальныеЗаписи.КоличествоТЧ;
Движение.Сумма = ВыборкаДетальныеЗаписи.КоличествоТЧ/ВыборкаДетальныеЗаписи.КоличествоОстаток*ВыборкаДетальныеЗаписи.СуммаОстаток;
КонецЕсли;
КонецЦикла;
//}}КОНСТРУКТОР_ЗАПРОСА_С_ОБРАБОТКОЙ_РЕЗУЛЬТАТА
//{{__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
// Данный фрагмент построен конструктором.
// При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!
//}}__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
КонецПроцедуры
Предположим, что расходной накладной №2 мы продали все оставшиеся 3 товара. Себестоимость = Сумма остатка / Количество остатка * Количество списания = 500 руб. / 3 шт. * 3 шт. = 166,66 руб. * 3 шт. = 499,98 руб.
После списания у нас остается нулевое количество товара и ненулевая себестоимость = 2 копейки. Другими словами остатки по регистру по количеству и сумме одновременно не выйдут в ноль. Решение проблемы довольно простое - в формуле поменять местами Сумму остатка и Количество списания.
Себестоимость = Количество списания / Количество остатка * Сумма остатка = 3 шт. / 3 шт. * 500 руб.= 1 * 500 = 500 руб.
При расчете себестоимости мы сначала читаем данные о суммовом и количественном остатке из регистра накопления "ОстаткиНоменклатуры", а затем используем их для расчета и формирования движений в этот же регистр. Если в процессе этой процедуры кто-то изменит данные по рассчитываемому товару, то результат расчета себестоимости и сформированные движения будут неверны.
Чтобы этого не произошло, необходимо заблокировать записи регистра на время транзакции. Но тут возможна другая проблема. Если заблокировать весь регистр, то при одновременной работе пользователи столкнутся с проблемой ожидания освобождения блокировки. В связи с этим лучше блокировать только те записи, которые касаются рассчитываемого товара.
Переходим к практике. В нашем случае учет ведется в разрезе товаров, поэтому блокировать регистр будем по товарам. Чтобы установить управляемые блокировки воспользуемся синтаксис-помощником, нажав F1. Ищем "БлокировкаДанных" там есть пример кода, вот как это выглядит в синтаксис-помощнике.
Этот код можно взять за основу. Осталось изменить наименование регистра "РегистрНакопления.ОстаткиНоменклатуры" и указать элемент блокировки. В нашем случае товар указывается в табличной части, поэтому используем в качестве источника данных табличную часть "Список номенклатуры" и поле "Номенклатура".
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ОстаткиНоменклатуры");
//ЭлементБлокировки.УстановитьЗначение("Качество", Справочники.Качество.НайтиПоКоду("1"));
//ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.ИсточникДанных = СписокНоменклатуры;
ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура", "Номенклатура");
//ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Склад", "Склад");
Блокировка.Заблокировать();
Чтобы этот код заработал в свойствах конфигурации необходимо сменить "Режим управления блокировкой" на "Управляемый".
Расходная накладная (списание себестоимости по партиям FIFO LIFO)
Пример расчета себестоимости по партиям рассмотрен в разделе Себестоимость. Перейдем к практике.
По структуре хранения данных необходимо дополнительное изменение - в регистр накопления "ОстаткиНоменклатуры" необходимо добавить измерение "Партия" с типом "ДокументСсылка.ПриходнаяНакладная".
В процедуре "ОбработкаПроведения" документа "Приходная накладная" необходимо заполнить новое измерение регистра в цикле Движение.Партия = Ссылка. Перейдем к документу "Расходная накладная".
Алгоритм списания себестоимости по партиям отличается от списания по средней тем, что списывать товар и сумму теперь необходимо не только в разрезе товара, но и последовательно в разрезе партий этого товара. Какие то партии списать целиком, а какие-то частично. Для этого необходимо использовать обход результата запроса по группировкам (вложенный цикл). Сначала будем обходить итоги по Номенклатуре, затем детальные записи с партиями в определенном порядке.
Важно отметить, что при списании по партиям вместо Партии может быть любой разрез: Фирма, Склад, Срок годности и т.п. Алгоритм при этом не меняется. Необходимо определиться с разрезом и порядком обхода. Для партий порядок определяется учетной политикой (FIFO - дата партии по возрастанию, LIFO - дата партии по убыванию), для Склада может быть какой-нибудь приоритет (числовой реквизит), Срок годности логично списывать по возрастанию.
В процедуре "ОбработкаПроведения" документа "РасходнаяНакладная" необходимо изменить текст запроса и алгоритм его обхода. Для этого необходимо щелкнуть по тексту запроса правой кнопкой мыши и вызвать в контекстном меню пункт "Конструктор запроса с обработкой результата", добавляем в поля второго запроса партию из виртуальной таблицы "ОстаткиНоменклатурыОстатки", на закладке Порядок сортируем по возрастанию по моменту времени партии (в нашем примере рассмотрим FIFO, для LIFO сортировать нужно по убыванию), на закладке Итоги добавляем группировочное поле "Номенклатура" и итоговые поля "КоличествоОстаток", "СуммаОстаток" и "КоличествоТЧ". "КоличествоОстаток" и "СуммаОстаток" вычисляем как сумма вложенных строк, а "КоличествоТЧ" как максимум. Вот что получилось.
//{{КОНСТРУКТОР_ЗАПРОСА_С_ОБРАБОТКОЙ_РЕЗУЛЬТАТА
// Данный фрагмент построен конструктором.
// При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| РасходнаяНакладнаяСписокНоменклатуры.Номенклатура КАК Номенклатура,
| СУММА(РасходнаяНакладнаяСписокНоменклатуры.Количество) КАК КоличествоТЧ
|ПОМЕСТИТЬ ТЧ
|ИЗ
| Документ.РасходнаяНакладная.СписокНоменклатуры КАК РасходнаяНакладнаяСписокНоменклатуры
|ГДЕ
| РасходнаяНакладнаяСписокНоменклатуры.Ссылка = &Ссылка
|
|СГРУППИРОВАТЬ ПО
| РасходнаяНакладнаяСписокНоменклатуры.Номенклатура
|
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ТЧ.Номенклатура КАК Номенклатура,
| ТЧ.Номенклатура.Представление КАК НоменклатураПредставление,
| ТЧ.КоличествоТЧ КАК КоличествоТЧ,
| ЕСТЬNULL(ОстаткиНоменклатурыОстатки.КоличествоОстаток, 0) КАК КоличествоОстаток,
| ЕСТЬNULL(ОстаткиНоменклатурыОстатки.СуммаОстаток, 0) КАК СуммаОстаток,
| ОстаткиНоменклатурыОстатки.Партия КАК Партия
|ИЗ
| ТЧ КАК ТЧ
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиНоменклатуры.Остатки(
| &МоментВремени,
| Номенклатура В
| (ВЫБРАТЬ
| ТЧ.Номенклатура КАК Номенклатура
| ИЗ
| ТЧ КАК ТЧ)) КАК ОстаткиНоменклатурыОстатки
| ПО ТЧ.Номенклатура = ОстаткиНоменклатурыОстатки.Номенклатура
|
|УПОРЯДОЧИТЬ ПО
| ОстаткиНоменклатурыОстатки.Партия.МоментВремени
|ИТОГИ
| МАКСИМУМ(КоличествоТЧ),
| СУММА(КоличествоОстаток),
| СУММА(СуммаОстаток)
|ПО
| Номенклатура";
Запрос.УстановитьПараметр("МоментВремени", МоментВремени);
Запрос.УстановитьПараметр("Ссылка", Ссылка);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаНоменклатура = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаНоменклатура.Следующий() Цикл
// Вставить обработку выборки ВыборкаНоменклатура
ВыборкаДетальныеЗаписи = ВыборкаНоменклатура.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
// Вставить обработку выборки ВыборкаДетальныеЗаписи
КонецЦикла;
КонецЦикла;
//}}КОНСТРУКТОР_ЗАПРОСА_С_ОБРАБОТКОЙ_РЕЗУЛЬТАТА
Обходя группировку "Номенклатура" будем проверять достаточно ли этого товара. Если достаточно, то будем обходить детальные записи и формировать движения по партиям. Перед обходом детальных записей запишем в переменную "ОсталосьСписать" общее количество требуемого товара, а при списании будем отнимать от нее списываемое количество. Цикл продолжаем пока не обнулим переменную "ОсталосьСписать".Обходя партии будем проверять хватает ли остатка в партии. Если хватает, то списываем нужное количество и переходим к следующему товару. Если не хватает, то последовательно списываем партии пока не наберем нужное количество. Вот пример цикла обхода.
Движения.ОстаткиНоменклатуры.Записывать = Истина;
ВыборкаНоменклатура = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаНоменклатура.Следующий() Цикл
// Вставить обработку выборки ВыборкаНоменклатура
Если ВыборкаНоменклатура.КоличествоТЧ > ВыборкаНоменклатура.КоличествоОстаток Тогда
Сообщить("По номенклатуре " + ВыборкаНоменклатура.НоменклатураПредставление +
"недостаточно товара. Остаток " + ВыборкаНоменклатура.КоличествоОстаток);
Отказ = Истина;
Продолжить;
КонецЕсли;
Если не Отказ Тогда
ВыборкаДетальныеЗаписи = ВыборкаНоменклатура.Выбрать();
ОсталосьСписать = ВыборкаНоменклатура.КоличествоТЧ;
Пока ВыборкаДетальныеЗаписи.Следующий() и ОсталосьСписать > 0 Цикл
// Вставить обработку выборки ВыборкаДетальныеЗаписи
Движение = Движения.ОстаткиНоменклатуры.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
Движение.Период = Дата;
Движение.Номенклатура = ВыборкаНоменклатура.Номенклатура;
Движение.Партия = ВыборкаДетальныеЗаписи.Партия;
Если ОсталосьСписать>ВыборкаДетальныеЗаписи.КоличествоОстаток Тогда
Движение.Количество = ВыборкаДетальныеЗаписи.КоличествоОстаток;
Иначе
Движение.Количество = ОсталосьСписать;
КонецЕсли;
ОсталосьСписать = ОсталосьСписать - Движение.Количество;
// расчет себестоимости + проблема копеек
Движение.Сумма = Движение.Количество/ВыборкаДетальныеЗаписи.КоличествоОстаток*ВыборкаДетальныеЗаписи.СуммаОстаток;
КонецЦикла;
КонецЕсли;
КонецЦикла;
Вот движения, сформированные накладными. Можно свериться с рассуждениями в разделе Себестоимость.
Период | Регистратор | Номер строки | Номенклатура | Партия | Количество | Сумма |
01.01.2020 0:00:00 | Приходная накладная 000000001 от 01.01.2020 0:00:00 | 1 | Товар | Приходная накладная 000000001 от 01.01.2020 0:00:00 | 6 | 600,00 |
02.01.2020 12:00:00 | Расходная накладная 000000001 от 02.01.2020 12:00:00 | 1 | Товар | Приходная накладная 000000001 от 01.01.2020 0:00:00 | 5 | 500,00 |
03.01.2020 12:00:00 | Приходная накладная 000000002 от 03.01.2020 12:00:00 | 1 | Товар | Приходная накладная 000000002 от 03.01.2020 12:00:00 | 2 | 400,00 |
04.01.2020 12:00:00 | Расходная накладная 000000002 от 04.01.2020 12:00:00 | 1 | Товар | Приходная накладная 000000001 от 01.01.2020 0:00:00 | 1 | 100,00 |
04.01.2020 12:00:00 | Расходная накладная 000000002 от 04.01.2020 12:00:00 | 2 | Товар | Приходная накладная 000000002 от 03.01.2020 12:00:00 | 1 | 200,00 |
"Старая" и "новая" методики контроля остатков
Если документ что-то списывает, то по-умолчанию нужно проверять, хватит ли того, что он списывает.
В рассмотренных ранее процедурах проведения документа "Расходная накладная" мы рассчитывали себестоимость и применяли, так называемую, "старую" методику. Её необходимо применять в случаях, когда формируемые документом движения зависят от других данных информационной базы. К примеру, сумма себестоимости, списываемая документом, зависит от остатка этой себестоимости на момент времени этого документа. "Старая" методика заключается в том, что сначала мы проверяем наличие списываемого товара, долга, аванса, заказа и т.п., а затем при недостаче сообщаем об ошибке и запрещаем проведение, а при наличии формируем движения и разрешаем проведение.
Алгоритм "старой" методики:
1. Очищаем движения документа путем записи пустого набора. Это необходимо для того, чтобы обеспечить чтение данных без учета существующих движений текущего документа.
К примеру, в некоторых решениях встречается следующая ситуация. Перепроводим документ - не проводится, ошибка "недостаточно товара". Выясняешь, каким документом списан товар? Оказывается, этим же. Если отменить проведение, а потом провести - все хорошо.
Движения.ОстаткиНоменклатуры.Записать();
2. Укажем по каким регистрам движения нужно записывать. Если не сделать, то документ проведется без ошибок, но движений никаких не запишется.
Движения.ОстаткиНоменклатуры.Записывать = Истина;
3. Заблокируем записи регистра, от которых зависит расчет себестоимости документа. Подробнее в разделе Блокировка данных. За основу берем пример синтаксис-помощника:
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ТоварыНаСкладах");
ЭлементБлокировки.УстановитьЗначение("Качество", Справочники.Качество.НайтиПоКоду("1"));
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.ИсточникДанных = ДокументОбъект.ВозвратнаяТара;
ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура", "Номенклатура");
ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Склад", "Склад");
Блокировка.Заблокировать();
4. Формируем запрос данных для расчета движений (например, расчет себестоимости, зачет аванса). Для этого необходимо пользоваться "Конструктором запроса с обработкой результата".
5. В процессе обхода записей результата запроса проверяем наличие товара, долга, аванса, заказа и т.п. Если достаточно, то формируем набор записей. Т.к. флаг "Записывать" установлен в п.2, то движения запишутся после выполнения процедуры "ОбработкаПроведения".
Если недостаточно, то выводим ошибку и устанавливаем признаку проведения "Отказ" значение ИСТИНА, проведение документа выполнено не будет, движения не запишутся.
Отказ = Истина;
Теперь рассмотрим случай, когда при проведении ничего рассчитывать не нужно. К примеру, не нужно зачитывать аванс, считать себестоимость. Нужно просто списать товар в том количестве, которое указано в документе. В движениях будут только данные документа. В этом случае применяется "новая" методика. "Новая" методика заключается в том, что сначала мы формируем и записываем движения документа, а потом проверяем, не сформировал ли он отрицательных остатков. Если появились отрицательные остатки, то не разрешаем провести документ.
Алгоритм "новой" методики:
1. У
кажем по каким регистрам движения нужно записывать. Если не сделать, то движения не запишутся.
Движения.ОстаткиНоменклатуры.Записывать = Истина;
2. В цикле обходим табличную часть документа или результат запроса по табличной части, если требуется ее преобразование (группировка, отбор), и формируем набор записей.
3. Установим свойство "БлокироватьДляИзменения" у набора записей. Это отключит режим разделения итогов регистра, что заблокирует другому документу запись данных с одинаковым набором измерений.
Движения.ОстаткиНоменклатуры.БлокироватьДляИзменения = Истина;
4. Запишем движения документа.
Движения.Записать();
После этой строки кода флаг "Записывать", установленный в п.1 сбросится и повторной записи движений по окончании процедуры "ОбработкаПроведения" не произойдет.
5. Создаем запрос, показывающий отрицательные остатки регистра по наборам измерений документа. Если отрицательные остатки есть, то выводим ошибку и запрещаем проведение документа (движения тоже отменятся).
Отказ = Истина;
Расходная накладная (без себестоимости по новой методике)
Так как учет себестоимости не нужен, удалим ресурс Сумма у регистра накопления "ОстаткиНоменклатуры" и из движений документа "ПриходнаяНакладная".
Сформируем по-новой процедуру "ОбработкаПроведения" документа "РасходнаяНакладная" по алгоритму новой методики. Начнем, как обычно, с конструктора движений.
Процедура ОбработкаПроведения(Отказ, Режим)
//{{__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
// Данный фрагмент построен конструктором.
// При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!
// регистр ОстаткиНоменклатуры Расход
Движения.ОстаткиНоменклатуры.Записывать = Истина;
Для Каждого ТекСтрокаСписокНоменклатуры Из СписокНоменклатуры Цикл
Движение = Движения.ОстаткиНоменклатуры.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
Движение.Период = Дата;
Движение.Номенклатура = ТекСтрокаСписокНоменклатуры.Номенклатура;
Движение.Количество = ТекСтрокаСписокНоменклатуры.Количество;
КонецЦикла;
//}}__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
КонецПроцедуры
Оставим сформированный конструктором код без изменений, хотя его можно улучшить аналогично приходной накладной.
Флаг записи в регистр выставлен, наборы записей сформированы. Далее согласно алгоритму необходимо установить у набора свойство "БлокироватьДляИзменения" в значение ИСТИНА и записать движения.
Движения.ОстаткиНоменклатуры.БлокироватьДляИзменения = Истина;
Движения.Записать();
Осталось проверить отрицательные остатки по товарам документа и принять решение, разрешать проводить документ или нет. Воспользуемся конструктором с обработкой результата, сделаем запрос остатков из виртуальной таблицы регистра сведений ОстаткиНоменклатуры.Остатки по номенклатуре документа на момент времени документа.
//{{КОНСТРУКТОР_ЗАПРОСА_С_ОБРАБОТКОЙ_РЕЗУЛЬТАТА
// Данный фрагмент построен конструктором.
// При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ОстаткиНоменклатурыОстатки.Номенклатура.Представление КАК НоменклатураПредставление,
| ОстаткиНоменклатурыОстатки.Номенклатура КАК Номенклатура,
| ОстаткиНоменклатурыОстатки.КоличествоОстаток КАК КоличествоОстаток
|ИЗ
| РегистрНакопления.ОстаткиНоменклатуры.Остатки(
| &МоментВремени,
| Номенклатура В
| (ВЫБРАТЬ
| РасходнаяНакладнаяСписокНоменклатуры.Номенклатура КАК Номенклатура
| ИЗ
| Документ.РасходнаяНакладная.СписокНоменклатуры КАК РасходнаяНакладнаяСписокНоменклатуры
| ГДЕ
| РасходнаяНакладнаяСписокНоменклатуры.Ссылка = &Ссылка)) КАК ОстаткиНоменклатурыОстатки";
Запрос.УстановитьПараметр("МоментВремени", МоментВремени);
Запрос.УстановитьПараметр("Ссылка", Ссылка);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
// Вставить обработку выборки ВыборкаДетальныеЗаписи
КонецЦикла;
//}}КОНСТРУКТОР_ЗАПРОСА_С_ОБРАБОТКОЙ_РЕЗУЛЬТАТА
Немного остановимся на заполнении параметра запроса "МоментВремени". Ранее мы обсуждали метод документа МоментВремени(), который в отличие от даты получает дату+ссылку, то есть более точное положение документа на временной оси. Мы использовали его в качестве параметра запроса остатков для проверки, хватит ли их для списания.
Сейчас мы уже списали товары и хотим узнать, не стали ли остатки отрицательными. Если мы в качестве параметра передадим МоментВремени(), то получим остатки до движений текущего документа, а нам надо после. Для этого предназначен объект Граница, позволяющий получить момент времени включая или исключая движения документа.
Запрос.УстановитьПараметр("МоментВремени", Новый Граница(МоментВремени(),ВидГраницы.Включая));
Для скорости можно написать просто Новый Граница(МоментВремени()), второй параметр по-умолчанию включая.
Осталось проверить отрицательные остатки в цикле и если их недостаточно, то сообщить о недостаче и отказать в проведении. Вот получившийся код процедуры "ОбработкаПроведения" документа "РасходнаяНакладная" по "новой" методике.
Процедура ОбработкаПроведения(Отказ, Режим)
Движения.ОстаткиНоменклатуры.Записывать = Истина;
Для Каждого ТекСтрокаСписокНоменклатуры Из СписокНоменклатуры Цикл
Движение = Движения.ОстаткиНоменклатуры.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
Движение.Период = Дата;
Движение.Номенклатура = ТекСтрокаСписокНоменклатуры.Номенклатура;
Движение.Количество = ТекСтрокаСписокНоменклатуры.Количество;
КонецЦикла;
Движения.ОстаткиНоменклатуры.БлокироватьДляИзменения = Истина;
Движения.Записать();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ОстаткиНоменклатурыОстатки.Номенклатура.Представление КАК НоменклатураПредставление,
| ОстаткиНоменклатурыОстатки.Номенклатура КАК Номенклатура,
| ОстаткиНоменклатурыОстатки.КоличествоОстаток КАК КоличествоОстаток
|ИЗ
| РегистрНакопления.ОстаткиНоменклатуры.Остатки(
| &МоментВремени,
| Номенклатура В
| (ВЫБРАТЬ
| РасходнаяНакладнаяСписокНоменклатуры.Номенклатура КАК Номенклатура
| ИЗ
| Документ.РасходнаяНакладная.СписокНоменклатуры КАК РасходнаяНакладнаяСписокНоменклатуры
| ГДЕ
| РасходнаяНакладнаяСписокНоменклатуры.Ссылка = &Ссылка)) КАК ОстаткиНоменклатурыОстатки";
Запрос.УстановитьПараметр("МоментВремени", Новый Граница(МоментВремени(),ВидГраницы.Включая));
Запрос.УстановитьПараметр("Ссылка", Ссылка);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
Если ВыборкаДетальныеЗаписи.КоличествоОстаток < 0 Тогда
Сообщить("Превышение по товару " + ВыборкаДетальныеЗаписи.НоменклатураПредставление + " на " + ВыборкаДетальныеЗаписи.КоличествоОстаток);
Отказ = Истина;
КонецЕсли;
КонецЦикла;
//{{__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
// Данный фрагмент построен конструктором.
// При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!
//}}__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
КонецПроцедуры
Решение задач бухгалтерского учета
Возьмем следующую задачу.
Необходимо реализовать возможность закупки и продажи редких товаров. Поступление таких товаров осуществляется документом «Приходная накладная». В документе поступления каждая единица товара оформляется отдельной строкой (с количеством равным 1). Каждой позиции закупаемого товара присваивается
уникальный инвентарный номер. Одним документом может оформляться поступление нескольких одинаковых товаров, но с разными инвентарными номерами.
Документ «Приходная накладная» реализует следующую проводку:
Дт «Товары» - Кт «Поставщики» на сумму закупаемого товара
Продажа товара регистрируется документом «Расходная накладная». При продаже инвентарный номер вводится в табличную часть документа вручную. При проведении документа должен производиться контроль наличия указанного в документе товара (по указанному инвентарному номеру). Себестоимость списываемого товара определяется как средняя по номенклатурной позиции по всем ее инвентарным номерам.
Документ «Расходная накладная» реализует следующие проводки:
Дт «Прибыли и убытки» - Кт «Товары» на сумму себестоимости;
Дт «Покупатели» - Кт «Прибыли и убытки» на сумму в продажных ценах.
По данным бухгалтерского учета необходимо сформировать отчет, который за указанный интервал дат показывал бы данные о проданном товаре.
Продажи за период с 01.09.2020 по 30.09.2020
Товар | Инв. номер | Себестоимость | Продажа |
Скрипка | 0001 | 120 000 | 200 000 |
Барабан | 0002 | 51 000 | 70 000 |
Барабан | 5008 | 51 000 | 75 890 |
План счетов и регистр бухгалтерии
Бухгалтерский учет ведется в разрезе бухгалтерских счетов. Ниже представлены предопределенные счета плана счетов каркасной конфигурации.
Как видно, нужные счета уже есть, осталось их только настроить. План счетов - это разрезы учета, сами цифры будут храниться в регистре бухгалтерии, связанному с этим планом.
Создаем регистр бухгалтерии, назовем его аналогично плану счетов "Управленческий", выбираем соответствующий план счетов, ставим галку "Корреспонденция" т.к. у нас на всех счетах будет выполняться метод двойной записи.
В качестве регистраторов нашего регистра бухгалтерии "Управленческий" выберем документы "Приходная накладная" и "Расходная накладная". Переходим на вкладку "Данные", здесь необходимо создать нужные измерения и ресурсы. Лучше придерживаться "исторически сложившихся" норм и не изобретать велосипед. В частности, нам точно нужен ресурс "Сумма", поэтому смело его создаем и ставим галочку "Балансовый" т.к. сумма нужна нам на всех счетах.
Далее для учета товаров нам нужен ресурс "Количество". Галочку "Балансовый" не ставим т.к. количественный учет потребуется не на всех счетах. Из используемых в задаче счетов «Товары», «Поставщики», «Прибыли и убытки», "Покупатели" количественный учет требуется только на счете "Товары". Для настройки использования ресурса или измерения на конкретных счетах предназначены "Признаки учета", которые создаются в плане счетов, указываются в свойстве измерения или ресурса и выбираются в счете. В плане счетов на вкладке "Данные" создаем признак учета "Количественный" с типом булево, указываем его в ресурсе "Количество" нашего регистра бухгалтерии, далее в предопределенном счете "Товары" ставим галочку "Количественный".
Для решения нашей задачи требуются только ресурсы "Сумма" (балансовый) и "Количество" (признак учета "Количественный"), которые мы только что создали.
При решении других задач могут потребоваться измерения "Организация" (балансовое), "Валюта" (признак учета "Валютный") и ресурс "Валютная сумма" (признак учета "Валютный"). Других измерений создавать не нужно. Обратите внимание: если у ресурса или измерения ставим галочку "Балансовый", то признак учета указывать не нужно, и наоборот.
Когда бы говорим об обобщенных цифрах на бухгалтерских счетах (товаров на 100 тыс.), то мы говорим о синтетическом учете. Для любых счетов можно ввести разрезы аналитики, тогда обобщенные цифры можно будет расшифровать (мяса на 30 тыс., сыра на 10 тыс.). Учет в разрезе аналитик - аналитический учет.
По условию задачи нам нужно организовать учет товаров в инвентарных номеров, то есть в проводке по счету товары должна быть возможность указать не только товар, но и инвентарный номер. Для организации аналитического учета используются такие понятия, как вид субконто и субконто. Субконто (например, контрагент Иванов) – это один из объектов аналитического учета, а вид субконто (план видов характеристик) объединяет однородные объекты аналитического учета (например, справочник Контрагенты) вместе.
В каркасной конфигурации уже создан план видов характеристик "ВидыСубконто" с выбранным типом значения характеристик СправочникСсылка.Номенклатура и одним предопределенным элементом "Номенклатура".
Нам помимо номенклатуры нужно учитывать инвентарный номер на счете "Товары". Для этого создадим справочник "ИнвентарныеНомера", добавим реквизит "ИнвентарныйНомер" в табличной части "СписокНоменклатуры" документов "Приходная накладная" и "Расходная накладная", изменим тип значения характеристик плана "ВидыСубконто" на составной, включающий в себя СправочникСсылка.Номенклатура и СправочникСсылка.ИнвентарныеНомера и добавим предопределенный элемент плана "ИнвентарныйНомера".
Далее для плана счетов "Управленческий" в разделе "Субконто" выбираем вид субконто "ВидыСубконто" и максимальное количество субконто 2, ведь на счете "Товары" нам понадобится одновременно 2 разреза: "Номенклатура" и "ИнвентарныйНомер". Открываем счет "Товары" и добавляем эти субконто.
При проектировании регистров учета и плана счетов нужно читать не только условие задачи, но и анализировать данные, выводимые в отчете. Обратите внимание, что в отчете для каждого товара нужно будет отобразить сумму в продажных ценах. Это значит, что на счете "Покупатели" нужны суммы в разрезе номенклатуры и инвентарных номеров. Добавим субконто "Номенклатура" и "ИнвентарныеНомера" в счет "Покупатели".
Внимательно проверяем правильность установленных галочек в счете "Покупатели". Из условия и отчета не следует, что нам нужен количественный учет проданных товаров, поэтому признак учета "Количественный" не ставим. Признак учета субконто "Суммовой" нам нужен т.к. суммы в отчете нужны именно в разрезе субконто. Теперь по поводу галочек "Только обороты". Можно запомнить, что когда говорим о продажах, то нас обычно интересуют только обороты. Остатки по продажам не имеют смысла. Ставим для обоих субконто "только обороты". Это касается и регистра накопления "Продажи" в контексте задач оперативного учета, он должен быть вида "Обороты".
Настройка учета себестоимости (признаки учета субконто)
Осталось учесть условие "Себестоимость списываемого товара определяется как средняя по номенклатурной позиции по всем ее инвентарным номерам." Это условие означает, что если мы купили первый барабан с инвентарным номером 0002 за 50 руб., а второй с инвентарным номером "5008" за 52 руб., то при продаже любого барабана должна списаться себестоимость согласно формуле (50 + 52)/2 = 51 руб.
Для реализации такого учета нужно правильно составить алгоритм списания, но не менее важно отразить это в настройке плана счетов. Инвентарный номер - это субконто №2 счета "Товары". Нам не нужен учет суммы в разрезе этого субконто, поэтому его нужно отключить.
Для этого нам необходимо в плане счетов "Управленческий" в разделе "Субконто" создать признак учета субконто "Суммовой" с типом булево, указать его в ресурсе "Сумма" регистра бухгалтерии "Управленческий" и отключить его для субконто "ИнвентарныеНомера" в счете "Товары".
Если не сделать эту настройку, то при правильном алгоритме себестоимость рассчитается правильно, но перед экзаменатором придется оправдываться.
Проводка документа "Приходная накладная" указана в условии задачи:
Дт «Товары» - Кт «Поставщики» на сумму закупаемого товара.
Начинаем с конструктора движений, который всё заполнил автоматически.
При правильной настройке плана счетов и регистра бухгалтерии ошибиться здесь сложно. Обратите внимание, в проводке количество нам доступно только по дебету. Это связано с тем, что "Количество" не является балансовым ресурсам, а признак учета "Количественный" установлен только у счета "Товары", который в нашей проводке по дебету. Вот сформированный код.
Процедура ОбработкаПроведения(Отказ, Режим)
//{{__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
// Данный фрагмент построен конструктором.
// При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!
// регистр Управленческий
Движения.Управленческий.Записывать = Истина;
Для Каждого ТекСтрокаСписокНоменклатуры Из СписокНоменклатуры Цикл
Движение = Движения.Управленческий.Добавить();
Движение.СчетДт = ПланыСчетов.Управленческий.Товары;
Движение.СчетКт = ПланыСчетов.Управленческий.Поставщики;
Движение.Период = Дата;
Движение.Сумма = ТекСтрокаСписокНоменклатуры.Сумма;
Движение.КоличествоДт = ТекСтрокаСписокНоменклатуры.Количество;
Движение.СубконтоДт[ПланыВидовХарактеристик.ВидыСубконто.Номенклатура] = ТекСтрокаСписокНоменклатуры.Номенклатура;
Движение.СубконтоДт[ПланыВидовХарактеристик.ВидыСубконто.ИнвентарныеНомера] = ТекСтрокаСписокНоменклатуры.ИнвентарныйНомер;
КонецЦикла;
//}}__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
КонецПроцедуры
В задаче оперативного учета мы запросом свернули табличную часть по номенклатуре, здесь этого не требуется т.к. по условию задачи количество может быть только 1. Поэтому сформированного конструктором кода вполне достаточно. Не забываем только //{__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ сдвигать вниз, чтобы повторный вызов конструктора не затирал наш код.
Согласно условию у документа «Расходная накладная» 2 проводки:
Дт «Прибыли и убытки» - Кт «Товары» на сумму себестоимости;
Дт «Покупатели» - Кт «Прибыли и убытки» на сумму в продажных ценах.
Запускаем конструктор движений и для настройки двух проводок добавляем регистр бухгалтерии 2 раза.
Процедура ОбработкаПроведения(Отказ, Режим)
//{{__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
// Данный фрагмент построен конструктором.
// При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!
// регистр Управленческий
Движения.Управленческий.Записывать = Истина;
Для Каждого ТекСтрокаСписокНоменклатуры Из СписокНоменклатуры Цикл
Движение = Движения.Управленческий.Добавить();
Движение.СчетДт = ПланыСчетов.Управленческий.ПрибылиУбытки;
Движение.СчетКт = ПланыСчетов.Управленческий.Товары;
Движение.Период = Дата;
Движение.Сумма = ТекСтрокаСписокНоменклатуры.Сумма;
Движение.КоличествоКт = ТекСтрокаСписокНоменклатуры.Количество;
Движение.СубконтоКт[ПланыВидовХарактеристик.ВидыСубконто.Номенклатура] = ТекСтрокаСписокНоменклатуры.Номенклатура;
Движение.СубконтоКт[ПланыВидовХарактеристик.ВидыСубконто.ИнвентарныеНомера] = ТекСтрокаСписокНоменклатуры.ИнвентарныйНомер;
КонецЦикла;
// регистр Управленческий
Движения.Управленческий.Записывать = Истина;
Для Каждого ТекСтрокаСписокНоменклатуры Из СписокНоменклатуры Цикл
Движение = Движения.Управленческий.Добавить();
Движение.СчетДт = ПланыСчетов.Управленческий.Покупатели;
Движение.СчетКт = ПланыСчетов.Управленческий.ПрибылиУбытки;
Движение.Период = Дата;
Движение.Сумма = ТекСтрокаСписокНоменклатуры.Сумма;
Движение.СубконтоДт[ПланыВидовХарактеристик.ВидыСубконто.Номенклатура] = ТекСтрокаСписокНоменклатуры.Номенклатура;
Движение.СубконтоДт[ПланыВидовХарактеристик.ВидыСубконто.ИнвентарныеНомера] = ТекСтрокаСписокНоменклатуры.ИнвентарныйНомер;
КонецЦикла;
//}}__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
КонецПроцедуры
Нам придется полностью переписать код, но сформированные конструктором строки помогут сэкономить время.
Так как нам необходимо рассчитать себестоимость, то применять необходимо "старую" методику. Начнем по порядку по алгоритму. Первым делом очищаем движения и выставляем флаг записи в регистр.
Движения.Управленческий.Записать();
Движения.Управленческий.Записывать = Истина;
Далее нужно заблокировать записи регистра, от которых зависит расчет себестоимости.
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрБухгалтерии.Управленческий");
ЭлементБлокировки.УстановитьЗначение("Счет", ПланыСчетов.Управленческий.Товары);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.ИсточникДанных = СписокНоменклатуры;
ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Субконто1", "Номенклатура");
Блокировка.Заблокировать();
При настройке блокировки регистра бухгалтерии первым делом необходимо указать счет. В нашем случае счет "Товары". Данные номенклатуры у нас в табличной части "СписокНоменклатуры", её и укажем в качестве источника данных. Себестоимость не хранится в разрезе инвентарных номеров, поэтому блокировать будем только по номенклатуре. В табличной части поле называется "Номенклатура", в регистре на счете "Товары" номенклатура указывается в Субконто1.
К сожалению, порядок субконто в счете можно изменить в пользовательском режиме и Субконто1 может превратиться в "ИнвентарныйНомер", поэтому лучше написать так
ЭлементБлокировки.ИспользоватьИзИсточникаДанных(ПланыВидовХарактеристик.ВидыСубконто.Номенклатура, "Номенклатура");
Теперь необходимо получить данные для расчета себестоимости. По условию задачи учет товаров (количество) нужно вести в разрезе номенклатуры и инвентарных номеров, а учет себестоимости только в разрезе номенклатуры. Мы использовали признак учета субконто "Суммовой" для настройки плана счетов и регистра бухгалтерии. Для построения алгоритма списания себестоимости, учитывающего эту особенность, неплохо бы посмотреть как данные хранятся в регистре. Для этого можно воспользоваться консолью запросов.
Консоль запросов присутствует в каркасной конфигурации, но она написана на обычных формах. Чтобы ее запустить, необходимо немного настроить конфигурацию.
Теперь консоль запросов запускается. Для наличия данных в регистре создадим приходную накладную.
Теперь в консоли запросов получим остатки счета "Товары".
Счет "Товары" активный, поэтому остатки берем по дебету. Из результата видно, что суммовой остаток хранится в регистре только в разрезе номенклатуры. С оборотами будет тоже самое. Так работает признак учета субконто.
Теперь, когда есть представление о структуре остатков регистра, приступим к получению данных для расчета себестоимости. Создадим пакет запросов и цикл обхода используя "Конструктор запроса с обработкой результата".
В первом запросе создадим временную таблицу ТЧ с данными табличной части документа.
ВЫБРАТЬ
РасходнаяНакладнаяСписокНоменклатуры.Номенклатура КАК Номенклатура,
РасходнаяНакладнаяСписокНоменклатуры.ИнвентарныйНомер КАК ИнвентарныйНомер,
РасходнаяНакладнаяСписокНоменклатуры.Количество КАК Количество,
РасходнаяНакладнаяСписокНоменклатуры.Сумма КАК Сумма
ПОМЕСТИТЬ ТЧ
ИЗ
Документ.РасходнаяНакладная.СписокНоменклатуры КАК РасходнаяНакладнаяСписокНоменклатуры
ГДЕ
РасходнаяНакладнаяСписокНоменклатуры.Ссылка = &Ссылка
ИНДЕКСИРОВАТЬ ПО
Номенклатура,
ИнвентарныйНомер
Во втором запросе нужно получить остатки количества и себестоимости и связать их с виртуальной таблицей "ТЧ". Так как количество учитывается в разрезе инвентарных номеров, а сумма нет, то виртуальную таблицу "Управленческий.Остатки" необходимо взять 2 раза. Обратите внимание на параметры этой виртуальной таблицы.
Параметр "Период" заполним "&МоментВремени", которому позже присвоим МоментВремени(). О моменте времени мы говорили тут и тут. В "Условие" напишем отбор по номенклатуре и инвентарным номерам.
Мы помним, что Субконто1 - это номенклатура, а Субконто2 - это инвентарный номер. И снова вспоминаем, что порядок субконто может изменить пользователь в режиме 1С:Предприятие и они поменяются местами, тогда наш отбор перестанет работать.
Для решения этой проблемы служит параметр "Субконто". В него надо передать массив видов субконто в нужном порядке. Пока просто заполним его "&Субконто".
Остался последний параметр "Условие счета". Здесь всё просто - нужно написать условию по счету или счетам. Нас интересуют остатки товаров - счет "Товары".
Параметры лучше называть так, чтобы потом можно было быстро вспомнить, что в них нужно указать. К примеру, если мы назовем параметр "Счет", то потом надо будет вспоминать какой именно счет указать. А если назвать "Товары", то не задумываясь укажем счет "Товары".
Вот что получилось.
Чтобы дальше не запутаться таблицу "Управленческий.Остатки" переименуем в "ОстаткиПоИнвНом". Добавим виртуальную таблицу "Управленческий.Остатки" еще раз и переименуем её в "ОстаткиПоНоменклатуре". Параметры заполним также за исключением отбора. Отбор нужен только по номенклатуре.
Теперь добавим временную таблицу "ТЧ" и настроим левое соединение по номенклатуре и инвентарным номерам с таблицей "ОстаткиПоИнвНом" и только по номенклатуре с таблицей "ОстаткиПоНоменклатуре".
Выберем следующие поля как на картинке.
Для того, чтобы списывать количество по инвентарным номерам, а себестоимость по номенклатуре настроим обход по группировкам на вкладке "Итоги". Добавим группировочное поле "Номенклатура" и 2 итоговых поля "КоличествоОстатокНоменкл" и "СуммаОстатокНоменкл", выражение расчета Максимум или Минимум потому, что во всех строках с одинаковой номенклатурой будут общие остатки.
Закрываем конструктор и переходим к заполнению параметров, которые мы ранее обсудили.
Запрос.УстановитьПараметр("МоментВремени", МоментВремени());
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Субконто = Новый Массив;
Субконто.Добавить(ПланыВидовХарактеристик.ВидыСубконто.Номенклатура);
Субконто.Добавить(ПланыВидовХарактеристик.ВидыСубконто.ИнвентарныеНомера);
Запрос.УстановитьПараметр("Субконто", Субконто);
Запрос.УстановитьПараметр("Товары", ПланыСчетов.Управленческий.Товары);
Запрос сформирован, теперь в цикле при обходе детальных записей проверим наличие товара.
Если ВыборкаДетальныеЗаписи.КоличествоТЧ > ВыборкаДетальныеЗаписи.КоличествоОстатокИнвНом Тогда
Сообщить("По номенклатуре " + ВыборкаДетальныеЗаписи.НоменклатураПредставление +
" инвентарному номеру " + ВыборкаДетальныеЗаписи.ИнвентарныйНомерПредставление +
" неостаточно товара. Остаток " + ВыборкаДетальныеЗаписи.КоличествоОстатокДт);
Отказ = Истина;
Продолжить;
КонецЕсли;
Для формирования движений воспользуемся ранее сгенерированным кодом конструктора движений, немного его доработав. Себестоимость как всегда считаем по формуле, учитывая проблему копеек.
Если не Отказ Тогда
//Остатки
Движение = Движения.Управленческий.Добавить();
Движение.СчетДт = ПланыСчетов.Управленческий.ПрибылиУбытки;
Движение.СчетКт = ПланыСчетов.Управленческий.Товары;
Движение.Период = Дата;
Движение.КоличествоКт = ВыборкаДетальныеЗаписи.КоличествоТЧ;
Движение.СубконтоКт[ПланыВидовХарактеристик.ВидыСубконто.Номенклатура] = ВыборкаДетальныеЗаписи.Номенклатура;
Движение.СубконтоКт[ПланыВидовХарактеристик.ВидыСубконто.ИнвентарныеНомера] = ВыборкаДетальныеЗаписи.ИнвентарныйНомер;
//Себестоимость и проблема копеек
Движение.Сумма = Движение.КоличествоКт/КоличествоПоНоменклатуре*СебестоимостьПоНоменклатуре;
КоличествоПоНоменклатуре = КоличествоПоНоменклатуре - Движение.КоличествоКт;
СебестоимостьПоНоменклатуре = СебестоимостьПоНоменклатуре - Движение.Сумма;
//Продажи
Движение = Движения.Управленческий.Добавить();
Движение.СчетДт = ПланыСчетов.Управленческий.Покупатели;
Движение.СчетКт = ПланыСчетов.Управленческий.ПрибылиУбытки;
Движение.Период = Дата;
Движение.Сумма = ВыборкаДетальныеЗаписи.СуммаТЧ;
Движение.СубконтоДт[ПланыВидовХарактеристик.ВидыСубконто.Номенклатура] = ВыборкаДетальныеЗаписи.Номенклатура;
Движение.СубконтоДт[ПланыВидовХарактеристик.ВидыСубконто.ИнвентарныеНомера] = ВыборкаДетальныеЗаписи.ИнвентарныйНомер;
КонецЕсли;
Для проверки введем расходную накладную.
По движениям видно, что себестоимость рассчитана верно. К примеру, мы купили две скрипки: одну за 140 000, вторую за 100 000. При продаже одой из них списалось (140 000 + 100 000)/2 = 120 000.
По условию задачи отчет выглядит так
Продажи за период с 01.09.2020 по 30.09.2020
Товар | Инв. номер | Себестоимость | Продажа |
Скрипка | 0001 | 120 000 | 200 000 |
Барабан | 0002 | 51 000 | 70 000 |
Барабан | 5008 | 51 000 | 75 890 |
Сбивает с толку себестоимость в разрезе инвентарных номеров, но если присмотреться, то по барабану видно, что цифра расчетная - средняя. Иногда об этом прямо написано в задаче, в остальных случаях лучше уточнить у экзаменатора.
При настройке отчета нужно определиться, какие виртуальные таблицы взять. Когда говорят о продажах, то это чаще обороты. Для оборотов берем виртуальную таблицу "Управленческий.ОборотыДтКт". В ней еще больше параметров, чем в виртуальной таблице "Управленческий.Остатки", но заполнять нужно не все. Переименуем "УправленческийОборотыДтКт" в "ПродажаОборот" и заполним ее параметры.
В параметрах нам нужно заполнить корреспонденцию счетов, чтобы получить цифры по нужным проводкам. Для заполнения нужно иметь перед глазами проводки.
Документ «Приходная накладная» реализует следующую проводку:
Дт «Товары» - Кт «Поставщики» на сумму закупаемого товара
Документ «Расходная накладная» реализует следующие проводки:
Дт «Прибыли и убытки» - Кт «Товары» на сумму себестоимости;
Дт «Покупатели» - Кт «Прибыли и убытки» на сумму в продажных ценах.
Возьмем виртуальную таблицу "Управленческий.ОборотыДтКт" еще раз и переименуем в "СебестоимостьОборот". Заполняем параметры по проводкам.
Настроим связи этих таблиц по номенклатуре.
И возьмем нужные поля для отчета.
Запрос готов.
На экзамене нужно уметь настраивать группировки, внешний вид отчета (параметры, заголовок), а так же уметь пользоваться макетами СКД.
Для примера в этом отчете использованы макеты для отображения заголовка с параметрами и необычного расположения полей. С этим нужно потренироваться.
Решение задач периодических расчетов
Начнем с самого популярного условия.
Начисление зарплаты сотрудникам предприятия осуществляется ежемесячно с использованием метода отклонений. Все сотрудники работают по пятидневному графику работы, однако в решении необходимо предусмотреть возможность работы по нескольким различным графикам.
Сотрудники предприятия получают оплату по окладу пропорционально отработанному времени в часах. Часовая ставка рассчитывается как начальное значение оклада, деленное на количество рабочих часов в том же периоде, что и фактически отработанные часы. Первоначальное значение оклада может изменяться не чаще, чем один раз в день, но берется на начало расчетного периода.
Сразу определимся с объектами конфигурации, с помощью которых решаются задачи периодических расчетов. В планах видов расчета определяются виды расчета. В каркасной конфигурации уже созданы планы видов расчета с предопределенными видами расчета. К примеру, в плане "ОсновныеНачисления" имеется предопределенный вид расчета "Оклад". Регистры расчета предназначены для учета результатов различных видов расчета (кому сколько чего начислено). Сам расчет происходит в процедуре "ОбработкаПроведения" документа "НачислениеЗарплаты".
План видов расчета и регистр расчета
Начнем с настройки плана видов расчета "ОсновныеНачисления", вкладка "Расчет". В первую очередь нужно определиться с галочкой "Использует период действия". По условию оклад пропорционален отработанному времени, которое определяется по пятидневному графику. Оклад безусловно обладает протяженностью во времени, поэтому галку ставим.
Для плана видов расчета "ОсновныеНачисления" создадим регистр расчета с таким же названием. На вкладке "Основные" укажем план видов расчета, галочку период действия, после чего предлагается указать график. Здесь необходимо указать непериодический регистр сведений с измерением "Дата" и числовым значением, в котором будут храниться графики работы. По условию задачи время считаем в часах, тогда в регистре будут записи: 11.09.2020 (ПТ) - 8 часов; 12.09.2020 (СБ) - 0 часов; 13.09.2020 (ВС) - 0 часов, 14.09.2020 (ПН) - 8 часов и т.д. Этот регистр уже создан, укажем его.
Переходим на вкладку "Данные". Назначение регистра расчета - учет результатов расчета, поэтому добавим числовое измерение "Результат". Также нужно учесть, кому предназначены деньги, поэтому добавим измерение Сотрудник с типом "СправочникСсылка.ФизическиеЛица".
На вкладке "Регистраторы" отметим документ "НачислениеЗарплаты".
Прежде чем перейти к расчету надо закончить с графиком работы. Чтобы не тратить время на ручное заполнение графика в каркасной конфигурации имеется обработка "ЗаполнениеГрафика".
Вот результат её работы.
Мы можем заполнить график пятидневкой, шестидневкой и т.д., В таком варианте в одном промежутке времени будет какой-то один график, а по условию задачи нужно предусмотреть возможность работы по нескольким графикам. Для этого заведем справочник "Графики" и добавим измерение "График" типа "СправочникСсылка.Графики" в регистр сведений "ГрафикиРаботы".
Если мы хотим по-прежнему заполнять график программно, а не вручную, то придётся изменить обработку "ЗаполнениеГрафика".
Добавим график на форму обработки и немного скорректируем код обработки.
Вот изначальный код.
Процедура ЗаполнитьГрафик(ДатаНачала, ДатаОкончания, ВыходныеДни) Экспорт
Набор = РегистрыСведений.ГрафикиРаботы.СоздатьНаборЗаписей();
Набор.Прочитать();
ЧислоСекундВСутках = 86400;
Дат = ДатаНачала;
Для к = 0 По Набор.Количество()-1 Цикл
Запись = Набор[к];
Если Запись.Дата < ДатаНачала Тогда
Продолжить;
ИначеЕсли Запись.Дата =Дат Тогда
Если Найти(ВыходныеДни, Строка(ДеньНедели(Дат))) Тогда
Запись.Значение = 0;
Иначе
Запись.Значение = 8;
КонецЕсли;
Дат = Дат + ЧислоСекундВСутках;
Иначе
Пока Дат < Мин(Запись.Дата, ДатаОкончания) Цикл
НоваяЗапись = Набор.Добавить();
НоваяЗапись.Дата = Дат;
Если Найти(ВыходныеДни, Строка(ДеньНедели(Дат))) Тогда
НоваяЗапись.Значение = 0;
Иначе
НоваяЗапись.Значение = 8;
КонецЕсли;
Дат = Дат + ЧислоСекундВСутках;
КонецЦикла;
Если Запись.Дата > ДатаОкончания Тогда
Прервать;
Иначе
Если Найти(ВыходныеДни, Строка(ДеньНедели(Дат))) Тогда
Запись.Значение = 0;
Иначе
Запись.Значение = 8;
КонецЕсли;
КонецЕсли;
Дат = Дат + ЧислоСекундВСутках;
КонецЕсли;
КонецЦикла;
Набор.Записать();
Пока Дат <= ДатаОкончания Цикл
Запись = Набор.Добавить();
Запись.Дата = Дат;
Если Найти(ВыходныеДни, Строка(ДеньНедели(Дат))) Тогда
Запись.Значение = 0;
Иначе
Запись.Значение = 8;
КонецЕсли;
Дат = Дат + ЧислоСекундВСутках;
КонецЦикла;
Набор.Записать();
КонецПроцедуры
Две трети кода предназначено для изменения существующих записей регистра. На экзамене проще очистить и перезаполнить график, поэтому цикл обхода существующих записей можно удалить, а оставить только цикл добавления новых записей. В процедуру нужно передать еще один параметр "График" и добавить заполнение измерения "График".
Процедура ЗаполнитьГрафик(ДатаНачала, ДатаОкончания, ВыходныеДни, График) Экспорт
Набор = РегистрыСведений.ГрафикиРаботы.СоздатьНаборЗаписей();
ЧислоСекундВСутках = 86400;
Дат = ДатаНачала;
Пока Дат <= ДатаОкончания Цикл
Запись = Набор.Добавить();
Запись.Дата = Дат;
Если Найти(ВыходныеДни, Строка(ДеньНедели(Дат))) Тогда
Запись.Значение = 0;
Запись.График = График;
Иначе
Запись.Значение = 8;
Запись.График = График;
КонецЕсли;
Дат = Дат + ЧислоСекундВСутках;
КонецЦикла;
Набор.Записать();
КонецПроцедуры
В режиме предприятия создадим элемент "Пятидневка" справочника "Графики", очистим регистр "ГрафикиРаботы" и снова заполним обработкой.
Оклад, тариф, фиксированная сумма, процент
Начисление может происходить исходя из оклада (30 000 в месяц), тарифной ставки (300 руб. в час), фиксированной суммы (20 000), процента (10% от начисленного оклада) и т.п. Эти цифры необходимо хранить в информационной базе в зависимости от ситуации в константе, регистре сведений или самом документе "НачислениеЗарплаты".
По условию нашей задачи оклад меняется не чаще одного раза в день, но берется на начало расчетного периода. Удобнее всего хранение оклада организовать в периодическом (периодичность - день) регистре сведений. Он не позволит записать два значения оклада для одной даты и из него легко получить "СрезПоследних" на начало месяца.
В каркасной конфигурации уже имеется регистр сведений "СведенияОСотрудниках" с нужной периодичностью. Он независимый, поэтому заполним его вручную.
Документ "Начисление зарплаты" (Оклад)
Документ "НачислениеЗарплаты" тоже присутствует в каркасной конфигурации. В документе имеется табличная часть "ОсновныеНачисления", в которой необходимо указать сотрудника, вид расчета, начало и конец периода. Название табличной части соответствует плану видов расчета. При проведении документ должен рассчитать результат и записать в регистр расчета.
По условию задачи сотрудники могут работать по разным графикам, поэтому в табличную часть добавим реквизит "График" типа "СправочникСсылка.Графики".
Перейдем к движениям документа.
Алгоритм проведения документа "НачислениеЗарплаты" следующий:
1. Формируем наборы записей и записываем в регистры расчета. У нас есть все данные кроме Результата.
2. Используя запрос получаем из информационной базы необходимые для расчета результата сведения: данные о фактически отработанном времени в соответствии с установленным графиком работы, началное значение оклада, процент премии и т.п.
3. Обходим сформированные в п.1 наборы движений, рассчитываем результат и дописываем его в наши наборы движений.
Сформируем обработку проведения с помощью конструктора движений следующим образом.
Процедура ОбработкаПроведения(Отказ, Режим)
// регистр ОсновныеНачисления
Движения.ОсновныеНачисления.Записывать = Истина;
Для Каждого ТекСтрокаОсновныеНачисления Из ОсновныеНачисления Цикл
Движение = Движения.ОсновныеНачисления.Добавить();
Движение.Сторно = Ложь;
Движение.ВидРасчета = ТекСтрокаОсновныеНачисления.ВидРасчета;
Движение.ПериодДействияНачало = ТекСтрокаОсновныеНачисления.ДатаНачала;
Движение.ПериодДействияКонец = ТекСтрокаОсновныеНачисления.ДатаОкончания;
Движение.ПериодРегистрации = Дата;
Движение.Сотрудник = ТекСтрокаОсновныеНачисления.Сотрудник;
Движение.Результат = 0;
КонецЦикла;
//{{__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
// Данный фрагмент построен конструктором.
// При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!
//}}__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
КонецПроцедуры
Пока оставим процедуру обработки в таком виде и создадим документ "НачислениеЗарплаты".
Для наглядности представим, что Бельдыева мы приняли в последний рабочий день июля. Поэтому мы должны оплатить ему по окладу 8 фактически отработанных часов. Для понимания виртуальных таблиц регистров расчета запросим оттуда данные с помощью консоли запросов. Как настроить работоспособность консоли запросов описано здесь.
Запросим данные по Бельдыеву из виртуальной таблицы "ОсновныеНачисленияДанныеГрафика". Её данные формируются путем соединения таблицы фактического периода с графиком (регистр сведений "ГрафикиРабот", указанный в настройках регистра в поле "График").
Из результата следует, что в июле 2020 года по пятидневному графику 184 плановых часов (23 дней * 8 часов), из них Бельдыев фактически отработал 8 (1 день). Этих данных достаточно, чтобы рассчитать его зарплату за июль: оклад 30000 руб./184 плановых часа*8 фактически отработанных часов = 1304,35 руб. Этот результат мы должны получить при расчете.
Продолжим формирование процедуры "ОбработкаПроведения" документа "РасчетЗарплаты", а именно запрос для получения данных для расчета. Перед запросом запишем движения, чтобы в результате были данные движений документа.
Движения.Записать();
В результате запроса нам нужно получить плановые и фактические данные из таблицы "ОсновныеНачисленияДанныеГрафика" и оклад из регистра сведений "СведенияОСотрудниках". В виртуальной таблице всего один параметр "Условия", укажем в нем отбор по регистратору и виду расчета. Регистр сведений "СведенияОСотрудниках" соединим левым соединением по сотруднику.
// Расчет оклада
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ОсновныеНачисленияДанныеГрафика.НомерСтроки КАК НомерСтроки,
| ОсновныеНачисленияДанныеГрафика.ЗначениеПериодДействия КАК ПланЧасов,
| ОсновныеНачисленияДанныеГрафика.ЗначениеФактическийПериодДействия КАК ФактЧасов,
| СведенияОСотрудникахСрезПоследних.Оклад КАК Оклад
|ИЗ
| РегистрРасчета.ОсновныеНачисления.ДанныеГрафика(
| Регистратор = &Ссылка
| И ВидРасчета = &Оклад) КАК ОсновныеНачисленияДанныеГрафика
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СведенияОСотрудниках.СрезПоследних(&НачалоПериода, ) КАК СведенияОСотрудникахСрезПоследних
| ПО ОсновныеНачисленияДанныеГрафика.Сотрудник = СведенияОСотрудникахСрезПоследних.Сотрудник";
Запрос.УстановитьПараметр("НачалоПериода", НачалоМесяца(Дата));
Запрос.УстановитьПараметр("Оклад", ПланыВидовРасчета.ОсновныеНачисления.Оклад);
Запрос.УстановитьПараметр("Ссылка", Ссылка);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДляОклада = РезультатЗапроса.Выбрать();
Для каждого СтрокаНабора из Движения.ОсновныеНачисления Цикл
ВыборкаДляОклада.Сбросить();
Если ВыборкаДляОклада.НайтиСледующий(СтрокаНабора.НомерСтроки,"НомерСтроки") Тогда
СтрокаНабора.Результат = ВыборкаДляОклада.Оклад/ВыборкаДляОклада.ПланЧасов*ВыборкаДляОклада.ФактЧасов;
КонецЕсли;
КонецЦикла;
Движения.ОсновныеНачисления.Записать(,Истина);
Обратите внимание, мы не обходим выборку результата запроса. Мы обходим движения для заполнения результата расчета. Для каждой строки движений ищем в выборке результата запроса строку по номеру и используя ее данные рассчитываем результат. Затем повторно записываем движения.
Кроме того, обратите внимание на строчку
Движения.ОсновныеНачисления.Записать(,Истина);
В методе Записать мы заполнили параметр "ТолькоЗапись". Он позволяет не перезаполнять таблицу фактического периода и таблицу перерасчетов т.к. мы изменили только ресурс "Результат". Будет работать и без этого, но это позволяет оптимизировать код.
Перепроводим документ и проверяем результат.
Зависимость по базовому периоду (Премия)
Дополним условие задачи.
Дополнительно, сотрудникам компании может быть начислена премия процентом от начисленного в том же расчетном периоде оклада. Процент премии в течение периода начисления может изменяться не чаще, чем один раз в день, но берется на начало текущего расчетного периода.
Сперва определимся с хранением процента премии в базе. У нас имеется периодический регистр сведений "СведенияОСотрудниках" (периодичность - день), в котором хранится значение оклада. Логично там же хранить процент премии.
Укажем процент премии для Бельдыева.
Очевидно, что премия - еще один вид расчета. Он должен входить в один из планов видов расчета. Для принятия решения в какой план видов расчета его включить необходимо понять, будет ли использоваться период действия в этом виде расчетов.
К примеру, для расчета оклада необходимо указать период работы сотрудника, от которого зависит фактически отработанное время. При начислении премии или штрафа период не указывается, это мгновенное событие, не протяженное во времени. Следовательно, премия должна быть в плане видов расчета, не использующем период действия.
Вид расчета "Оклад" содержится в плане видов расчета "ОсновныеНачисления", в котором мы установили настройку "Использует период действия". В связи с этим премию необходимо включить в другой план видов расчета.
В каркасной конфигурации имеется план видов расчета "ДополнительныеНачисления" и в нем присутствует предопределенный вид расчета "Премия". Перейдем к настройке этого плана, вкладка "Расчет". Галочку "Использует период действия" снимаем, далее необходимо настроить зависимость от базы.
Зависимость от базы - механизм расчета одного вида расчета на основании результата другого. В нашем случае "Премия" зависит от "Оклада", поэтому выберем "Зависит по периоду действия". В качестве базового укажем вид расчета, в котором находится "Оклад", то есть "ОсновныеНачисления".
У предопределённого вида расчета "Премия" укажем зависимость по базе от вида расчета "Оклад".
Для плана видов расчета "ДополнительныеНачисления" создадим регистр расчета с таким же названием. На вкладке "Основные" галочку "Период действия" не ставим, а галочку "Базовый период" ставим.
На вкладке "Данные" по аналогии с регистром "ОсновныеНачисления" создаем измерение "Сотрудник" и ресурс "Результат", на вкладке "Регистраторы" отмечаем документ "НачислениеЗарплаты".
В документе "НачислениеЗарплаты" копированием создадим вторую табличную часть для регистра "ДополнительныеНачисления" с таким же названием.
Используя конструктор движений сформируем движения в регистр "ДополнительныеНачисления" по табличной части "ДополнительныеНачисления".
Обратите внимание, периода действия в регистре нет, зато есть базовый период. Из документа мы можем заполнить только "ПериодРегистрации", "ВидРасчета" и "Сотрудник". В качестве базы для расчета премии необходимо использовать начисленный в этом же месяце оклад, поэтому начало и конец базового периода - это начало и конец месяца расчета соответственно.
// регистр ДополнительныеНачисления
Движения.ДополнительныеНачисления.Записывать = Истина;
Для Каждого ТекСтрокаДополнительныеНачисления Из ДополнительныеНачисления Цикл
Движение = Движения.ДополнительныеНачисления.Добавить();
Движение.Сторно = Ложь;
Движение.ВидРасчета = ТекСтрокаДополнительныеНачисления.ВидРасчета;
Движение.ПериодРегистрации = Дата;
Движение.БазовыйПериодНачало = НачалоМесяца(Дата);
Движение.БазовыйПериодКонец = КонецМесяца(Дата);
Движение.Сотрудник = ТекСтрокаДополнительныеНачисления.Сотрудник;
Движение.Результат = 0;
КонецЦикла;
Переместим запись движений в начало к основным начислениям и в режиме 1С:Предприятие введем в документ "РасчетЗарплаты" в табличную часть "ДополнительныеНачисления" строку о премии.
Данные о базе хранятся в виртуальной таблице "ДополнительныеНачисления.БазаОсновныеНачисления". Воспользуемся консолью запросов для просмотра данных этой таблицы.
Как видно, из таблицы можно получить базовое значение для заданного нами в движениях базового периода. Перейдем к алгоритму расчета премии. Согласно алгоритму проведения документа "РасчетЗарплаты" сначалы мы записываем движения в регистр (все кроме результата), затем рассчитываем результаты видов расчета. Очевидно, порядок расчетов имеет значение. Ведь, не рассчитав и не записав в регистр Оклад мы не сможем получить базу для премии. В связи с этим, алгоритм расчета премии размещаем после расчета оклада.
Используя конструктор с обработкой результата сформируем запрос получения нужных сведений: базы и процента премии. Данные будем брать из виртуальных таблиц "ДополнительныеНачисления.БазаОсновныеНачисления" и "СведенияОСотрудникахСрезПоследних". Остановимся подробнее на параметрах виртуальной таблицы "ДополнительныеНачисления.БазаОсновныеНачисления".
В параметры "ИзмеренияОсновногоРегистра" и "ИзмеренияБазовогоРегистра" необходимо передать массивы наименований измерений регистров "ДополнительныеНачисления" и "ОсновныеНачисления" соответственно, по которым нужно соединить данные о базе. В нашей задача только одно измерение "Сотрудник", в обоих регистрах называется одинаково.
Параметр "Разрезы" рассмотрим немного позже на примере. а пока оставим пустым.
Из виртуальной таблицы "ДополнительныеНачисления.БазаОсновныеНачисления" нам необходимы поля "НомерСтроки" и "РезультатБаза", из виртуальной таблицы "СведенияОСотрудникахСрезПоследних" нужно поле "ПроцентПремии". Таблицы соединяем левым соединением по сотруднику.
Обход движений, поиск нужной строки в выборке результата запроса и вычисление результата аналогичны расчету оклада.
// Расчет премии
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ДополнительныеНачисленияБазаОсновныеНачисления.НомерСтроки КАК НомерСтроки,
| ДополнительныеНачисленияБазаОсновныеНачисления.РезультатБаза КАК РезультатБаза,
| ЕСТЬNULL(СведенияОСотрудникахСрезПоследних.ПроцентПремии, 0) КАК ПроцентПремии
|ИЗ
| РегистрРасчета.ДополнительныеНачисления.БазаОсновныеНачисления(&Измерения, &Измерения, , Регистратор = &Ссылка) КАК ДополнительныеНачисленияБазаОсновныеНачисления
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СведенияОСотрудниках.СрезПоследних(&НачалоМесяца,) КАК СведенияОСотрудникахСрезПоследних
| ПО ДополнительныеНачисленияБазаОсновныеНачисления.Сотрудник = СведенияОСотрудникахСрезПоследних.Сотрудник";
Измерения = Новый Массив;
Измерения.Добавить("Сотрудник");
Запрос.УстановитьПараметр("Измерения", Измерения);
Запрос.УстановитьПараметр("НачалоМесяца", НачалоМесяца(Дата));
Запрос.УстановитьПараметр("Ссылка", Ссылка);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДляПремии = РезультатЗапроса.Выбрать();
Для каждого СтрокаНабора из Движения.ДополнительныеНачисления Цикл
ВыборкаДляПремии.Сбросить();
Если ВыборкаДляПремии.НайтиСледующий(СтрокаНабора.НомерСтроки,"НомерСтроки") Тогда
СтрокаНабора.Результат = ВыборкаДляПремии.РезультатБаза*ВыборкаДляПремии.ПроцентПремии/100;
КонецЕсли;
КонецЦикла;
Движения.ДополнительныеНачисления.Записать();
Использование разрезов при получении базы (Премия руководителя)
Немного изменим условие начисления премии.
Руководителям подразделений выплачивается премия в виде процента от начисленного в том же расчетном периоде оклада сотрудникам их подразделения. С оклада самого руководителя премия не начисляется.
Так как нам теперь необходимо получать сумму оклада, начисленного сотрудникам подразделения, то в первую очередь добавляем измерение "Подразделение" типа "СправочникСсылка.Подразделения" в регистры расчета, а также учитываем это при формировании движений по табличным частям.
При расчете оклада и премии мы получаем начальное значение оклада и процента премии из регистра сведений "СведенияОСотрудниках" (срез последних). До этого мы соединяли таблицу "СведенияОСотрудникахСрезПоследних" по полю "Сотрудник". Так как теперь ведём учет в разрезе подразделений и не исключено, что сотрудник работаем одновременно в разных подразделениях с разными окладами и процентами премий, то добавим связь по подразделению.
Далее необходимо по-другому заполнить параметры виртуальной таблицы "ДополнительныеНачисленияБазаОсновныеНачисления" и добавить отбор по сотрудникам.
Измерения основного и базового регистров, по которым должна быть присоединена таблица с данными о базе, ранее мы заполняли измерением "Сотрудник". Теперь нас интересует база (начисленный оклад) не сотрудника, а подразделения, поэтому в измерения добавим только "Подразделение".
Измерения = Новый Массив;
Измерения.Добавить("Подразделение");
Запрос.УстановитьПараметр("Измерения", Измерения);
Также нам потребуется заполнить параметр "Разрезы". В разрезы необходимо передать массив наименований измерений, в разрезе которых необходимо получить базу. По условию задачи оклад самого руководителя не должен учитываться при расчете его же премии, поэтому в разрезы добавим измерение "Сотрудник".
Разрезы = Новый Массив;
Разрезы.Добавить("Сотрудник");
Запрос.УстановитьПараметр("Разрезы", Разрезы);
Мы сделали это для того, чтобы сработало условие отбора в запросе.
ДополнительныеНачисленияБазаОсновныеНачисления.СотрудникРазрез <> ДополнительныеНачисленияБазаОсновныеНачисления.Сотрудник
СотрудникРазрез (сотрудник, которому начислялся оклад) не должен равняться Сотруднику, которому начисляется премия (руководитель). Вот получившийся алгоритм.
// Расчет премии руководителя подразделения
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ДополнительныеНачисленияБазаОсновныеНачисления.НомерСтроки КАК НомерСтроки,
| СУММА(ДополнительныеНачисленияБазаОсновныеНачисления.РезультатБаза) КАК РезультатБаза,
| ЕСТЬNULL(СведенияОСотрудникахСрезПоследних.ПроцентПремии, 0) КАК ПроцентПремии
|ИЗ
| РегистрРасчета.ДополнительныеНачисления.БазаОсновныеНачисления(&Измерения, &Измерения, &Разрезы, Регистратор = &Ссылка) КАК ДополнительныеНачисленияБазаОсновныеНачисления
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СведенияОСотрудниках.СрезПоследних(&НачалоМесяца, ) КАК СведенияОСотрудникахСрезПоследних
| ПО ДополнительныеНачисленияБазаОсновныеНачисления.Сотрудник = СведенияОСотрудникахСрезПоследних.Сотрудник
| И ДополнительныеНачисленияБазаОсновныеНачисления.Подразделение = СведенияОСотрудникахСрезПоследних.Подразделение
|ГДЕ
| ДополнительныеНачисленияБазаОсновныеНачисления.СотрудникРазрез <> ДополнительныеНачисленияБазаОсновныеНачисления.Сотрудник
|
|СГРУППИРОВАТЬ ПО
| ДополнительныеНачисленияБазаОсновныеНачисления.НомерСтроки,
| ЕСТЬNULL(СведенияОСотрудникахСрезПоследних.ПроцентПремии, 0)";
Измерения = Новый Массив;
Измерения.Добавить("Подразделение");
Запрос.УстановитьПараметр("Измерения", Измерения);
Разрезы = Новый Массив;
Разрезы.Добавить("Сотрудник");
Запрос.УстановитьПараметр("Разрезы", Разрезы);
Запрос.УстановитьПараметр("НачалоМесяца", НачалоМесяца(Дата));
Запрос.УстановитьПараметр("Ссылка", Ссылка);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДляПремии = РезультатЗапроса.Выбрать();
Для каждого СтрокаНабора из Движения.ДополнительныеНачисления Цикл
ВыборкаДляПремии.Сбросить();
Если ВыборкаДляПремии.НайтиСледующий(СтрокаНабора.НомерСтроки,"НомерСтроки") Тогда
СтрокаНабора.Результат = ВыборкаДляПремии.РезультатБаза*ВыборкаДляПремии.ПроцентПремии/100;
КонецЕсли;
КонецЦикла;
Движения.ДополнительныеНачисления.Записать();
Для проверки добавим в документ "НачислениеЗарплаты" начисление оклада еще одного сотрудника Васина и проанализируем теперь премию Бельдыева. Так как мы ему назначаем премию, то считается, что он и есть руководитель подразделения.
Из примера видно, что премию Бельдыев получит только с оклада Васиной.
Механизм вытеснения (командировка, больничный)
Дополним условие задачи.
По мере необходимости любой сотрудник может быть отправлен в командировку. В этом случае начисление по окладу не происходит. Начисление командировочных происходит фиксированной суммой за все время, в течение которого сотрудник находился в командировке.
С точки зрения периодических расчетов командировка и больничный интересны тем, что их нельзя совмещать с работой (оклад). Невозможно одновременно быть на работы и в отъезде или на больничном. Один вид расчета будет вытеснять другой. Необходимо использовать механизм вытеснения.
Первое, с чем нужно определиться - в какой план видов расчета добавить командировку? У нас два плана: "Основные начисления" (используется период действия) и "Дополнительные начисления" (не используется период действия). Нужно понять, нужен ли командировке период действия.
С одной стороны результат расчета командировки никак не зависит от периода и даже от количества дней, ведь назначается он фиксированной суммой. С другой стороны оклад во время командировки начисляться не должен. Другими словами, командировка должна вытеснять оклад.
Если вид расчета участвует в механизме вытеснения, то период действия необходимо использовать. Это означает, что предопределенный вид расчета "Командировка" нужно создать в плане видов расчета "ОсновныеНачисления".
А также у вида расчета "Оклад" на вкладке "Вытесняющие" необходимо отметить вид расчета "Командировка".
По условию задачи начисление командировочных происходит фиксированной суммой, это означает, что ничего считать не нужно. В табличной части "Основные начисления" имеется реквизит "Размер", в который можно указать сумму. Тогда результат можно записать сразу при формировании движений по табличной части. Исправим строку
Движение.Результат = 0;
на
Движение.Результат = ТекСтрокаОсновныеНачисления.Размер;
Для примера сначала начислим Бельдыеву оклад за август и проверим результат.
Он отработал весь месяц, поэтому ему положен целый оклад. Теперь отправим его в командировку на денёк за 100 рублей.
Видим, что уехав в командировку на 1 день, Бельдыев отработал 20 фактических из 21 плановых дней и получил по окладу 30000/21*20 = 28 571 руб.
Для того, чтобы лучше понять механизм вытеснения, посмотрим на виртуальную таблицу "ОсновныеНачисленияФактическийПериодДействия" до вытеснения оклада командировкой и после.
Видно, что в виртуальной таблице фактического периода оклад сначала занимал весь месяц, а после ввода командировки оклад разбился на два периода. При записи движений система актуализировала записи таблицы фактического периода и при запросе данных фактического времени мы получили данные с учетом вытеснения оклада командировкой, то есть 20 дней.
Формирование сторно-записей (ввод командировки задним числом)
Дополним условие задачи.
Следует учесть, что данные о командировке могут вводиться в систему задним числом.
В любом учете (бухгалтерском, торговом, зарплатном и т.п.) принято периодически подводить черту, считать итоги и закрывать период (запрещать вносить изменения). Когда зарплата начислена и выдана, то месяц считается закрытым и исправления в эти начисления вносить нельзя.
Но как быть, если мы вспомнили, что Бельдыев 10.09.2020 был в командировке, а мы ему начислили и выдали оклад за весь сентябрь 2020? Для этого существует механизм сторно-записей, позволяющий внести исправление прошлых периодов текущей датой.
Начнем с начисления оклада за сентябрь.
Обратите внимание, мы начисляем оклад за сентябрь в сентябре (дата документа, она же дата регистрации). Как видим, Бельдыев получил оклад полностью (30000 руб.).
Представим, что оклад начислен и выдан, месяц закрыт. Вдруг 2 октября мы вспомнили, что Бельдыев 10.09.2020 был в командировке. Начисляем сентябрьскую командировку в октябре.
Командировка начислена, но начисление оклада за этот день тоже осталось. Нужно его сторнировать, для этого воспользуемся методом ПолучитьДополнение() набора записей регистра расчета. Вызовем его после формирования движений по табличным частям и до их записи.
ТаблицаСторноЗаписей = Движения.ОсновныеНачисления.ПолучитьДополнение();
Для каждого СтрокаСторно Из ТаблицаСторноЗаписей Цикл
Движение = Движения.ОсновныеНачисления.Добавить();
Движение.Сторно = Истина;
Движение.ВидРасчета = СтрокаСторно.ВидРасчета;
Движение.ПериодДействияНачало = СтрокаСторно.ПериодДействияНачалоСторно;
Движение.ПериодДействияКонец = СтрокаСторно.ПериодДействияНачалоСторно;
Движение.ПериодРегистрации = СтрокаСторно.ПериодРегистрацииСторно;
Движение.Сотрудник = СтрокаСторно.Сотрудник;
Движение.Подразделение = СтрокаСторно.Подразделение;
КонецЦикла;
Если поставить точку останова в цикле и посмотреть отладчиком сформированные методом ПолучитьДополнение() записи, то увидим, что системы предлагает отсторнировать оклад Бельдыева за 10.09.2020.
После добавления в алгоритм формирования сторно-записей перепроведем документ начисления командировки задним числом.
Командировка начислена, оклад за один день 30000/22*1 = 1363,64 руб. сторнирован, но результат со знаком плюс, а нам нужно эти деньги удержать. Добавим в алгоритм расчета оклада особую обработку сторно-записей.
Если СтрокаНабора.Сторно Тогда
СтрокаНабора.Результат = -ВыборкаДляОклада.Оклад/ВыборкаДляОклада.ПланЧасов*ВыборкаДляОклада.ФактЧасов;
Иначе
СтрокаНабора.Результат = ВыборкаДляОклада.Оклад/ВыборкаДляОклада.ПланЧасов*ВыборкаДляОклада.ФактЧасов;
КонецЕсли;
Теперь оклад и командировка Бельдыева за сентябрь начислены правильно. Но это еще не всё.
При расчете результата сторно-записей начальное значение оклада берется на начало месяца, в котором введен документ, а вытесняемый оклад был рассчитан исходя из значения оклада на начало месяца начисления. Для примера изменим значение оклада к октябрю с 30000 руб. до 40000 руб.
Перепроведем начисление командировки.
Теперь к удержанию 1818,18 руб., хотя получил Бельдыев из расчета 1363,64 за день - несправедливо.
Для того, чтобы рассчитывать результат сторно-записи с правильным начальным окладом, проще всего сохранять его при расчете оклада. Добавим реквизит "Оклад" в регистр расчета "ОсновныеНачисления".
При расчете оклада сохраним его начальное значение в реквизит "Оклад". При формировании сторно-записей продублируем реквизит "Оклад" в сторно-запись, а при расчете сторно-записи возьмем начальное значение не из результата запроса, а из реквизита "Оклад" строки набора.
ТаблицаСторноЗаписей = Движения.ОсновныеНачисления.ПолучитьДополнение();
Для каждого СтрокаСторно Из ТаблицаСторноЗаписей Цикл
Движение = Движения.ОсновныеНачисления.Добавить();
Движение.Сторно = Истина;
Движение.ВидРасчета = СтрокаСторно.ВидРасчета;
Движение.ПериодДействияНачало = СтрокаСторно.ПериодДействияНачалоСторно;
Движение.ПериодДействияКонец = СтрокаСторно.ПериодДействияНачалоСторно;
Движение.ПериодРегистрации = СтрокаСторно.ПериодРегистрацииСторно;
Движение.Сотрудник = СтрокаСторно.Сотрудник;
Движение.Подразделение = СтрокаСторно.Подразделение;
Движение.Оклад = СтрокаСторно.Оклад;
КонецЦикла;
Движения.Записать();
// Расчет оклада
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ОсновныеНачисленияДанныеГрафика.НомерСтроки КАК НомерСтроки,
| ОсновныеНачисленияДанныеГрафика.ЗначениеПериодДействия КАК ПланЧасов,
| ОсновныеНачисленияДанныеГрафика.ЗначениеФактическийПериодДействия КАК ФактЧасов,
| ЕСТЬNULL(СведенияОСотрудникахСрезПоследних.Оклад, 0) КАК Оклад
|ИЗ
| РегистрРасчета.ОсновныеНачисления.ДанныеГрафика(
| Регистратор = &Ссылка
| И ВидРасчета = &Оклад) КАК ОсновныеНачисленияДанныеГрафика
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СведенияОСотрудниках.СрезПоследних(&НачалоПериода, ) КАК СведенияОСотрудникахСрезПоследних
| ПО ОсновныеНачисленияДанныеГрафика.Сотрудник = СведенияОСотрудникахСрезПоследних.Сотрудник
| И ОсновныеНачисленияДанныеГрафика.Подразделение = СведенияОСотрудникахСрезПоследних.Подразделение";
Запрос.УстановитьПараметр("НачалоПериода", НачалоМесяца(Дата));
Запрос.УстановитьПараметр("Оклад", ПланыВидовРасчета.ОсновныеНачисления.Оклад);
Запрос.УстановитьПараметр("Ссылка", Ссылка);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДляОклада = РезультатЗапроса.Выбрать();
Для каждого СтрокаНабора из Движения.ОсновныеНачисления Цикл
ВыборкаДляОклада.Сбросить();
Если ВыборкаДляОклада.НайтиСледующий(СтрокаНабора.НомерСтроки,"НомерСтроки") Тогда
Если СтрокаНабора.Сторно Тогда
СтрокаНабора.Результат = -СтрокаНабора.Оклад/ВыборкаДляОклада.ПланЧасов*ВыборкаДляОклада.ФактЧасов;
Иначе
СтрокаНабора.Оклад = ВыборкаДляОклада.Оклад;
СтрокаНабора.Результат = ВыборкаДляОклада.Оклад/ВыборкаДляОклада.ПланЧасов*ВыборкаДляОклада.ФактЧасов;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Движения.ОсновныеНачисления.Записать(,Истина);
Дополним условие задачи.
Создать отчет «Перерасчет зарплаты», в котором пользователь должен увидеть записи регистра расчета, которые возможно требуется пересчитать.
Объект перерасчета | Вид расчета | Сотрудник | Подразделение |
Результат одного вида расчета может зависеть от результата расчета другого. К примеру, в нашем случае оклад зависит от командировки. Если сначала ввести оклад, а потом отдельным документом командировку, то оклад необходимо пересчитать. Вид расчета "Командировка" является по отношению к виду расчета "Оклад" ведущим видом расчета.
Вид расчета, оказывающий влияние на результат другого вида расчета, является по отношению к нему ведущим.
Перерасчет является объектом конфигурации, подчиненный регистру расчета. Это просто таблица, в которую автоматически попадают записи регистра, которые по мнению платформы необходимо пересчитать.
На вкладке "Перерасчеты" регистра расчета "ОсновныеНачисления" создадим перерасчет и назовем его "ПерерасчетОсн", добавим измерения перерасчета аналогичные измерениям регистра. Их необходимо связать с измерениями ведущих регистров. Свяжем их с измерениями этого же регистра.
Уже можно посмотреть, как выглядит и работает перерасчет. Для примера начислим Васиной оклад за октябрь 2020.
Её оклад 20000 руб., она отработала месяц полностью, поэтому резутльтат тоже 20000 руб. Теперь отдельным документом введем командировку Васиной за один день октября 2020.
Теперь если посмотреть таблицу фактического периода действия регистра расчета "ОсновныеНачисления", то увидим, что командировка вытеснила оклад 30.10.2020. Но результат расчета оклада Васиной в документу №5 остался 20000 руб., хотя один день вытеснен командировкой. Для корректного расчета необходимо пересчитать документ №5. Это понимает и платформа без дополнительных настроек. Давайте посмотрим на таблицу перерасчета через консоль запросов.
Как видим, платформа автоматически добавила в перерасчет запись регистра с документом №5 по окладу Васиной из бухгалтерии. Платформа считает, что эти записи могли оказаться неактуальными в связи с произошедшим вытеснением.
Пересчитаем (перепроведём) документ "НачислениеЗарплаты" №5 и увидим, что таблица очистилась.
Необходимость перерасчета записей регистра расчета может возникнуть не только вследствие вытеснения. Ранее мы реализовали расчет премии руководителя, который зависит по базе от начисленного оклада сотрудникам этого подразделения. Если рассчитать оклад и командировку после расчета премии, то расчет премии может оказаться неактуальным.
Так как премия в регистре расчета "ДополнительныеНачисления", то на его вкладке "Перерасчеты" создадим перерасчет и назовем его "ПерерасчетДоп", добавим измерение перерасчета "Подразделение" и свяжем его с измерением "Подразделение" регистра "ОсновныеНачисления" т.к. премия зависит от оклада, который в регистре "ОсновныеНачисления".
Почему мы не добавили измерение сотрудник? Потому что премия рассчитывается руководителю подразделения, то есть не тому сотруднику, которому рассчитывается оклад, в связи с чем добавление измерения "Сотрудник" приведет к неправильному формированию записей перерасчета премии.
Отменим проведение документов "НачислениеЗарплаты" №5 (оклад Васиной за октябрь), №6 (командировка Васиной за 30 октября) и введем документ №7 (расчет премии за октябрь руководителю бухгалтерии Бельдыеву, процент премии 100).
Результат нулевой т.к. сотрудникам бухгалтерии оклад за октябрь не начислен.
Проведём документ "НачислениеЗарплаты" №5 (оклад Васиной за октябрь) и посмотрим содержимое таблицы перерасчета "ПерерасчетДоп".
Так как вид расчета "Оклад" является базовым по отношению к виду расчета "Премия", то при пересчете оклада платформа понимает, что нужно пересчитать премию. Пересчитаем премию и таблица перерасчета очистится. Теперь проведем документ №6 (командировка Васиной за 30 октября) и в таблицу перерасчета снова попадет премия документа №7 т.к. командировка вытеснила оклад, который является базой для премии.
Подытожим. Если у вида расчета есть "Базовые" и "Вытесняющие" виды расчета, то они же и являются "Ведущими" для этого вида расчета.
Но "Ведущими" являются не только "Базовые" и "Вытесняющие" виды расчета. К примеру, первый вид премии зависит по базе от оклада, а второй вид премии зависит по базе от первого вида премии. В итоге, второй вид премии напрямую не зависит по базе от оклада и при расчете оклада второй вид премии в перерасчет не попадет.
В связи с этим для сложных взаимосвязей в настройках видов расчетов имеется вкладка "Ведущие", где можно галочками отметить ведущие виды расчета по отношению к этому виду расчета. В нашем случае можно смело отметить командировку у оклада, а также командировку и оклад у премии, хотя на результат это не повлияет.
Необходимость перерасчета может возникнуть не только при расчете другого вида расчета. К примеру, мы изменили процент премии у конкретного сотрудника на начало расчетного периода и хотим, чтобы в таблица перерасчетов показала, кому нужно пересчитать премию.
В таком случае придется программно добавить записи в таблицу перерасчетов. К примеру, в обработчике "ПриЗаписи" регистра сведений "СведенияОСотрудниках".
Процедура ПриЗаписи(Отказ, Замещение)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ДополнительныеНачисления.Регистратор КАК Регистратор
|ИЗ
| РегистрРасчета.ДополнительныеНачисления КАК ДополнительныеНачисления
|ГДЕ
| ДополнительныеНачисления.ВидРасчета = &Премия
| И ДополнительныеНачисления.Подразделение = &Подразделение
| И ДополнительныеНачисления.ПериодРегистрации = &ПериодРегистрации";
Запрос.УстановитьПараметр("Премия", ПланыВидовРасчета.ДополнительныеНачисления.Премия);
Запрос.УстановитьПараметр("Подразделение", ЭтотОбъект.Отбор.Подразделение.Значение);
Запрос.УстановитьПараметр("ПериодРегистрации", НачалоМесяца(ДобавитьМесяц(ЭтотОбъект.Отбор.Период.Значение,1)));
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
НаборЗаписей = РегистрыРасчета.ДополнительныеНачисления.Перерасчеты.ПерерасчетДоп.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.ОбъектПерерасчета.Значение = ВыборкаДетальныеЗаписи.Регистратор;
Запись = НаборЗаписей.Добавить();
Запись.ВидРасчета = ПланыВидовРасчета.ДополнительныеНачисления.Премия;
Запись.Подразделение = ЭтотОбъект.Отбор.Подразделение.Значение;
НаборЗаписей.Записать();
КонецЦикла;
КонецПроцедуры
Механизмы формирования записей в таблицах перерасчета рассмотрены, вернемся к условию задачи, согласно которому нам необходимо добавить в конфигурацию отчет "Перерасчет зарплаты". В отчете нам нужно просто вывести записи таблиц перерасчетов "ПерерасчетОсн" и "ПерерасчетДоп". Ниже запрос и сам отчет.
ВЫБРАТЬ
ПерерасчетДоп.ОбъектПерерасчета КАК ОбъектПерерасчета,
ПерерасчетДоп.ВидРасчета КАК ВидРасчета,
ПерерасчетДоп.Подразделение КАК Подразделение,
NULL КАК Сотрудник
ИЗ
РегистрРасчета.ДополнительныеНачисления.ПерерасчетДоп КАК ПерерасчетДоп
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ПерерасчетОсн.ОбъектПерерасчета,
ПерерасчетОсн.ВидРасчета,
ПерерасчетОсн.Подразделение,
ПерерасчетОсн.Сотрудник
ИЗ
РегистрРасчета.ОсновныеНачисления.ПерерасчетОсн КАК ПерерасчетОсн
Немного изменим условие.
Создать отчет «Перерасчет зарплаты», в котором пользователь должен не только увидеть записи регистра расчета, которые возможно требуется пересчитать, но и выполнить саму процедуру перерасчета.
Процедура перерасчета должна автоматически пересчитать нужные виды расчета по конкретным сотрудникам и подразделениям. Следует подчеркнуть, что процедура перерасчета - это не автоматизированное перепроведение документов "НачислениеЗарплаты". Ведь если мы ввели информацию о командировке одного сотрудника, то пересчитать оклад и премию необходимо именно у него. Представим, что в документе сотни сотрудников и перепроводить весь документ неоптимально.
Процедура перерасчета должна пересчитать только необходимые виды расчетов конкретных сотрудников и подразделений и скорректировать движения именно по ним.
Так как перерасчет ничем не отличается от начального расчета (алгоритм тот же), то логично использовать один и тот же код. Сейчас расчет происходит в процедуре "ОбработкаПроведения" документа "НачислениеЗарплаты". Для того, чтобы иметь возможность вызывать его и при проведении документа, и при перерасчете из отчета, перенесем код расчета в процедуру общего модуля. Согласно алгоритму проведения документа "РасчетЗарплаты", сначала мы записываем в регистр расчета данные табличных частей, затем получаем необходимые для расчета данные из информационной базы, производим расчет и дописываем результат в сформированные на первом шаге движения.
Именно получение необходимых для расчета данных, сам расчет и изменение движений перенесем в экспортную процедуру "Рассчитать" общего модуля "Периодические расчеты. Нам потребуется передать параметры для расчета. Какие именно параметры нужны проще определить используя синтаксический контроль.
Последовательно исправим ошибки модуля добавлением необходимых параметров в определение процедуры, а затем вызываем её из процедуры "ОбработкаПроведения" модуля документа "НачислениеЗарплаты".
ПериодическиеРасчеты.Рассчитать(Дата, Ссылка, Движения);
Код работает, но этого недостаточно для проведения перерасчета из отчета, ведь нам необходимо иметь возможность пересчитать только необходимые виды расчетов конкретных сотрудников и подразделений. Для этого передадим в процедуру таблицу значений "ТаблицаРасчета" с видами расчета, сотрудниками и подразделениями, которые необходимо пересчитать.
Вызов расчета из процедуры "ОбработкаПроведения" документа "РасчетЗарплаты" немного усложнится в связи с необходимостью подготовки описанной таблицы.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ОсновныеНачисления.ВидРасчета КАК ВидРасчета,
| ОсновныеНачисления.Сотрудник КАК Сотрудник,
| ОсновныеНачисления.Подразделение КАК Подразделение
|ИЗ
| РегистрРасчета.ОсновныеНачисления КАК ОсновныеНачисления
|ГДЕ
| ОсновныеНачисления.Регистратор = &Ссылка
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| ДополнительныеНачисления.ВидРасчета,
| ДополнительныеНачисления.Сотрудник,
| ДополнительныеНачисления.Подразделение
|ИЗ
| РегистрРасчета.ДополнительныеНачисления КАК ДополнительныеНачисления
|ГДЕ
| ДополнительныеНачисления.Регистратор = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
РезультатЗапроса = Запрос.Выполнить();
ТаблицаРасчета = Запрос.Выполнить().Выгрузить();
ЭтоПерерасчет = ложь;
ПериодическиеРасчеты.Рассчитать(Дата, Ссылка, Движения, ТаблицаРасчета, ЭтоПерерасчет);
Процедуру "Рассчитать" общего модуля "Периодические расчеты" построим следующим образом.
Процедура Рассчитать(Дата, Ссылка, Движения, ТаблицаРасчета, ЭтоПерерасчет) Экспорт
Для каждого СтрокаРасчета из ТаблицаРасчета Цикл
Если СтрокаРасчета.ВидРасчета = ПланыВидовРасчета.ОсновныеНачисления.Оклад Тогда
// Расчет оклада
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ОсновныеНачисленияДанныеГрафика.НомерСтроки КАК НомерСтроки,
| ОсновныеНачисленияДанныеГрафика.ЗначениеПериодДействия КАК ПланЧасов,
| ОсновныеНачисленияДанныеГрафика.ЗначениеФактическийПериодДействия КАК ФактЧасов,
| ЕСТЬNULL(СведенияОСотрудникахСрезПоследних.Оклад, 0) КАК Оклад
|ИЗ
| РегистрРасчета.ОсновныеНачисления.ДанныеГрафика(
| Регистратор = &Ссылка
| И ВидРасчета = &Оклад
| И Сотрудник = &Сотрудник
| И Подразделение = &Подразделение) КАК ОсновныеНачисленияДанныеГрафика
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СведенияОСотрудниках.СрезПоследних(&НачалоПериода, ) КАК СведенияОСотрудникахСрезПоследних
| ПО ОсновныеНачисленияДанныеГрафика.Сотрудник = СведенияОСотрудникахСрезПоследних.Сотрудник
| И ОсновныеНачисленияДанныеГрафика.Подразделение = СведенияОСотрудникахСрезПоследних.Подразделение";
Запрос.УстановитьПараметр("НачалоПериода", НачалоМесяца(Дата));
Запрос.УстановитьПараметр("Оклад", ПланыВидовРасчета.ОсновныеНачисления.Оклад);
Запрос.УстановитьПараметр("Сотрудник", СтрокаРасчета.Сотрудник);
Запрос.УстановитьПараметр("Подразделение", СтрокаРасчета.Подразделение);
Запрос.УстановитьПараметр("Ссылка", Ссылка);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДляОклада = РезультатЗапроса.Выбрать();
Для каждого СтрокаНабора из Движения.ОсновныеНачисления Цикл
ВыборкаДляОклада.Сбросить();
Если ВыборкаДляОклада.НайтиСледующий(СтрокаНабора.НомерСтроки,"НомерСтроки") Тогда
Если СтрокаНабора.Сторно Тогда
СтрокаНабора.Результат = -СтрокаНабора.Оклад/ВыборкаДляОклада.ПланЧасов*ВыборкаДляОклада.ФактЧасов;
Иначе
СтрокаНабора.Оклад = ВыборкаДляОклада.Оклад;
СтрокаНабора.Результат = ВыборкаДляОклада.Оклад/ВыборкаДляОклада.ПланЧасов*ВыборкаДляОклада.ФактЧасов;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Если ЭтоПерерасчет Тогда
Движения.ОсновныеНачисления.Записать(,,Ложь);
Иначе
Движения.ОсновныеНачисления.Записать(,Истина);
КонецЕсли;
ИначеЕсли СтрокаРасчета.ВидРасчета = ПланыВидовРасчета.ДополнительныеНачисления.Премия Тогда
// Расчет премии руководителя подразделения
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ДополнительныеНачисленияБазаОсновныеНачисления.НомерСтроки КАК НомерСтроки,
| СУММА(ДополнительныеНачисленияБазаОсновныеНачисления.РезультатБаза) КАК РезультатБаза,
| ЕСТЬNULL(СведенияОСотрудникахСрезПоследних.ПроцентПремии, 0) КАК ПроцентПремии
|ИЗ
| РегистрРасчета.ДополнительныеНачисления.БазаОсновныеНачисления(
| &Измерения,
| &Измерения,
| &Разрезы,
| Регистратор = &Ссылка
| И Подразделение = &Подразделение) КАК ДополнительныеНачисленияБазаОсновныеНачисления
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СведенияОСотрудниках.СрезПоследних(&НачалоМесяца, ) КАК СведенияОСотрудникахСрезПоследних
| ПО ДополнительныеНачисленияБазаОсновныеНачисления.Сотрудник = СведенияОСотрудникахСрезПоследних.Сотрудник
| И ДополнительныеНачисленияБазаОсновныеНачисления.Подразделение = СведенияОСотрудникахСрезПоследних.Подразделение
|ГДЕ
| ДополнительныеНачисленияБазаОсновныеНачисления.СотрудникРазрез <> ДополнительныеНачисленияБазаОсновныеНачисления.Сотрудник
|
|СГРУППИРОВАТЬ ПО
| ДополнительныеНачисленияБазаОсновныеНачисления.НомерСтроки,
| ЕСТЬNULL(СведенияОСотрудникахСрезПоследних.ПроцентПремии, 0)";
Измерения = Новый Массив;
Измерения.Добавить("Подразделение");
Запрос.УстановитьПараметр("Измерения", Измерения);
Разрезы = Новый Массив;
Разрезы.Добавить("Сотрудник");
Запрос.УстановитьПараметр("Разрезы", Разрезы);
Запрос.УстановитьПараметр("НачалоМесяца", НачалоМесяца(Дата));
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Запрос.УстановитьПараметр("Подразделение", СтрокаРасчета.Подразделение);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДляПремии = РезультатЗапроса.Выбрать();
Для каждого СтрокаНабора из Движения.ДополнительныеНачисления Цикл
ВыборкаДляПремии.Сбросить();
Если ВыборкаДляПремии.НайтиСледующий(СтрокаНабора.НомерСтроки,"НомерСтроки") Тогда
СтрокаНабора.Результат = ВыборкаДляПремии.РезультатБаза*ВыборкаДляПремии.ПроцентПремии/100;
КонецЕсли;
КонецЦикла;
Движения.ДополнительныеНачисления.Записать();
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Перебирая строки таблицы "ТаблицаРасчета" выполняем расчет нужного вида расчета, а получаемые запросом данные для расчета отбираем по нужным сотрудникам и подразделениям, пересчитывая таким образом только движения по ним. Также обратите внимание на запись движений оклада.
Если ЭтоПерерасчет Тогда
Движения.ОсновныеНачисления.Записать(,,Ложь);
Иначе
Движения.ОсновныеНачисления.Записать(,Истина);
КонецЕсли;
Ранее при записи результата оклада мы заполнили параметр "ТолькоЗапись" значением Истина для того, чтобы не перезаписывать записи фактического периода и перерасчетов, ведь это уже произошло при заполнении движений по табличным частям. Это позволяет оптимизировать код (не выполняются ненужные действия).
Но если мы будем производить перерасчет, то перед этим не будет происходить заполнения движений по табличным частям. Записи фактического периода по-прежнему перезаписывать не стоит, а вот записи перерасчета могут появиться (т.к. оклад является ведущим по отношению к премии) и по-хорошему их надо записать. В связи с этим, передаем в процедуру дополнительный параметр ЭтоПерерасчет, и в случае значения Истина записываем движения не перезаписывая только записи фактического периода действия.
Только что описанное не актуально для премии т.к. план видов расчета "ДополнительныеНачисления" не использует период действия (таблицы фактического периода нет) и премия не является ведущим видом расчета (перерасчеты породить не может).
Без этой тонкой настройки записи движений глобально ничего не изменится, можно везде использовать Записать(). Но такой код свидетельствует о понимании программистом механизма работы регистров расчета.
Перейдем к отчету "Перерасчет зарплаты", который пока только выводит необходимые для перерасчета строки.
Нам необходимо добавить на форму отчета кнопку "Перерасчет" и ее обработчик. В связи с этим создадим основную форму отчета через конструктор.
На форме необходимо добавить команду "Перерасчет", перетянуть на форму в виде кнопки, и создать для команды обработчик.
В обработчике команды на сервере создадим запрос, используя конструктор запросов с обработкой результата. Текст запроса будет такой же, как в схеме компановки этого отчета, за исключением того, что обходить его будем по группировкам ОбъектПерерасчета (это документ "НачислениеЗарплаты").
//{{КОНСТРУКТОР_ЗАПРОСА_С_ОБРАБОТКОЙ_РЕЗУЛЬТАТА
// Данный фрагмент построен конструктором.
// При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПерерасчетДоп.ОбъектПерерасчета КАК ОбъектПерерасчета,
| ПерерасчетДоп.ВидРасчета КАК ВидРасчета,
| ПерерасчетДоп.Подразделение КАК Подразделение,
| NULL КАК Сотрудник
|ИЗ
| РегистрРасчета.ДополнительныеНачисления.ПерерасчетДоп КАК ПерерасчетДоп
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| ПерерасчетОсн.ОбъектПерерасчета,
| ПерерасчетОсн.ВидРасчета,
| ПерерасчетОсн.Подразделение,
| ПерерасчетОсн.Сотрудник
|ИЗ
| РегистрРасчета.ОсновныеНачисления.ПерерасчетОсн КАК ПерерасчетОсн
|ИТОГИ ПО
| ОбъектПерерасчета";
РезультатЗапроса = Запрос.Выполнить();
ВыборкаОбъектПерерасчета = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаОбъектПерерасчета.Следующий() Цикл
// Вставить обработку выборки ВыборкаОбъектПерерасчета
ВыборкаДетальныеЗаписи = ВыборкаОбъектПерерасчета.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
// Вставить обработку выборки ВыборкаДетальныеЗаписи
КонецЦикла;
КонецЦикла;
//}}КОНСТРУКТОР_ЗАПРОСА_С_ОБРАБОТКОЙ_РЕЗУЛЬТАТА
Перерасчет будем запускать в процессе обхода записей перерасчета. Обходя выборку "ВыборкаОбъектРасчета" получим Ссылку, Дату и Движения документа "НачислениеЗарплаты", обходя выборку "ВыборкаДетальныеЗаписи" соберем таблицу расчета. Этого достаточно для запуска процедуры "ПериодическиеРасчеты.Рассчитать".
Кроме того, после перерасчета необходимо очистить таблицу перерасчетов, но ведь в процессе перерасчета могут возникнуть новые записи в таблице, которые не надо удалять. В связи с этим очистку лучше сделать после выполнения запроса, но до обхода его результата. Но если мы очистим таблицу перерасчета раньше самого перерасчета, то должны быть уверены, что перерасчет пройдет успешно. Для этого воспользуемся транзакциями: если в процессе перерасчета произойдет ошибка, то все действия, включая очистку таблиц перерасчета, откатятся. Вот весь модуль формы отчета.
&НаСервере
Процедура ПерерасчетНаСервере()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПерерасчетДоп.ОбъектПерерасчета КАК ОбъектПерерасчета,
| ПерерасчетДоп.ВидРасчета КАК ВидРасчета,
| ПерерасчетДоп.Подразделение КАК Подразделение,
| NULL КАК Сотрудник
|ИЗ
| РегистрРасчета.ДополнительныеНачисления.ПерерасчетДоп КАК ПерерасчетДоп
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| ПерерасчетОсн.ОбъектПерерасчета,
| ПерерасчетОсн.ВидРасчета,
| ПерерасчетОсн.Подразделение,
| ПерерасчетОсн.Сотрудник
|ИЗ
| РегистрРасчета.ОсновныеНачисления.ПерерасчетОсн КАК ПерерасчетОсн
|ИТОГИ ПО
| ОбъектПерерасчета";
РезультатЗапроса = Запрос.Выполнить();
ВыборкаОбъектПерерасчета = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
НачатьТранзакцию();
Попытка
ОчиститьТаблицыПерерасчета();
Пока ВыборкаОбъектПерерасчета.Следующий() Цикл
Ссылка = ВыборкаОбъектПерерасчета.ОбъектПерерасчета;
Объект = Ссылка.ПолучитьОбъект();
Движения = Объект.Движения;
Движения.ОсновныеНачисления.Прочитать();
Движения.ДополнительныеНачисления.Прочитать();
Дата = Объект.Дата;
ТаблицаРасчета = Новый ТаблицаЗначений;
ТаблицаРасчета.Колонки.Добавить("ВидРасчета");
ТаблицаРасчета.Колонки.Добавить("Подразделение");
ТаблицаРасчета.Колонки.Добавить("Сотрудник");
ВыборкаДетальныеЗаписи = ВыборкаОбъектПерерасчета.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
НоваяСтрокаТаблицыРасчета = ТаблицаРасчета.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрокаТаблицыРасчета, ВыборкаДетальныеЗаписи);
КонецЦикла;
ЭтоПерерасчет = Истина;
ПериодическиеРасчеты.Рассчитать(Дата, Ссылка, Движения, ТаблицаРасчета, ЭтоПерерасчет);
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
КонецПопытки;
КонецПроцедуры
&НаКлиенте
Процедура Перерасчет(Команда)
ПерерасчетНаСервере();
КонецПроцедуры
&НаСервере
Процедура ОчиститьТаблицыПерерасчета()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПерерасчетОсн.ОбъектПерерасчета КАК ОбъектПерерасчета
|ИЗ
| РегистрРасчета.ОсновныеНачисления.ПерерасчетОсн КАК ПерерасчетОсн
|ИТОГИ ПО
| ОбъектПерерасчета";
РезультатЗапроса = Запрос.Выполнить();
ВыборкаОбъектПерерасчета = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаОбъектПерерасчета.Следующий() Цикл
НаборЗаписей = РегистрыРасчета.ОсновныеНачисления.Перерасчеты.ПерерасчетОсн.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.ОбъектПерерасчета.Установить(ВыборкаОбъектПерерасчета.ОбъектПерерасчета);
НаборЗаписей.Записать();
КонецЦикла;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПерерасчетДоп.ОбъектПерерасчета КАК ОбъектПерерасчета
|ИЗ
| РегистрРасчета.ДополнительныеНачисления.ПерерасчетДоп КАК ПерерасчетДоп
|ИТОГИ ПО
| ОбъектПерерасчета";
РезультатЗапроса = Запрос.Выполнить();
ВыборкаОбъектПерерасчета = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаОбъектПерерасчета.Следующий() Цикл
НаборЗаписей = РегистрыРасчета.ДополнительныеНачисления.Перерасчеты.ПерерасчетДоп.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.ОбъектПерерасчета.Установить(ВыборкаОбъектПерерасчета.ОбъектПерерасчета);
НаборЗаписей.Записать();
КонецЦикла;
КонецПроцедуры
Ранее мы рассмотрели начисление оклада и командировки наиболее популярным в задачах методом "отклонений", при котором отработанное время определяется на основании данных графика работы и зарегистрированных отклонений от графика ("вытеснение" невыходом, командировкой).
В задачах реже, но также встречается начисление по Табелю.
Рассмотрим начисление оклада и командировки при измененном начальном условии:
Начисление зарплаты сотрудникам предприятия осуществляется ежемесячно с использованием метода отклонений. Все сотрудники работают по пятидневному графику работы, однако в решении необходимо предусмотреть возможность работы по нескольким различным графикам.
Количество фактически отработанных часов вводится в систему с помощью документа «Табель». Документ может заполняться на список сотрудников. Для каждого сотрудника, на каждый день месяца, вводится количество фактически отработанных часов на основном месте работы и в командировке.
Сотрудники предприятия получают оплату по окладу пропорционально отработанному времени в часах. Часовая ставка рассчитывается как начальное значение оклада, деленное на количество рабочих часов в том же периоде, что и фактически отработанные часы. Первоначальное значение оклада может изменяться не чаще, чем один раз в день, но берется на начало расчетного периода.
По мере необходимости любой сотрудник может быть отправлен в командировку. В этом случае начисление по окладу не происходит. Начисление командировочных происходит фиксированной суммой за все время, в течение которого сотрудник находился в командировке.
Возьмем чистую каркасную конфигурацию. Первое, с чем нужно определиться - планы видов расчета и предопределенные виды расчета "Оклад" и Командировка".
Вид расчета "Оклад" у нас находится в плане видов расчета "ОсновныеНачисления". Самое важное в настройке плана видов расчета - определиться с галочкой "Использует период действия". Для расчета нам нужно будет фактическое время работы и плановое время согласно графику. Фактическое мы возьмем из табеля, для этого галочка "Использует период действия" не нужна. Плановое время нужно определить по графику. Для получения данных графика галочку "Использует период действия" нужно поставить.
Теперь нужно решить в каком плане видов расчета мы заведем вид расчета "Командировка". Решение также зависит от необходимости галочки "Использует период действия". Для расчета командировки нам не нужно ничего т.к. начисляется она фиксированной суммой. Но по условию начисление оклада в период командировки не происходит. Ранее при использовании метода отклонений мы использовали механизм вытеснения, для работы которого необходимо, чтобы вытесняющий и вытесняемый виды расчета были в одном плане видов расчета. По этой причине вид расчета "Командировка" мы заводили в плане "ОсновныеНачисления". Сейчас мы используем начисление по табелю и вытеснение не требуется т.к. и оклад и командировка отмечаются в документе "Табель" и никогда не будут конкурировать за место на оси времени (в ячейке табеля можно отметить либо оклад, тибо командировку). По этой причине предопределенный вид расчета "Командировка" заведем в плане "ДополнительныеНачисления", и снимем галочку "Использует период действия" у последнего.
Далее создадим регистры расчета для каждого плана.
Для выполнения условия работы по нескольким графикам заведем справочник "Графики", подправим обработку заполнения графиков и заполним график "пятидневка" за ноябрь 2020 года. Это подробно описано ранее.
Перейдем к табелю. Создадим документ "Табель", табличную часть "ФактическоеВремя", включающую реквизиты "Сотрудник" (тип СправочникСсылка.ФизическиеЛица) и реквизиты типа Строка длиной 2 (для удобства формы) для каждого календарного дня месяца. Удобнее всего первый реквизит назвать "_1", а остальные создать копированием "F9".
Условимся заполнять табель следующим образом: "О4" - оклад 4 часа; "К3" - командировка 3 часа. К примеру, Бельдыев отработал в ноябре 2020 года неделю: 4 дня на основном рабочем месте и 1 день в командировке.
При расчете оклада данные табеля должны заменить нам таблицу фактического периода. То есть, при расчете мы должны иметь возможность получить число отработанных часов за заданный период. Это означает, что хранить часы оклада нужно в периодическом регистре. Можно использовать периодический регистр сведений, но тогда количество часов за период нужно будет считать, поэтому проще использовать регистр накопления (Период является стандартным измерением регистров накопления). Не забываем анализировать необходимость учета остатков по регистру накопления:
- данный регистр нам необходим для подсчета часов за период (обороты);
- мы не будем списывать часы по этому регистру (движения только в "плюс";
- нам не требуется получать остатки на конкретную дату по этому регистру.
В связи с изложенным, создадим регистр накопления "ФактическоеВремя" с видом "Обороты", регистратором которого будет документ "Табель". Измерение - "Сотрудник", ресурс - "КоличествоЧасов".
Перейдем к движениям. К сожалению, конструктор движений нам мало поможет:
Всего реквизитов (дней) - 31. Каждый надо проверить на наличие "О" (оклад), прочитать из строки количество часов и сформировать движение в регистр за дату, соответствующую регистру (к примеру, "_1" - 01.09.2020).
Если СтрНайти(ТекСтрокаФактическоеВремя._2,"О")>0 Тогда
Движение = Движения.ФактическоеВремя.Добавить();
Движение.Период = НачалоМесяца(Дата)+60*60*24;
Движение.Сотрудник = ТекСтрокаФактическоеВремя.Сотрудник;
Движение.КоличествоЧасов = Число(СтрЗаменить(ТекСтрокаФактическоеВремя._2,"О",""));
КонецЕсли;
Эти 6 строк кода необходимо отработать для каждого из 31 реквизита. Повторять один и тот же код 31 раз не хочется, но что делать?
На помощь приходит метод "Метаданные()", который позволяет программно получить структуру документа и обойти реквизиты.
Используя точки останова и вычисление выражений можно быстрее написать рабочий код, чем постоянно обновляя конфигурацию и проверяя ошибки. Через метод "Метаданные()" можно получить имена реквизитов табличной части "ФактическоеВремя" и затем обратиться к их значениям используя [квадратные скобки]. Вот что получилось.
Процедура ОбработкаПроведения(Отказ, Режим)
// регистр ФактическоеВремя
Движения.ФактическоеВремя.Записывать = Истина;
Для Каждого ТекСтрокаФактическоеВремя Из ФактическоеВремя Цикл
Для каждого Реквизит из Метаданные().ТабличныеЧасти.ФактическоеВремя.Реквизиты Цикл
Если Реквизит.Имя <> "Сотрудник" И СтрНайти(ТекСтрокаФактическоеВремя[Реквизит.Имя],"О")>0 Тогда
Движение = Движения.ФактическоеВремя.Добавить();
Движение.Период = НачалоМесяца(Дата)+60*60*24*(Число(Реквизит.Синоним)-1);
Движение.Сотрудник = ТекСтрокаФактическоеВремя.Сотрудник;
Движение.КоличествоЧасов = Число(СтрЗаменить(ТекСтрокаФактическоеВремя[Реквизит.Имя],"О",""));
КонецЕсли;
КонецЦикла
КонецЦикла;
//{{__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
// Данный фрагмент построен конструктором.
// При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!
//}}__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
КонецПроцедуры
Проведем табель и убедимся, что фактическое отработанное время зафиксировано в регистре накопления "ФактическоеВремя".
Перейдем к документу "НачислениеЗарплаты". Используя конструктор движений сформируем обработку проведения документа.
Процедура ОбработкаПроведения(Отказ, Режим)
//{{__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
// Данный фрагмент построен конструктором.
// При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!
// регистр ОсновныеНачисления
Движения.ОсновныеНачисления.Записывать = Истина;
Для Каждого ТекСтрокаОсновныеНачисления Из ОсновныеНачисления Цикл
Движение = Движения.ОсновныеНачисления.Добавить();
Движение.Сторно = Ложь;
Движение.ВидРасчета = ТекСтрокаОсновныеНачисления.ВидРасчета;
Движение.ПериодДействияНачало = ТекСтрокаОсновныеНачисления.ДатаНачала;
Движение.ПериодДействияКонец = ТекСтрокаОсновныеНачисления.ДатаОкончания;
Движение.ПериодРегистрации = Дата;
Движение.Сотрудник = ТекСтрокаОсновныеНачисления.Сотрудник;
Движение.Результат = 0;
КонецЦикла;
// регистр ДополнительныеНачисления
Движения.ДополнительныеНачисления.Записывать = Истина;
Для Каждого ТекСтрокаДополнительныеНачисления Из ДополнительныеНачисления Цикл
Движение = Движения.ДополнительныеНачисления.Добавить();
Движение.Сторно = Ложь;
Движение.ВидРасчета = ТекСтрокаДополнительныеНачисления.ВидРасчета;
Движение.ПериодРегистрации = ТекСтрокаДополнительныеНачисления.ДатаНачала;
Движение.Сотрудник = ТекСтрокаДополнительныеНачисления.Сотрудник;
Движение.Результат = ТекСтрокаДополнительныеНачисления.Размер;
КонецЦикла;
//}}__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
КонецПроцедуры
Согласно алгоритму проведения документа "НачислениеЗарплаты" после записи наборов движений по табличным частям необходимо из информационной базы получить данные для расчета результата начисления. Для расчета оклада нам потребуются данные графика для получения планового времени, данные регистра накопления "ФактическоеВремя" для получения фактического времени и сведения о начальном окладе.
Создадим документ "НачислениеЗарплаты" и начислим Бельдыеву оклад за ноябрь 2020.
Теперь используя консоль запросов получим необходимые данные для расчета.
Согласно табелю Бельдыев отработал 4 дня (32 часа), согласно календарю в ноябре 2020 года 21 рабочий день (186 часов), оклад у Бельдыева установлен 30000. Всё верно, оклад должен получиться 30000/168*32 = 5714,29 руб. Ниже готовая процедура ОбработкаПроведения документа "НачислениеЗарплаты" и движения документа.
Процедура ОбработкаПроведения(Отказ, Режим)
// регистр ОсновныеНачисления
Движения.ОсновныеНачисления.Записывать = Истина;
Для Каждого ТекСтрокаОсновныеНачисления Из ОсновныеНачисления Цикл
Движение = Движения.ОсновныеНачисления.Добавить();
Движение.Сторно = Ложь;
Движение.ВидРасчета = ТекСтрокаОсновныеНачисления.ВидРасчета;
Движение.ПериодДействияНачало = ТекСтрокаОсновныеНачисления.ДатаНачала;
Движение.ПериодДействияКонец = ТекСтрокаОсновныеНачисления.ДатаОкончания;
Движение.ПериодРегистрации = Дата;
Движение.Сотрудник = ТекСтрокаОсновныеНачисления.Сотрудник;
Движение.Результат = 0;
КонецЦикла;
// регистр ДополнительныеНачисления
Движения.ДополнительныеНачисления.Записывать = Истина;
Для Каждого ТекСтрокаДополнительныеНачисления Из ДополнительныеНачисления Цикл
Движение = Движения.ДополнительныеНачисления.Добавить();
Движение.Сторно = Ложь;
Движение.ВидРасчета = ТекСтрокаДополнительныеНачисления.ВидРасчета;
Движение.ПериодРегистрации = ТекСтрокаДополнительныеНачисления.ДатаНачала;
Движение.Сотрудник = ТекСтрокаДополнительныеНачисления.Сотрудник;
Движение.Результат = ТекСтрокаДополнительныеНачисления.Размер;
КонецЦикла;
Движения.Записать();
// расчет оклада
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ОсновныеНачисленияДанныеГрафика.НомерСтроки КАК НомерСтроки,
| ОсновныеНачисленияДанныеГрафика.Сотрудник КАК Сотрудник,
| ОсновныеНачисленияДанныеГрафика.ЗначениеПериодДействия КАК План,
| ЕСТЬNULL(СведенияОСотрудникахСрезПоследних.Оклад, 0) КАК Оклад,
| ЕСТЬNULL(ФактическоеВремяОбороты.КоличествоЧасовОборот, 0) КАК Факт
|ИЗ
| РегистрРасчета.ОсновныеНачисления.ДанныеГрафика(Регистратор = &Ссылка) КАК ОсновныеНачисленияДанныеГрафика
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СведенияОСотрудниках.СрезПоследних КАК СведенияОСотрудникахСрезПоследних
| ПО ОсновныеНачисленияДанныеГрафика.Сотрудник = СведенияОСотрудникахСрезПоследних.Сотрудник
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ФактическоеВремя.Обороты КАК ФактическоеВремяОбороты
| ПО ОсновныеНачисленияДанныеГрафика.Сотрудник = ФактическоеВремяОбороты.Сотрудник";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаОклад = РезультатЗапроса.Выбрать();
Для каждого СтрокаНабора из Движения.ОсновныеНачисления Цикл
ВыборкаОклад.Сбросить();
Если ВыборкаОклад.НайтиСледующий(СтрокаНабора.НомерСтроки, "НомерСтроки") Тогда
СтрокаНабора.Результат = ВыборкаОклад.Оклад/ВыборкаОклад.План*ВыборкаОклад.Факт;
КонецЕсли;
КонецЦикла;
Движения.ОсновныеНачисления.Записать(,Истина);
//{{__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
// Данный фрагмент построен конструктором.
// При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!
//}}__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
КонецПроцедуры
Решение задач по бизнес-процессам
Условие задачи по бизнес процессам - это блок-схема (карта маршрута) и таблица с сотрудниками, их должностями и отделами.
Сотрудник | Подразделение | Должность |
Васина | Бухгалтерия | Кассир |
Мишина | Бухгалтерия | Кассир |
Мишина | Бухгалтерия | Бухгалтер |
Кротов | Бухгалтерия | Бухгалтер |
Иванов | Бухгалтерия | Гл. бухгалтер |
Онопко | Отдел закупок | Начальник отдела |
Петренко | Отдел закупок | Зам. начальника отдела |
Бельдыев | Отдел закупок | Менеджер |
Рахимов | Отдел закупок | Менеджер |
Мансуров | Отдел закупок | Менеджер |
Жупиков | Отдел закупок | Кладовщик |
Сидоров | Отдел закупок | Кладовщик |
Галкин | Отдел продаж | Менеджер |
Палкин | Отдел продаж | Менеджер |
Механизм бизнес-процессов работает следующим образом.
Бизнес-процесс аналогичен справочнику. Бизнес-процессов как и справочников может быть много. Справочники отличаются структурами хранимых данных, а бизнес-процессы - картами маршрута. Карта маршрута - алгоритм решения какой-то производственной задачи в виде блок-схемы. К примеру, наша блок-схема (выше) - это описание процесса закупки товаров.
В режиме 1С:Предприятие один из пользователей стартует бизнес-процесс (по сути создает элемент справочника). К примеру, клиент заказал у нас компьютер, и нам необходимо заказать комплектующие у поставщика.
После запуска бизнес-процесса первая задача по нашей схеме - заказ товара. Выполнить ее может согласно схеме любой сотрудник отдела закупок, поэтому все сотрудники отдела закупок должны увидеть у себя эту задачу. После заказа сотрудник должен отметить выполнение задачи и бизнес процесс автоматически создаст новую задачу бухгалтерам на оплату.
В бизнес процессе как и в справочнике есть реквизиты, которые можно заполнять. В нашем случае от значения реквизита "ОплатаНаличными" зависит какая задача будет создана - "Оплата через банк" или "Оплата из кассы". Задачу "Оплата через банк" должны увидеть только сотрудники с должностью "Бухгалтер" из отдела "Бухгалтерия", а "Оплата из кассы" - только сотрудники с должностью "Кассир" из отдела "Бухгалтерия". После отметки о выполнении задачи механизм бизнес-процессов должен создать задачу именно пользователю "Сидоров" на оприходование товаров, после выполнения который бизнес-процесс (элемент справочника) считается завершенным. Предполагается, что его данные и связанные с ним задачи более редактироваться не будут. Для начала новой закупки необходимо создать новый элемент бизнес-процесса.
Механизм подразумевает, что все пользователи в системе захоят под своим Пользоваиелем. Для этого необходимо в конфигурации создать роль "Администратор".
Далее нужно завести отсутствующих сотрудников в предопределенных элементах справочника "ФизическиеЛица" и соответствующих пользователей (все с ролью Администратор) в базе Администрирование - Пользователи.
Программно можно определить какой пользователь сейчас работает в системе следующим образом.
ИмяПользователя()
Информация о сотрудниках содержится в справочнике "Физические лица", поэтому каждому пользователю должно соответствовать физическое лицо. В нашем случае имя пользователя и имя физического лица совпадают, поэтому текущего сотрудника можно определить так.
Справочники.ФизическиеЛица.НайтиПоКоду(ИмяПользователя())
Задачи бизнес-процессов определяют текущего сотрудника по параметру сеанса. Создадим его.
Его необходимо заполнить при запуске сеанса. Сделаем это в модуле сеанса (правой кнопкой на конфигурацию - Модуль сеанса).
Для адресации задач служит регистр сведений "РегистрАдресации", присутствующий в каркасной конфигурации с одним измерением "Исполнитель" типа "СправочникСсылка.ФизическиеЛица".
Как описано выше, задачи могут быть адресованы как конкретным сотрудникам, так и должностям и подразделениям, поэтому нам нужно добавить измерения "Должность" и "Подразделение".
Создадим справочник "Должности", заполним предопределенные значения.
Добавим необходимые измерения в регистр сведений "РегистрАдресации" и заполним его в режиме 1С:Предприятие, внимательно проходя по каждой задаче блок-схемы:
- Заказ товаров может сделать любой сотрудник отдела закупок. В отделе закупок работает 7 человек, поэтому в регистре создаем 7 строк с указанием исполнителя и подразделения. Обратите внимание, что должность заполнять не нужно.
- Оплата через банк. Любой бухгалтер из бухгалтерии. Заполняем всех бухгалтеров из бухгалтерии. Так как указаны подразделения и должности, то указываем их.
- Оплата из кассы. Любой кассир из бухгалтерии. Заполняем всех кассиров из бухгалтерии. Так как указаны подразделения и должности, то указываем их.
- Оприходование товара. Сидоров. Не указано ни подразделение, ни должность. Даже если он станет директором, это не должно повлиять на адресацию, поэтому ни должность, ни подразделение не указываем. Вот что получилось.
Далее в конфигураторе создаем задачу и заполняем ее параметры адресации.
При входе пользователь должен увидеть список своих невыполненных задач. Для этого создадим форму списка, используя конструктор.
Для динамического списка формы основной таблицей выбрать виртуальную таблицу "ЗадачиПоИсполнителю", в которой будут только задачи исполнителя, указанного в параметре сеанса "Текущий исполнитель".
Настроим, чтобы список выводил только невыполненные задачи.
Отменим использование формы в качестве основной и настроим ее отображение на начальной странице.
Далее создаем бизнес-процесс, на вкладке "Основное" указываем в поле "Задачи" только что созданную задачу, на вкладке "Данные" в создадим реквизит с типом "Булево" под названием "ОплатаНаличными", на вкладке "Дополнительно" необходимо нажать "Карта маршрут" и нарисовать карту по условию.
Для всех элементов схемы "Точка действия" заполняем адресацию. Если на схеме условия задачи прямоугольник состоит из нескольких, то ставим галочку "Групповая".
Для элемента "Точка условия" необходимо прописать условие.
Процедура ОплатаНаличнымиПроверкаУсловия(ТочкаМаршрутаБизнесПроцесса, Результат)
Результат = ОплатаНаличными;
КонецПроцедуры
Автоматически генерируемая форма бизнес-процесса не содержит карту маршрута, поэтому создаем форму элемента. В форме создаём реквизит типа "ГрафическаяСхема" и перетягиваем на форму.
В модуле формы прописываем код.
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
ОбъектЗначение = РеквизитФормыВЗначение("Объект");
ГрафическаяСхема = ОбъектЗначение.ПолучитьКартуМаршрута();
КонецПроцедуры
Теперь в режиме 1С:Предприятие создаем бизнес-процесс и заходя под разными пользователями проверяем работоспособность.
Решение задач по управляемым формам
Работая с управляемыми формами необходимо уметь настраивать внешний вид и поведение элементов, запускать формы с параметрами, вызывать одну форму из другой, передавать данные между клиентом и сервером и т.п. Рассмотрим следующее условие задачи.
В документе «Расходная накладная» реализовать возможность подбора сразу нескольких номенклатурных позиций. В специальной форме подбора, открываемой из формы документа, для каждой номенклатурной позиции должен отображаться остаток на дату документа.
В дальнейшем, в форме подбора пользователь должен иметь возможность выбрать произвольное количество номенклатурных позиций, которые в результате отображаются в дополнительной таблице «Отобранные товары», с указанием самого товара и его количества.
Дополнительно должна поддерживаться возможность перетаскивания мышкой выбранного товара из списка товаров в таблицу «Отобранные товары».
После окончания подбора в табличной части документа должны появиться строки со всеми выбранными товарами в указанном количестве.
Возьмем чистую каркасную конфигурацию. Сделаем типовые движения для документа "ПриходнаяНакладная" как описано ранее. Создадим произвольную форму для справочника "Номенклатура", установим свойство формы "РежимОткрытияОкна" в значение "Блокировать окно владельца", чтобы форма была в окне.
Создадим реквизит "Список" типа "ДинамическийСписок", в нем будем отображать номенклатуру с остатками, и реквизит "ПодобранныеТовары" типа "ТаблицаЗначений", в которой будем временно хранить товары для переноса в документ.
Для динамического списка "Список" в свойствах выбираем основную таблицу Справочник.Номенклатура, затем поставим галочку "Произвольный запрос" и перейдем к его настройке. Используя конструктор запросов левым соединением добавим виртуальную таблицу "ОстаткиНоменклатурыОстатки".
В таблице значений "ПодобранныеТовары" добавим 2 столбца: Номенклатура и Количество. Перетянем ее тоже на форму. Для удобства высоту наших таблиц сделаем поменьше, например, равной 3 строкам.
Теперь создадим форму документа "РасходнаяНакладная" и кнопку "Подбор", при нажатии на которую будет вызываться наша форма с параметром "ДатаДокумента". Вот обработчик нажатия кнопки "Подбор".
&НаКлиенте
Процедура Подбор(Команда)
ПараметрыФормы = Новый Структура("ДатаДокумента", Объект.Дата);
ОткрытьФорму("Справочник.Номенклатура.Форма.ФормаПодбора", ПараметрыФормы);
КонецПроцедуры
В свою очередь в форме подбора справочника номенклатуры можно получить этот параметр "ПриСозданииНаСервере" и установить его в качестве параметра динамического списка "Список" вот так.
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
Список.Параметры.УстановитьЗначениеПараметра("ДатаДокумента", Параметры.ДатаДокумента);
КонецПроцедуры
Вот что получилось.
Далее нужно описать обработчики двойного нажатия на номенклатуру (ОбработкаВыбора) и перетаскивания (Перетаскивание) ее в подобранные товары. Вот пример кода.
// Двойное нажатие
&НаКлиенте
Процедура СписокВыбор(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка)
Если НЕ Элементы.Список.ТекущиеДанные.ЭтоГруппа Тогда
СтандартнаяОбработка = Ложь;
ДобавитьНоменклатуру(Элементы.Список.ТекущиеДанные.Ссылка);
КонецЕсли
КонецПроцедуры
// Перетаскивание
&НаКлиенте
Процедура ПодобранныеТоварыПеретаскивание(Элемент, ПараметрыПеретаскивания, СтандартнаяОбработка, Строка, Поле)
ДобавитьНоменклатуру(ПараметрыПеретаскивания.Значение[0]);
КонецПроцедуры
&НаКлиенте
Процедура ДобавитьНоменклатуру(Номенклатура)
ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("Номенклатура", Номенклатура);
НайденныеСтроки = ПодобранныеТовары.НайтиСтроки(ПараметрыОтбора);
Если НайденныеСтроки.Количество() > 0 Тогда
ПодобраннаяСтрока = НайденныеСтроки[0];
ПодобраннаяСтрока.Количество = ПодобраннаяСтрока.Количество + 1;
Иначе
ПодобраннаяСтрока = ПодобранныеТовары.Добавить();
ПодобраннаяСтрока.Номенклатура = Номенклатура;
ПодобраннаяСтрока.Количество = 1;
КонецЕсли
КонецПроцедуры
Осталось передать подобранные товары обратно в документ. Для обмена данными между формами используются оповещения. Создадим в форме подбора кнопку "ПеренестиВДокумент", при нажатии на которую форма подбора оповестит форму документа о том, что товары подобраны.
&НаКлиенте
Процедура ПеренестиВДокумент(Команда)
Оповестить("ТоварыПодобраны",ПодобранныеТовары);
ЭтаФорма.Закрыть();
КонецПроцедуры
Форма документа "РасходнаяНакладная" в свою очередь должна ждать оповещения от формы подбора. Используем обработчик "ОбработкаОповещения".
&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
Если ИмяСобытия = "ТоварыПодобраны" Тогда
Для каждого СтрокаТовара из Параметр Цикл
ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("Номенклатура", СтрокаТовара.Номенклатура);
НайденныеСтроки = Объект.СписокНоменклатуры.НайтиСтроки(ПараметрыОтбора);
Если НайденныеСтроки.Количество() > 0 Тогда
ПодобраннаяСтрока = НайденныеСтроки[0];
ПодобраннаяСтрока.Количество = ПодобраннаяСтрока.Количество + СтрокаТовара.Количество;
Иначе
ПодобраннаяСтрока = Объект.СписокНоменклатуры.Добавить();
ПодобраннаяСтрока.Номенклатура = СтрокаТовара.Номенклатура;
ПодобраннаяСтрока.Количество = СтрокаТовара.Количество;
КонецЕсли
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Перетаскиваем, переносим, всё работает.
Разрабатывая решения на 1С полезно пользоваться всевозможными конструкторами, мастерами, синтаксис-помощником, документацией 1С, отладчиком, точками останова, стэком вызовов, консолью запросов и т.п. Все эти инструменты позволяют минимизировать ошибки, писать меньше кода вручную и, как следствие, решать задачи быстрее, что очень важно в жизни и на экзамене.
Правилом хорошего тона является проверка на null при левом соединении в запросе, проверка деления на ноль, запрет незаполненных значений в измерениях регистров. В общем все, что минимизирует ошибки программы и пользователя, а также оптимизирует алгоритм.
На экзамене в первую очередь нужно решить основные задачи - задачи оперативного, бухгалтерского учета и периодических расчетов. Без них экзамен не сдать. Далее решать задачи по бизнес-процессам и управляемым формам.
При создании отчетов тоже не стоит много времени тратить на внешний вид. Если что-то не получается, лучше оставить на потом (если останется время).
За ошибки на экзамене снижаются баллы согласно файлу ATT83PL.rtf. Но если ошибку сделал случайно и можешь все объяснить, то баллы могут и не снять.
Удачи!