При автоматизации учета в сфере услуг (например, в прачечных, химчистках или автомойках) часто возникает задача пересчета стоимости сырья. Моющие средства и расходные материалы обычно закупаются крупным оптом — в канистрах по 10, 20 или более литров. Однако при оказании услуг нормы расхода рассчитываются в миллилитрах или граммах на единицу работы (например, на 1 килограмм белья). Попытка рассчитывать и хранить цену за один литр или миллилитр непосредственно в карточке товара вручную приводит к огромным трудозатратам при каждом изменении закупочных цен.
В этой статье мы подробно разберем, как правильно спроектировать архитектуру базы данных в 1С:Предприятии 7.7, как организовать автоматический пересчет единиц измерения с помощью коэффициентов и как реализовать проведение документов списания с расчетом себестоимости «на лету».
Начинающие разработчики часто пытаются решить задачу «в лоб»: создать в справочнике номенклатуры реквизит ЦенаЗаЛитр и при изменении цены канистры программно делить её на объем и записывать обратно в справочник. Рассмотрим, почему этот подход ошибочен и опасен:
Правильный подход заключается в том, чтобы хранить только базовую цену поступления упаковки и коэффициенты пересчета, а конечную стоимость списываемого объема рассчитывать в момент проведения документов списания.
Для гибкого пересчета объемов (канистра → литр → миллилитр) мы рекомендуем использовать классический механизм подчиненных справочников. Проанализируем эту структуру подробнее.
Создадим справочник «Номенклатура» (владелец) и подчиненный ему справочник «Единицы». В подчиненном справочнике нам понадобятся следующие реквизиты:
Коэффициент (тип: Число, точность 4 знака). Показывает отношение данной единицы к базовой.ВесОбъем (тип: Число). Объем упаковки.За базовую единицу измерения номенклатуры (с коэффициентом 1) логично принять минимальную неделимую единицу расхода — миллилитр (мл). В этом случае коэффициенты для других упаковок будут выглядеть следующим образом:
При такой архитектуре, зная цену закупки одной канистры (например, 20 литров за 5800 рублей), мы всегда можем легко получить цену одного миллилитра программным путем:
ЦенаЗаМл = ЦенаКанистры / КоэффициентКанистры (20 000) = 0.29 руб.
Чтобы автоматизировать расчет расхода средств в зависимости от программы стирки, нам понадобится справочник норм расходов. Опишем спецификацию:
МоющееСредство (тип: Справочник.Номенклатура).НормаРасхода (тип: Число, точность 3 знака). Норма расхода в миллилитрах на 1 килограмм сухого белья.Это позволит нам гибко настраивать нормы расхода. Например, для программы «Хлопок 60°» мы можем указать: Стиральный порошок — 15 мл/кг, Кондиционер — 5 мл/кг. Для программы «Шерсть»: Специальный гель — 25 мл/кг.
Теперь рассмотрим, как связать все эти данные воедино при проведении документа списания (например, «Акт оказанных услуг» или «Акт стирки»). Документ содержит реквизиты: ПрограммаСтирки (ссылка на справочник) и ВесБелья (в килограммах).
Разберем по шагам алгоритм списания материалов в модуле документа в процедуре ОбработкаПроведения(). Нам нужно получить нормы расхода по программе, рассчитать объем списания в миллилитрах, перевести его в единицы учета на складе и рассчитать сумму списания по средней стоимости.
Посмотрим на пример программной реализации этого процесса на встроенном языке 1С 7.7:
Процедура ОбработкаПроведения()
// Создаем объекты для работы со справочниками и регистрами
СпрСпецификации = СоздатьОбъект("Справочник.Спецификации");
РегОстатки = СоздатьОбъект("Регистр.ОстаткиМатериалов");
// Устанавливаем фильтр спецификаций по выбранной в документе программе стирки
СпрСпецификации.ИспользоватьВладельца(ПрограммаСтирки);
СпрСпецификации.ВыбратьЭлементы();
Пока СпрСпецификации.ПолучитьЭлемент() = 1 Цикл
ТекущееСредство = СпрСпецификации.МоющееСредство;
Норма = СпрСпецификации.НормаРасхода;
// Вычисляем общий расход средства в миллилитрах для текущей стирки
ОбщийРасходМл = Норма * ВесБелья;
// Переводим расход в базовые единицы учета на складе (например, в литры)
// Предположим, что учет на складе ведется в литрах. Коэффициент литра = 1000
РасходВЕдиницахУчета = ОбщийРасходМл / 1000;
// Получаем текущую среднюю себестоимость единицы на складе из регистров
// Для этого используем временный расчет регистра на момент времени документа
РегОстатки.УстановитьФильтр(ТекущееСредство);
Если РегОстатки.ИтогиАктуальны() = 0 Тогда
РегОстатки.ВременныйРасчет(1);
РассчитатьРегистрыНа(ТекущийДокумент());
КонецЕсли;
БалансКоличество = РегОстатки.СводныйОстаток(ТекущееСредство, "Количество");
БалансСумма = РегОстатки.СводныйОстаток(ТекущееСредство, "Сумма");
// Рассчитываем среднюю цену за единицу учета на складе (за 1 литр)
Если БалансКоличество > 0 Тогда
СредняяЦенаЗаЛитр = БалансСумма / БалансКоличество;
СуммаСписания = РасходВЕдиницахУчета * СредняяЦенаЗаЛитр;
Иначе
// Если остатка нет, выводим предупреждение и берем цену из прайса
Предупреждение("Нет остатка по номенклатуре: " + ТекущееСредство.Наименование);
Продолжить;
EndIf;
// Выполняем движение расхода по регистру остатков материалов
РегОстатки.Номенклатура = ТекущееСредство;
РегОстатки.Количество = РасходВЕдиницахУчета;
РегОстатки.Сумма = СуммаСписания;
РегОстатки.ДвижениеРасход();
Сообщить("Списано средство: " + ТекущееСредство.Наименование +
" в количестве: " + Строка(РасходВЕдиницахУчета) + " л. на сумму: " + Строка(СуммаСписания) + " руб.");
КонецЦикла;
КонецПроцедуры
Если в вашей конфигурации нет регистров учета (база написана «с нуля» только на справочниках) и вам всё же требуется рассчитывать и сохранять значение цены одного литра в карточке номенклатуры при обновлении цены канистры, мы должны использовать специальный механизм для работы с периодическими реквизитами — объект Периодический.
Рассмотрим ситуацию: пользователь вносит новую цену канистры объемом 10 литров вручную в справочник ЦеныКанистр. Программа должна автоматически рассчитать цену 1 литра и записать её в периодический реквизит ЦенаЛитр справочника Номенклатура на рабочую дату документа или системы.
Посмотрим на пример кода, реализующего данную задачу:
// Функция производит расчет и запись цены за литр в периодический реквизит номенклатуры
Процедура РассчитатьИЗаписатьЦенуЛитра(ВыбранныйТовар, ЦенаКанистры, ОбъемКанистры)
// Проверяем корректность переданных параметров
Если ОбъемКанистры = 0 Тогда
Предупреждение("Не указан объем канистры для расчета!");
Возврат;
КонецЕсли;
// Рассчитываем цену за 1 литр
РассчитаннаяЦенаЗаЛитр = ЦенаКанистры / ОбъемКанистры;
// Для записи периодического реквизита используем объект "Периодический"
// Это позволит избежать ошибок блокировки платформы
ОбъектПериод = СоздатьОбъект("Периодический");
// Связываем объект с нужным периодическим реквизитом элемента справочника Номенклатура
ОбъектПериод.ИспользоватьОбъект("ЦенаЛитр", ВыбранныйТовар);
// Устанавливаем дату, на которую будет записано новое значение цены
ОбъектПериод.ДатаЗнач = РабочаяДата();
// Присваиваем рассчитанное значение и записываем изменения в базу данных
ОбъектПериод.Значение = РассчитаннаяЦенаЗаЛитр;
ОбъектПериод.Записать();
Сообщить("Для товара " + ВыбранныйТовар.Наименование +
" успешно установлена цена за 1 литр: " + Строка(РассчитаннаяЦенаЗаЛитр) + " руб.");
КонецПроцедуры
Мы проанализировали два подхода к расчету стоимости единицы объема в 1С v7.7. Первый подход — архитектурный (через спецификации и динамический расчет в документах) — является наиболее профессиональным и отказоустойчивым. Он исключает дублирование данных и гарантирует точный расчет себестоимости в любой момент времени независимо от колебания рыночных цен. Второй подход — программный записью в справочник — применим в простых базах данных, где нет документов-регистраторов цен, но требует строгого использования объекта Периодический во избежание системных ошибок платформы.