diff --git a/CHANGELOG.md b/CHANGELOG.md
index ad42ad0..652f880 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,14 @@
---
+## 1.3.1.110 (2026-09-02)
+
+### РК_ЗагрузкаОстатков — серии: ГоденДо и заглушки по номенклатуре (#30, #31)
+
+- `series_expire` из API → реквизит ТЧ `СерияГоденДо`; при создании серии заполняются `ГоденДо`, `ДатаПроизводства`, `ВидНоменклатуры`.
+- Fallback срока из имени серии (`до DD.MM.YY`); при отсутствии данных — дата среза cutover.
+- Заглушки: ключ `ВидСер + Номенклатура + ГоденДо`, номер `Перенос_{код номенклатуры}_{yyyyMMdd}` (не одна серия на склад).
+
## 1.3.1.109 (2026-09-01)
### РК_ЗагрузкаОстатков — маппинг соглашений комиссии cutover (#24)
diff --git a/VERSION b/VERSION
index 9f0b865..823727f 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.3.1.109
+1.3.1.110
diff --git a/src/ConfigDumpInfo.xml b/src/ConfigDumpInfo.xml
index 4041d0b..158f846 100644
--- a/src/ConfigDumpInfo.xml
+++ b/src/ConfigDumpInfo.xml
@@ -57,7 +57,7 @@
-
+
@@ -130,6 +130,8 @@
+
+
@@ -201,7 +203,7 @@
-
+
diff --git a/src/Configuration.xml b/src/Configuration.xml
index 0e532cc..afa84f5 100644
--- a/src/Configuration.xml
+++ b/src/Configuration.xml
@@ -62,7 +62,7 @@
Role.РК_ОсновнаяРоль
- 1.3.1.109
+ 1.3.1.110
Language.Русский
diff --git a/src/DataProcessors/РК_ЗагрузкаОстатков.xml b/src/DataProcessors/РК_ЗагрузкаОстатков.xml
index b2efc60..c060eab 100644
--- a/src/DataProcessors/РК_ЗагрузкаОстатков.xml
+++ b/src/DataProcessors/РК_ЗагрузкаОстатков.xml
@@ -1301,6 +1301,45 @@
Auto
+
+
+ СерияГоденДо
+
+
+ ru
+ Срок годности серии
+
+
+
+
+ xs:dateTime
+
+ DateTime
+
+
+ false
+
+
+
+ false
+
+ false
+ false
+
+
+ false
+
+ DontCheck
+ Items
+
+
+ Auto
+ Auto
+
+
+ Auto
+
+
СерияСсылка
@@ -2788,6 +2827,45 @@
Auto
+
+
+ СерияГоденДо
+
+
+ ru
+ Срок годности серии
+
+
+
+
+ xs:dateTime
+
+ DateTime
+
+
+ false
+
+
+
+ false
+
+ false
+ false
+
+
+ false
+
+ DontCheck
+ Items
+
+
+ Auto
+ Auto
+
+
+ Auto
+
+
СерияСсылка
diff --git a/src/DataProcessors/РК_ЗагрузкаОстатков/Ext/ObjectModule.bsl b/src/DataProcessors/РК_ЗагрузкаОстатков/Ext/ObjectModule.bsl
index e14104e..2456af7 100644
--- a/src/DataProcessors/РК_ЗагрузкаОстатков/Ext/ObjectModule.bsl
+++ b/src/DataProcessors/РК_ЗагрузкаОстатков/Ext/ObjectModule.bsl
@@ -98,7 +98,7 @@
КонецПроцедуры
// Сопоставление строк ТЧ с НСИ целевой ИБ.
-// Серии создаются только если не найдены; заглушки Перенос_* — только при СоздаватьЗаглушкиСерий.
+// Серии создаются только если не найдены; заглушки Перенос_{номенклатура}_{срок} — только при СоздаватьЗаглушкиСерий.
//
// Параметры:
// РежимТест - Булево - Истина: только поиск, без создания серий/характеристик
@@ -835,6 +835,7 @@
НовСтр.СерияИдентификатор = UUIDИзСтроки(ЗначениеСвойства(СтрокаAPI, "series_guid", ""));
НовСтр.Серия = СокрЛП(Строка(ЗначениеСвойства(СтрокаAPI, "series_name", "")));
НовСтр.СерияНомер = СокрЛП(Строка(ЗначениеСвойства(СтрокаAPI, "series_number", "")));
+ НовСтр.СерияГоденДо = ДатаИзSeriesExpire(ЗначениеСвойства(СтрокаAPI, "series_expire", ""));
Если ПустаяСтрока(НовСтр.Серия) И Не ПустаяСтрока(НовСтр.СерияНомер) Тогда
НовСтр.Серия = НовСтр.СерияНомер;
КонецЕсли;
@@ -1169,6 +1170,173 @@
КонецПроцедуры
+Функция ДатаИзSeriesExpire(СтрокаДаты)
+
+ Текст = СокрЛП(Строка(СтрокаДаты));
+ Если ПустаяСтрока(Текст) Тогда
+ Возврат '00010101';
+ КонецЕсли;
+
+ Попытка
+ ПозT = СтрНайти(Текст, "T");
+ Если ПозT > 0 Тогда
+ Текст = Лев(Текст, ПозT - 1);
+ КонецЕсли;
+ ПозПроб = СтрНайти(Текст, " ");
+ Если ПозПроб > 0 Тогда
+ Текст = Лев(Текст, ПозПроб - 1);
+ КонецЕсли;
+ Части = СтрРазделить(Текст, "-", Ложь);
+ Если Части.Количество() >= 3 Тогда
+ Возврат Дата(Число(Части[0]), Число(Части[1]), Число(Части[2]));
+ КонецЕсли;
+ Исключение
+ КонецПопытки;
+
+ Возврат '00010101';
+
+КонецФункции
+
+Функция ДатаИзИмениСерии(ИмяСерии)
+
+ Текст = СокрЛП(Строка(ИмяСерии));
+ Если ПустаяСтрока(Текст) Тогда
+ Возврат '00010101';
+ КонецЕсли;
+
+ Позиция = СтрНайти(НРег(Текст), " до ");
+ Если Позиция = 0 Тогда
+ Возврат '00010101';
+ КонецЕсли;
+
+ Хвост = СокрЛП(Сред(Текст, Позиция + 4));
+ Части = СтрРазделить(Хвост, ".", Ложь);
+ Если Части.Количество() < 3 Тогда
+ Возврат '00010101';
+ КонецЕсли;
+
+ Попытка
+ День = Число(Части[0]);
+ Месяц = Число(Части[1]);
+ Год = Число(Части[2]);
+ Если Год < 100 Тогда
+ Если Год >= 50 Тогда
+ Год = 1900 + Год;
+ Иначе
+ Год = 2000 + Год;
+ КонецЕсли;
+ КонецЕсли;
+ Возврат Дата(Год, Месяц, День);
+ Исключение
+ Возврат '00010101';
+ КонецПопытки;
+
+КонецФункции
+
+Функция ДатаСрезаОстатковОбработки()
+
+ Если ЗначениеЗаполнено(ДатаДокументовВвода) Тогда
+ Возврат ДатаДокументовВвода;
+ КонецЕсли;
+ Если ЗначениеЗаполнено(ДатаОстатков) Тогда
+ Возврат ДатаОстатков;
+ КонецЕсли;
+ Возврат ТекущаяДатаСеанса();
+
+КонецФункции
+
+Функция ОпределитьГоденДоСерииСтроки(СтрТ)
+
+ Если ЗначениеЗаполнено(СтрТ.СерияГоденДо) Тогда
+ Возврат СтрТ.СерияГоденДо;
+ КонецЕсли;
+
+ ДатаИзИмени = ДатаИзИмениСерии(СокрЛП(СтрТ.Серия));
+ Если ЗначениеЗаполнено(ДатаИзИмени) Тогда
+ Возврат ДатаИзИмени;
+ КонецЕсли;
+
+ Возврат '00010101';
+
+КонецФункции
+
+Функция КодНоменклатурыДляЗаглушкиСерии(СтрТ, Ном)
+
+ Арт = СокрЛП(СтрТ.Артикул);
+ Если ЗначениеЗаполнено(Арт) Тогда
+ Возврат Лев(СтрЗаменить(СтрЗаменить(Арт, " ", "_"), "/", "_"), 20);
+ КонецЕсли;
+
+ Код = СокрЛП(СтрТ.НоменклатураКод);
+ Если ЗначениеЗаполнено(Код) Тогда
+ Возврат Лев(СтрЗаменить(Код, " ", "_"), 20);
+ КонецЕсли;
+
+ Если ЗначениеЗаполнено(Ном) Тогда
+ Попытка
+ КодНом = СокрЛП(Строка(ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ном, "Код")));
+ Если ЗначениеЗаполнено(КодНом) Тогда
+ Возврат Лев(СтрЗаменить(КодНом, " ", "_"), 20);
+ КонецЕсли;
+ Исключение
+ КонецПопытки;
+ Возврат Лев(СтрЗаменить(Строка(Ном.УникальныйИдентификатор()), "-", ""), 8);
+ КонецЕсли;
+
+ Возврат Лев(СтрЗаменить(Строка(СтрТ.НоменклатураИдентификатор), "-", ""), 8);
+
+КонецФункции
+
+Функция СформироватьНомерЗаглушкиСерии(СтрТ, Ном, ГоденДо)
+
+ КодНом = КодНоменклатурыДляЗаглушкиСерии(СтрТ, Ном);
+ СуффиксСрока = Формат(ГоденДо, "ДФ=yyyyMMdd");
+ Возврат "Перенос_" + КодНом + "_" + СуффиксСрока;
+
+КонецФункции
+
+Функция СформироватьИмяЗаглушкиСерии(СтрТ, Ном, ГоденДо)
+
+ Номер = СформироватьНомерЗаглушкиСерии(СтрТ, Ном, ГоденДо);
+ Возврат Номер + " до " + Формат(ГоденДо, "ДФ=dd.MM.yyyy");
+
+КонецФункции
+
+Функция КлючКэшаСерииЗаглушки(ВидСер, Ном, ГоденДо)
+
+ Возврат Строка(ВидСер.УникальныйИдентификатор()) + "|"
+ + Строка(Ном.УникальныйИдентификатор()) + "|"
+ + Формат(ГоденДо, "ДФ=yyyyMMdd");
+
+КонецФункции
+
+Функция ОпределитьДатуПроизводстваСерии(ГоденДо, ЭтоЗаглушка)
+
+ // Политика cutover: дата производства в API не передаётся — дата среза остатков.
+ Возврат НачалоДня(ДатаСрезаОстатковОбработки());
+
+КонецФункции
+
+Процедура ЗаполнитьОбязательныеРеквизитыСерии(ОбСер, ГоденДо, ВидНом, ЭтоЗаглушка)
+
+ Если ЗначениеЗаполнено(ГоденДо) Тогда
+ ОбСер.ГоденДо = КонецДня(ГоденДо);
+ КонецЕсли;
+
+ Попытка
+ ОбСер.ДатаПроизводства = ОпределитьДатуПроизводстваСерии(ГоденДо, ЭтоЗаглушка);
+ Исключение
+ КонецПопытки;
+
+ Попытка
+ Если ЗначениеЗаполнено(ВидНом) Тогда
+ ОбСер.ВидНоменклатуры = ВидНом;
+ КонецЕсли;
+ Исключение
+ КонецПопытки;
+
+КонецПроцедуры
+
Функция НайтиИлиСоздатьСерию(СтрТ, Ном, СкладЦель, СуффиксДатыСер, РежимТест,
КэшТребуетсяСерия, КэшСерий, Результат)
@@ -1176,39 +1344,6 @@
ВидНом = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ном, "ВидНоменклатуры");
ТребуетсяСерия = НоменклатураИспользуетСерии(Ном, КэшТребуетсяСерия);
- ИмяСерии = СокрЛП(СтрТ.Серия);
- НомерСерии = СокрЛП(СтрТ.СерияНомер);
- ЭтоЗаглушка = Ложь;
-
- Если Не ЗначениеЗаполнено(ИмяСерии) И Не ЗначениеЗаполнено(НомерСерии) Тогда
- Если Не ТребуетсяСерия Тогда
- Возврат Сер;
- КонецЕсли;
- Если Не СоздаватьЗаглушкиСерий Тогда
- СтрТ.КомментарийСопоставления = "Серия обязательна, в источнике пустая (заглушки выключены)";
- Возврат Сер;
- КонецЕсли;
- КодСклЗагл = "КОМ";
- Если ЗначениеЗаполнено(СкладЦель) Тогда
- Попытка
- КодСклЗагл = СокрЛП(Строка(ОбщегоНазначения.ЗначениеРеквизитаОбъекта(СкладЦель, "Код")));
- Исключение
- КодСклЗагл = "";
- КонецПопытки;
- Если ПустаяСтрока(КодСклЗагл) Тогда
- КодСклЗагл = Лев(СтрЗаменить(Строка(СкладЦель.УникальныйИдентификатор()), "-", ""), 8);
- КонецЕсли;
- КонецЕсли;
- ИмяСерии = "Перенос_" + КодСклЗагл + "_" + СуффиксДатыСер;
- СтрТ.Серия = ИмяСерии;
- ЭтоЗаглушка = Истина;
- КонецЕсли;
-
- СсылкаПоГуид = СсылкаПоUUID(Справочники.СерииНоменклатуры, СтрТ.СерияИдентификатор);
- Если ЗначениеЗаполнено(СсылкаПоГуид) И ОбщегоНазначения.СсылкаСуществует(СсылкаПоГуид) Тогда
- Возврат СсылкаПоГуид;
- КонецЕсли;
-
ВидСер = Неопределено;
ИспНомерСерии = Истина;
Попытка
@@ -1222,43 +1357,102 @@
ВидСер = ВидНом;
КонецЕсли;
- КлючПоискаНомера = ?(ЗначениеЗаполнено(НомерСерии), НомерСерии, ИмяСерии);
- КлючКэшСер = Строка(ВидСер.УникальныйИдентификатор()) + "|" + ИмяСерии + "|" + КлючПоискаНомера;
+ ИмяСерии = СокрЛП(СтрТ.Серия);
+ НомерСерии = СокрЛП(СтрТ.СерияНомер);
+ ЭтоЗаглушка = Ложь;
+ ГоденДо = ОпределитьГоденДоСерииСтроки(СтрТ);
+
+ Если Не ЗначениеЗаполнено(ИмяСерии) И Не ЗначениеЗаполнено(НомерСерии) Тогда
+ Если Не ТребуетсяСерия Тогда
+ Возврат Сер;
+ КонецЕсли;
+ Если Не СоздаватьЗаглушкиСерий Тогда
+ СтрТ.КомментарийСопоставления = "Серия обязательна, в источнике пустая (заглушки выключены)";
+ Возврат Сер;
+ КонецЕсли;
+ Если Не ЗначениеЗаполнено(ГоденДо) Тогда
+ ГоденДо = КонецДня(ДатаСрезаОстатковОбработки());
+ КонецЕсли;
+ СтрТ.СерияГоденДо = ГоденДо;
+ НомерСерии = СформироватьНомерЗаглушкиСерии(СтрТ, Ном, ГоденДо);
+ ИмяСерии = СформироватьИмяЗаглушкиСерии(СтрТ, Ном, ГоденДо);
+ СтрТ.Серия = ИмяСерии;
+ СтрТ.СерияНомер = НомерСерии;
+ ЭтоЗаглушка = Истина;
+ Иначе
+ Если Не ЗначениеЗаполнено(ГоденДо) Тогда
+ ГоденДо = ДатаИзИмениСерии(ИмяСерии);
+ КонецЕсли;
+ Если ЗначениеЗаполнено(ГоденДо) Тогда
+ СтрТ.СерияГоденДо = ГоденДо;
+ КонецЕсли;
+ КонецЕсли;
+
+ СсылкаПоГуид = СсылкаПоUUID(Справочники.СерииНоменклатуры, СтрТ.СерияИдентификатор);
+ Если ЗначениеЗаполнено(СсылкаПоГуид) И ОбщегоНазначения.СсылкаСуществует(СсылкаПоГуид) Тогда
+ Если ЭтоЗаглушка Тогда
+ КэшСерий.Вставить(КлючКэшаСерииЗаглушки(ВидСер, Ном, ГоденДо), СсылкаПоГуид);
+ КонецЕсли;
+ Возврат СсылкаПоГуид;
+ КонецЕсли;
+
+ Если ЭтоЗаглушка Тогда
+ КлючКэшСер = КлючКэшаСерииЗаглушки(ВидСер, Ном, ГоденДо);
+ Иначе
+ КлючПоискаНомера = ?(ЗначениеЗаполнено(НомерСерии), НомерСерии, ИмяСерии);
+ КлючКэшСер = Строка(ВидСер.УникальныйИдентификатор()) + "|" + ИмяСерии + "|" + КлючПоискаНомера;
+ Если ЗначениеЗаполнено(ГоденДо) Тогда
+ КлючКэшСер = КлючКэшСер + "|" + Формат(ГоденДо, "ДФ=yyyyMMdd");
+ КонецЕсли;
+ КонецЕсли;
+
СерИзКэш = КэшСерий.Получить(КлючКэшСер);
Если СерИзКэш <> Неопределено Тогда
Возврат СерИзКэш;
КонецЕсли;
- Если ИспНомерСерии И ЗначениеЗаполнено(КлючПоискаНомера) Тогда
- Сер = Справочники.СерииНоменклатуры.НайтиПоРеквизиту("Номер", КлючПоискаНомера, , ВидСер);
- КонецЕсли;
- Если Не ЗначениеЗаполнено(Сер) И ЗначениеЗаполнено(ИмяСерии) Тогда
- Сер = Справочники.СерииНоменклатуры.НайтиПоНаименованию(ИмяСерии, Истина, , ВидСер);
- КонецЕсли;
- Если Не ЗначениеЗаполнено(Сер) И ЗначениеЗаполнено(НомерСерии) И НомерСерии <> ИмяСерии Тогда
- Сер = Справочники.СерииНоменклатуры.НайтиПоНаименованию(НомерСерии, Истина, , ВидСер);
+ Если ЭтоЗаглушка Тогда
+ Если ИспНомерСерии И ЗначениеЗаполнено(НомерСерии) Тогда
+ Сер = Справочники.СерииНоменклатуры.НайтиПоРеквизиту("Номер", НомерСерии, , ВидСер);
+ КонецЕсли;
+ Если Не ЗначениеЗаполнено(Сер) И ЗначениеЗаполнено(ИмяСерии) Тогда
+ Сер = Справочники.СерииНоменклатуры.НайтиПоНаименованию(ИмяСерии, Истина, , ВидСер);
+ КонецЕсли;
+ Иначе
+ КлючПоискаНомера = ?(ЗначениеЗаполнено(НомерСерии), НомерСерии, ИмяСерии);
+ Если ИспНомерСерии И ЗначениеЗаполнено(КлючПоискаНомера) Тогда
+ Сер = Справочники.СерииНоменклатуры.НайтиПоРеквизиту("Номер", КлючПоискаНомера, , ВидСер);
+ КонецЕсли;
+ Если Не ЗначениеЗаполнено(Сер) И ЗначениеЗаполнено(ИмяСерии) Тогда
+ Сер = Справочники.СерииНоменклатуры.НайтиПоНаименованию(ИмяСерии, Истина, , ВидСер);
+ КонецЕсли;
+ Если Не ЗначениеЗаполнено(Сер) И ЗначениеЗаполнено(НомерСерии) И НомерСерии <> ИмяСерии Тогда
+ Сер = Справочники.СерииНоменклатуры.НайтиПоНаименованию(НомерСерии, Истина, , ВидСер);
+ КонецЕсли;
КонецЕсли;
- МожноСоздать = Не РежимТест И ЗначениеЗаполнено(ИмяСерии);
+ МожноСоздать = Не РежимТест И (ЗначениеЗаполнено(ИмяСерии) Или ЗначениеЗаполнено(НомерСерии));
Если ЭтоЗаглушка Тогда
- МожноСоздать = МожноСоздать И СоздаватьЗаглушкиСерий;
+ МожноСоздать = МожноСоздать И СоздаватьЗаглушкиСерий И ЗначениеЗаполнено(ГоденДо);
КонецЕсли;
Если Не ЗначениеЗаполнено(Сер) И МожноСоздать Тогда
Попытка
+ Если Не ЗначениеЗаполнено(ГоденДо) Тогда
+ ГоденДо = КонецДня(ДатаСрезаОстатковОбработки());
+ СтрТ.СерияГоденДо = ГоденДо;
+ КонецЕсли;
ОбСер = Справочники.СерииНоменклатуры.ПустойОбъект(ВидСер);
Если ИспНомерСерии Тогда
- НомерСер = КлючПоискаНомера;
- Если ЭтоЗаглушка И СтрНачинаетсяС(ИмяСерии, "Перенос_") Тогда
- НомерСер = Сред(ИмяСерии, СтрДлина("Перенос_") + 1);
- КонецЕсли;
- ОбСер.Номер = НомерСер;
+ ОбСер.Номер = ?(ЗначениеЗаполнено(НомерСерии), НомерСерии,
+ ?(ЗначениеЗаполнено(ИмяСерии), ИмяСерии, ""));
Иначе
Попытка
ОбСер.Наименование = ИмяСерии;
Исключение
КонецПопытки;
КонецЕсли;
+ ЗаполнитьОбязательныеРеквизитыСерии(ОбСер, ГоденДо, ВидНом, ЭтоЗаглушка);
ОбСер.Записать();
Сер = ОбСер.Ссылка;
Результат.СозданоСерий = Результат.СозданоСерий + 1;