Задача партионного учета и ее решение в 1С
Разработка - Механизмы платформы 1С
Задача партионного учета и ее решение в 1С
Задача партионного учета состоит в том, чтобы по каждому списанию было видно, из какого поступления ушел товар и какая себестоимость при этом списана. В рассматриваемом примере партия хранится как ссылка на документ «Приходная накладная». Дата партии совпадает с датой этого документа. При расходе партии обходятся от более раннего поступления к более позднему.
На этой задаче удобно собрать несколько механизмов платформы, которые потом встречаются по отдельности: движения регистра накопления, чтение остатков на момент документа, итоги запроса и отказ в проведении.
Состав конфигурации
Учет ведется в регистре накопления «ОстаткиНаСкладах». Вид регистра - остатки.
|
Роль |
Имя |
Назначение |
|
Измерения |
Номенклатура, Склад, Партия |
Партия имеет тип ДокументСсылка.ПриходнаяНакладная |
|
Ресурсы |
Количество, Себестоимость |
Себестоимость относится к партии, а не к сумме продажи |
Склад задан реквизитом шапки приходной и расходной накладных. В табличной части своего склада нет. Номенклатура делится на товары и услуги перечислением «ВидыНоменклатуры». Услуги в регистр не записываются.
Имена в коде дальше совпадают с именами в дереве конфигурации: табличная часть «Товары», регистр «ОстаткиНаСкладах», перечисление «ВидыНоменклатуры».
Поступление
Поступление оформляется документом «Приходная накладная». В строке указываются номенклатура, количество и сумма. Сумма строки в этом примере принимается за себестоимость партии. Отдельного номера партии в документе нет: в измерение «Партия» пишется ссылка на сам документ поступления.
Набор движений заполняется в обработке проведения. Запись набора выполняет платформа по окончании проведения, для этого у регистра устанавливается признак записи.
Процедура ОбработкаПроведения(Отказ, Режим)
Движения.ОстаткиНаСкладах.Записывать = Истина;
Для Каждого ТекСтрокаТовары Из Товары Цикл
Если ТекСтрокаТовары.Номенклатура.ВидНоменклатуры
<> Перечисления.ВидыНоменклатуры.Товар Тогда
Продолжить;
КонецЕсли;
Движение = Движения.ОстаткиНаСкладах.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Приход;
Движение.Период = Дата;
Движение.Номенклатура = ТекСтрокаТовары.Номенклатура;
Движение.Склад = Склад;
Движение.Партия = Ссылка;
Движение.Количество = ТекСтрокаТовары.Количество;
Движение.Себестоимость = ТекСтрокаТовары.Сумма;
КонецЦикла;
КонецПроцедуры
Строка с услугой пропускается. Остатка по ней не возникает, и при последующем списании она не участвует в проверке количества.
Списание
Списание оформляется документом «Расходная накладная». При проведении нужно получить товарные строки документа, прочитать остатки партий на момент этого документа, проверить достаточность остатка и записать расход по партиям в порядке даты поступления.
Запрос
Запрос собирает три выборки.
ВТ_Продать - строки документа, свернутые по номенклатуре и складу. Услуги отсекаются отбором. Количество здесь - количество к списанию. Сумма продажи в запрос не берется: в ресурс «Себестоимость» попадет стоимость партии, а не сумма расходной накладной.
ВТ_Остатки - остатки регистра на момент проводимого документа. Условие виртуальной таблицы ограничивает выборку парами «номенклатура и склад» из ВТ_Продать. Остаток той же номенклатуры на другом складе в расчет не попадает.
Итоговая выборка соединяет продажу с остатками по номенклатуре и по складу. Если соединить только по номенклатуре, партии двух складов попадут в одно списание. Левое соединение оставляет строку продажи и тогда, когда остатка нет: количество остатка в такой строке равно нулю, и нехватка видна до записи движений.
Сортировка идет по дате партии, затем по ссылке партии. Дата берется из приходной накладной. Сортировка по ссылке документа эту дату не гарантирует: порядок ссылок и порядок поступлений расходятся.
ВЫБРАТЬ
ПродажаТовары.Номенклатура КАК Номенклатура,
СУММА(ПродажаТовары.Количество) КАК Количество,
ПродажаТовары.Ссылка.Склад КАК Склад
ПОМЕСТИТЬ ВТ_Продать
ИЗ
Документ.РасходнаяНакладная.Товары КАК ПродажаТовары
ГДЕ
ПродажаТовары.Ссылка = &НашДокумент
И ПродажаТовары.Номенклатура.ВидНоменклатуры =
ЗНАЧЕНИЕ(Перечисление.ВидыНоменклатуры.Товар)
СГРУППИРОВАТЬ ПО
ПродажаТовары.Номенклатура,
ПродажаТовары.Ссылка.Склад
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Остатки.Номенклатура КАК Номенклатура,
Остатки.Склад КАК Склад,
Остатки.Партия КАК Партия,
Остатки.КоличествоОстаток КАК КоличествоОстаток,
Остатки.СебестоимостьОстаток КАК СебестоимостьОстаток
ПОМЕСТИТЬ ВТ_Остатки
ИЗ
РегистрНакопления.ОстаткиНаСкладах.Остатки(
&ДатаДокумента,
(Номенклатура, Склад) В
(ВЫБРАТЬ
ВТ_Продать.Номенклатура,
ВТ_Продать.Склад
ИЗ
ВТ_Продать КАК ВТ_Продать)) КАК Остатки
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_Продать.Номенклатура КАК Номенклатура,
ВТ_Продать.Склад КАК Склад,
ВТ_Продать.Количество КАК Количество,
ВТ_Остатки.Партия КАК Партия,
ЕСТЬNULL(ВТ_Остатки.КоличествоОстаток, 0) КАК КоличествоОстаток,
ЕСТЬNULL(ВТ_Остатки.СебестоимостьОстаток, 0) КАК СебестоимостьОстаток
ИЗ
ВТ_Продать КАК ВТ_Продать
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Остатки КАК ВТ_Остатки
ПО ВТ_Продать.Номенклатура = ВТ_Остатки.Номенклатура
И ВТ_Продать.Склад = ВТ_Остатки.Склад
УПОРЯДОЧИТЬ ПО
ВТ_Остатки.Партия.Дата,
ВТ_Остатки.Партия
ИТОГИ
МАКСИМУМ(Количество),
СУММА(КоличествоОстаток),
СУММА(СебестоимостьОстаток)
ПО
Номенклатура,
Склад
Параметр ДатаДокумента получает МоментВремени() проводимого документа. Параметр НашДокумент получает ссылку на него.
Итоги запроса
После соединения количество к продаже повторяется в каждой строке партии. Сумма по этому полю умножила бы потребность на число партий. В итоге поэтому стоит МАКСИМУМ(Количество): значение во всех строках одно, максимум его не меняет.
КоличествоОстаток и СебестоимостьОстаток суммируются. В строке группировки видно, сколько товара есть на складе по всем партиям. Партия в группировке пустая: итог рассчитан по номенклатуре и складу.
На числах это выглядит так. К продаже 10 штук, на складе три поступления.
|
Строка |
Партия |
Дата |
К продаже |
Остаток |
Себестоимость остатка |
|
Партия |
Поступление 05.12.2021 |
05.12.2021 |
10 |
4 |
4 000 |
|
Партия |
Поступление 07.02.2022 |
07.02.2022 |
10 |
3 |
3 300 |
|
Партия |
Поступление 13.02.2022 |
13.02.2022 |
10 |
8 |
9 600 |
|
Итог |
|
|
10 |
15 |
16 900 |
Десять в колонке «К продаже» - одна и та же потребность документа. Пятнадцать в итоге - сумма остатков 4, 3 и 8. Десять не больше пятнадцати, товара хватает. Порядок строк совпадает с датами прихода: сначала декабрь 2021 года, затем 7 февраля 2022 года, затем 13 февраля.
Запись движений
Обход результата идет по группировкам. Сначала номенклатура, затем склад, затем строки партий. Сравнение потребности и остатка выполняется на группировке склада, до обхода партий.
Если потребность больше остатка, процедура сообщает номенклатуру, склад, нужное количество и остаток, устанавливает Отказ и завершается. Документ либо проводится целиком, либо не проводится. Движения при отказе платформа не записывает.
Пока потребность не закрыта, партии берутся в порядке выборки. Строка с нулевым остатком пропускается, делить на нее себестоимость не потребуется. Если в партии товара меньше, чем еще нужно списать, списывается вся партия и вся ее себестоимость. Если партия больше потребности, списывается оставшаяся потребность, а себестоимость - доля стоимости партии.
В движение попадают номенклатура, склад и партия из строки выборки.
Процедура ОбработкаПроведения(Отказ, Режим)
Движения.ОстаткиНаСкладах.Записывать = Истина;
Запрос = Новый Запрос;
Запрос.Текст = ТекстЗапросаСписания();
Запрос.УстановитьПараметр("ДатаДокумента", МоментВремени());
Запрос.УстановитьПараметр("НашДокумент", Ссылка);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаНоменклатура = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаНоменклатура.Следующий() Цикл
ВыборкаСклад = ВыборкаНоменклатура.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаСклад.Следующий() Цикл
Если ВыборкаСклад.Количество > ВыборкаСклад.КоличествоОстаток Тогда
Сообщить("Не хватает товара " + ВыборкаСклад.Номенклатура
+ " на складе " + ВыборкаСклад.Склад
+ ". Нужно " + ВыборкаСклад.Количество
+ ", в остатке " + ВыборкаСклад.КоличествоОстаток);
Отказ = Истина;
Возврат;
КонецЕсли;
ОсталосьПродать = ВыборкаСклад.Количество;
ВыборкаЗаписей = ВыборкаСклад.Выбрать();
Пока ВыборкаЗаписей.Следующий() И ОсталосьПродать > 0 Цикл
Если ВыборкаЗаписей.КоличествоОстаток <= 0 Тогда
Продолжить;
КонецЕсли;
Если ОсталосьПродать >= ВыборкаЗаписей.КоличествоОстаток Тогда
КоличествоКСписанию = ВыборкаЗаписей.КоличествоОстаток;
СуммаКСписанию = ВыборкаЗаписей.СебестоимостьОстаток;
ОсталосьПродать = ОсталосьПродать - КоличествоКСписанию;
Иначе
КоличествоКСписанию = ОсталосьПродать;
СебестоимостьЕдиницы = ВыборкаЗаписей.СебестоимостьОстаток
/ ВыборкаЗаписей.КоличествоОстаток;
СуммаКСписанию = СебестоимостьЕдиницы * КоличествоКСписанию;
ОсталосьПродать = 0;
КонецЕсли;
Движение = Движения.ОстаткиНаСкладах.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
Движение.Период = Дата;
Движение.Номенклатура = ВыборкаЗаписей.Номенклатура;
Движение.Склад = ВыборкаЗаписей.Склад;
Движение.Партия = ВыборкаЗаписей.Партия;
Движение.Количество = КоличествоКСписанию;
Движение.Себестоимость = СуммаКСписанию;
КонецЦикла;
КонецЦикла;
КонецЦикла;
КонецПроцедуры
Текст запроса вынесен в функцию ТекстЗапросаСписания() того же модуля.
По таблице выше получаются три строки расхода.
|
Партия |
Количество |
Себестоимость |
Откуда сумма |
|
Поступление 05.12.2021 |
4 |
4 000 |
Списана вся партия |
|
Поступление 07.02.2022 |
3 |
3 300 |
Списана вся партия |
|
Поступление 13.02.2022 |
3 |
3 600 |
9 600 / 8 x 3 |
|
Итого |
10 |
10 900 |
|
Потребность закрыта. В поступлении от 13.02.2022 остается 5 штук и 6 000 себестоимости. Если к продаже указать 16 при остатке 15, процедура завершится с отказом и не запишет ни одной из этих строк.
Повторное проведение
Остатки читаются на МоментВремени() документа. Движения этого документа в такой остаток не входят. Новый набор формируется в процедуре. Записывает его платформа в конце проведения, поскольку у регистра установлен признак записи. Прежние записи регистратора заменяются новым набором.
Отдельно очищать регистр вызовом Записать() в начале процедуры не нужно: граница чтения уже задана моментом документа. При отказе проведение отменяется вместе с набором, собранным в памяти. Прежние движения документа в базе сохраняются.
Если переносить пример дальше
В примере блокировка данных не устанавливается. В рабочей базе перед чтением остатков ставится исключительная блокировка по номенклатуре и складу. Без нее два расходных документа могут списать одну и ту же партию.
При полном списании партии в движение уходит весь остаток ее себестоимости, и сумма партии закрывается. При частичном списании доля «себестоимость остатка / количество остатка x количество к списанию» может оставить на партии копейку. Ее относят на последнее списание этой партии.
Смысл измерения «Партия» в задаче можно заменить. На то же место встают срок годности или проект оплаты. Приход записывает новое значение, расход читает остатки на момент документа и забирает количество в том порядке, который для этого значения уже задан. Для срока годности это дата годности, для проекта - порядок, принятый в учете оплат. Сама схема проведения остается прежней: остаток на момент документа, проверка достаточности, запись расхода и замена прежних движений регистратора платформой.
Вступайте в нашу телеграмм-группу Инфостарт
Вы можете заказать платную адаптацию этой статьи под ваши задачи на «Бирже заказов».
- 0% комиссии — оплата напрямую исполнителю;
- Исполнители любого масштаба — от отдельных специалистов до команд под проект;
- Прямой обмен контактами между заказчиком и исполнителем;
- Безопасная сделка — при необходимости;
- Рейтинги, кейсы и прозрачная система откликов.