В процессе работы с учетными системами 1С часто возникает задача по резервированию товаров на складе. Нередко начальные простые реализации сталкиваются с усложнениями бизнес-процессов, такими как возможность многократных отгрузок по одному счету. Давайте подробно рассмотрим типичную проблему, возникающую при такой схеме, и способы ее решения, начиная от 'малой крови' и заканчивая более фундаментальными архитектурными изменениями.
Исходная ситуация, с которой мы столкнулись, предполагала, что резервирование товара на складе осуществляется через регистр остатков, в который добавлен ресурс Резерв. Документ Счет увеличивает этот ресурс на количество товара в счете, а документ отгрузки, созданный на основании счета, уменьшает его. Изначально это работало, потому что на основании счета создавался только один документ отгрузки. Однако, когда появилась потребность создавать несколько различных документов отгрузки по одному счету, возникла проблема: второй и последующие документы стали излишне сторнировать резервы, нарушая корректность учета.
Давайте проанализируем текущую логику резервирования, которая привела к ошибке. В нашей схеме документ Счет увеличивает ресурс Резерв в регистре остатков. Автор поясняет, что при отгрузке (даже частичной) по этому счету весь зарезервированный объем товара сторнируется. То есть, если по счету зарезервировано 7 штук товара, а отгружено только 2, система полностью снимает резерв на 7 штук.
Если на основании счета создается несколько документов отгрузки, каждый из них выполняет полное сторнирование. В результате, после первой же отгрузки резерв обнуляется, а последующие отгрузки по тому же счету производят излишнее (ненужное) сторнирование, приводя к некорректным движениям и, возможно, отрицательному резерву, если не предусмотрены проверки. Это основная причина возникшей ошибки и некорректного поведения системы.
Наиболее простой и прямой способ исправить возникшую ошибку, не перестраивая всю систему учета резервов, заключается в изменении логики проведения документа отгрузки. Вместо того чтобы полностью сторнировать резерв на количество, указанное в исходном счете, документ отгрузки должен сторнировать резерв *только на фактически отгруженное количество*.
Рассмотрим по шагам, как это можно реализовать:
Счета, нам необходимо определить, сколько товара фактически отгружается по этому документу.Счетом. Если наш регистр резервов не детализирован до уровня счета, это может быть сложнее, но решаемо.Резерв должно формироваться в минус на количество, равное фактически отгруженному товару, но не более чем на имеющийся зарезервированный остаток по данному счету.Для этого нам потребуется немного изменить структуру регистра или логику запросов. Если регистр резервов не имеет измерения Счет, то для корректного частичного снятия резерва нам придется проверять, сколько резерва "висит" по конкретной связке товар-склад, а также сколько уже было отгружено по данному счету ранее. Однако, самое правильное и наименее трудозатратное "малой кровью" решение — это добавить измерение Счет (или ДокументОснование) в регистр остатков для ресурса Резерв.
После такого изменения, логика движений будет следующей:
Счет:
Движения.Остатки.Приход("Резерв", Товар, Склад, Количество, Счет);
Здесь Счет — это ссылка на сам документ "Счет", который инициирует резервирование.
РеализацияТоваров), созданный на основании Счета:
// Перед формированием движения, получаем текущий резерв по этому Счету, Товару и Складу
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| Остатки.Резерв КАК Резерв
|ИЗ
| РегистрНакопления.Остатки КАК Остатки
|ГДЕ
| Остатки.Товар = &Товар
| И Остатки.Склад = &Склад
| И Остатки.ДокументОснование = &Счет"; // Предполагаем, что измерение ДокументОснование добавлено
Запрос.УстановитьПараметр("Товар", ТекСтрокаТабличнойЧасти.Товар);
Запрос.УстановитьПараметр("Склад", ТекСтрокаТабличнойЧасти.Склад);
Запрос.УстановитьПараметр("Счет", ДокументОснование); // Документ-основание, то есть наш Счет
РезультатЗапроса = Запрос.Выполнить().Выбрать();
ТекущийРезервПоСчету = 0;
Если РезультатЗапроса.Следующий() Тогда
ТекущийРезервПоСчету = РезультатЗапроса.Резерв;
КонецЕсли;
// Движение формируем на меньшее из отгружаемого количества и текущего резерва
КоличествоКСнятиюРезерва = Мин(ТекСтрокаТабличнойЧасти.Количество, ТекущийРезервПоСчету);
Если КоличествоКСнятиюРезерва > 0 Тогда
Движения.Остатки.Расход("Резерв", Товар, Склад, КоличествоКсМяСнятиюРезерва, Счет);
КонецЕсли;
Такая логика обеспечит, что резерв будет сниматься ровно на отгруженное количество, и при последующих отгрузках по тому же счету будет сниматься оставшаяся часть резерва, пока он не достигнет нуля.
Кроме того, можно использовать флаг в документе отгрузки, например, ЗакрытьЗаказПолностью, который менеджер устанавливает, если клиент отказался от оставшейся части заказа, и тогда резерв снимается полностью (остаток + отгруженное количество). Это даст гибкость в работе.
Хотя вышеописанное решение исправляет непосредственный баг, в долгосрочной перспективе и для более гибкого и производительного учета в типовых конфигурациях 1С используется подход с раздельными регистрами для физических остатков и для резервов. Это позволяет лучше управлять аналитикой и оптимизировать производительность.
Рассмотрим преимущества и реализацию такого подхода:
ОстаткиТоваров): Этот регистр предназначен исключительно для учета физического наличия товара на складах. Его измерения – Товар, Склад. Ресурс – Количество. Движения по этому регистру формируют документы прихода и расхода товара (поступления, реализации, перемещения).РезервыТоваров или РезервыПоЗаказам): Этот регистр предназначен для учета зарезервированного товара. Его измерения – Товар, Склад, ДокументОснование (ссылка на Счет или ЗаказКлиента), Клиент. Ресурс – Количество (зарезервировано).Как работает с двумя регистрами:
Счет (или ЗаказКлиента): Формирует движения по регистру РезервыТоваров в приход.
Движения.РезервыТоваров.Приход(Товар, Склад, Количество, Счет, Клиент);
РеализацияТоваров): Формирует движения по регистру ОстаткиТоваров в расход (физический отгруженный товар) и по регистру РезервыТоваров в расход (снятие резерва).
// Списание физического остатка
Движения.ОстаткиТоваров.Расход(Товар, Склад, КоличествоОтгружено);
// Снятие резерва
// Здесь важно снять резерв, относящийся именно к этому Счету/Заказу
Движения.РезервыТоваров.Расход(Товар, Склад, КоличествоОтгружено, Счет, Клиент);
Важно, чтобы отгрузка не могла превысить количество в резерве, если мы реализуем жесткое резервирование. Для этого в процедуре проведения документа необходимо выполнять проверки на доступность резерва.
ОстаткиТоваров.Количество - РезервыТоваров.Количество. Поскольку оба регистра предварительно агрегируют данные, такой расчет выполняется очень быстро.Преимущества раздельных регистров:
Товару и Складу, но и по конкретному Клиенту, Счету (Заказу), сроку действия резерва и т.д.В ходе дискуссии были затронуты два основных подхода к резервированию:
Выбор между "мягким" и "жестким" резервом зависит от специфики бизнеса и уровня обслуживания клиентов. В типовых конфигурациях 1С часто предусмотрена настройка, разрешающая или запрещающая отгрузку "чужого" резерва, что позволяет гибко управлять этим аспектом.
Автор в своей текущей схеме использует автоматическое распроведение счета после истечения срока его действия или для "закрытия" резерва, когда товар фактически был отгружен. Этот подход является нетиповым и потенциально проблемным с точки зрения учета и целостности данных:
Счет или Реализация) фиксирует факт хозяйственной жизни (договоренность с клиентом, отгрузка товара). Распроведение документа означает отмену этого факта, что не всегда соответствует действительности.Вместо распроведения рекомендуется использовать следующие методы:
Счет статусы, например: "В работе", "Частично отгружен", "Закрыт", "Аннулирован", "Просрочен". Эти статусы могут устанавливаться вручную или автоматически по результатам проведения документов отгрузки или регламентными заданиями.ЗакрытиеРезерва или ОтменаЗаказа, который будет делать соответствующие движения по регистру резервов.Эти подходы позволяют сохранить историю всех взаимодействий и изменений, что является важным для анализа деятельности предприятия и контроля менеджеров.
Идея рассчитывать свободный остаток динамически, как "физический остаток минус количество товара в проведенных, но не списанных счетах", кажется привлекательной с точки зрения "чистоты" данных (не храним то, что можно посчитать). Однако, в 1С v7.7 (и даже в более поздних версиях), это может привести к серьезным проблемам с производительностью, особенно на больших базах данных.
Для такого расчета системе пришлось бы каждый раз выполнять запросы с агрегацией по множеству документов Счет, анализировать их табличные части и сравнивать с данными по отгрузкам. Это очень ресурсоемкая операция, неприемлемая для оперативных отчетов, форм выбора или расчетов, которые должны выполняться мгновенно.
Именно для ускорения таких расчетов в 1С существуют регистры накопления. Они предварительно агрегируют данные, позволяя получать актуальные остатки (как физические, так и зарезервированные) практически мгновенно. Таким образом, опасения автора по поводу производительности при динамическом расчете являются полностью оправданными.
Давайте подытожим наши выводы и рекомендации для вашей системы:
ДокументОснование (ссылка на Счет) в ваш регистр остатков для ресурса Резерв. Это наименее затратное решение для устранения текущей ошибки.ОстатковТоваров, другой для РезервовТоваров, детализированных по Счету/Клиенту. Это обеспечит высокую производительность, гибкость и чистоту учета.Внедрение этих рекомендаций поможет не только исправить текущую ошибку, но и заложить фундамент для более надежной, масштабируемой и производительной системы учета резервов, соответствующей лучшим практикам 1С.