From 0bd398f28c09d52e3695adf54389338691e53f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=BE=D1=84=D0=B8-=D0=A1=D0=BE=D1=84=D1=82?= Date: Thu, 9 Jul 2026 00:06:09 +0000 Subject: [PATCH] v3.0.0.17: NDFL/SV from employee sheets, employee IDs, DDD naming --- .../Forms/Форма/Ext/Form/Module.bsl | 382 ++++++++++++------ 1 file changed, 256 insertions(+), 126 deletions(-) diff --git a/DataProcessors/ЗагрузкаДанныхИзExcel/Forms/Форма/Ext/Form/Module.bsl b/DataProcessors/ЗагрузкаДанныхИзExcel/Forms/Форма/Ext/Form/Module.bsl index 61e2ff3..c2032b5 100644 --- a/DataProcessors/ЗагрузкаДанныхИзExcel/Forms/Форма/Ext/Form/Module.bsl +++ b/DataProcessors/ЗагрузкаДанныхИзExcel/Forms/Форма/Ext/Form/Module.bsl @@ -25,11 +25,11 @@ Сообщить("Выберите файл!"); Возврат; КонецЕсли; - + Состояние("Инициализация загрузки...", 0); Прогресс = 0; СтатусЗагрузки = "Подготовка..."; - + ПрочитатьExcelНаКлиенте(); КонецПроцедуры @@ -52,8 +52,6 @@ // Ответственность: открытие файла, поиск листов, чтение ячеек // ============================================================================ -// Создаёт COM-объект Excel и открывает файл. -// Возвращает структуру {Книга, Excel} или Неопределено при ошибке. &НаКлиенте Функция ОткрытьФайлExcel(ПутьКФайлу) Попытка @@ -68,7 +66,6 @@ КонецПопытки; КонецФункции -// Закрывает книгу и приложение Excel, удаляет временный файл. &НаКлиенте Процедура ЗакрытьExcel(КонтекстExcel, ВременныйФайл) Если КонтекстExcel = Неопределено Тогда Возврат; КонецЕсли; @@ -82,7 +79,6 @@ КонецЕсли; КонецПроцедуры -// Читает числовое значение ячейки с обработкой ошибок. &НаКлиенте Функция ПрочитатьЯчейкуЧисло(Лист, Строка, Колонка, КонтекстОшибки) Попытка @@ -92,14 +88,13 @@ КонецЕсли; Возврат Число(Значение); Исключение - Сообщить("Ошибка чтения числа: " + КонтекстОшибки - + " | строка " + Строка + " колонка " + Колонка + Сообщить("Ошибка чтения числа: " + КонтекстОшибки + + " | строка " + Строка + " колонка " + Колонка + " | значение: """ + Значение + """ | " + ОписаниеОшибки()); Возврат 0; КонецПопытки; КонецФункции -// Читает строковое значение ячейки. &НаКлиенте Функция ПрочитатьЯчейкуСтрока(Лист, Строка, Колонка) Попытка @@ -111,23 +106,19 @@ КонецПопытки; КонецФункции -// Извлекает имя листа сотрудника из формулы вида ='ИмяЛиста'!C2 -// Возвращает имя листа или пустую строку. &НаКлиенте Функция ИзвлечьИмяЛистаИзФормулы(Формула) Если Формула = Неопределено Тогда Возврат ""; КонецЕсли; ТекстФормулы = СокрЛП("" + Формула); Если Не СтрНачинаетсяС(ТекстФормулы, "='") Тогда Возврат ""; КонецЕсли; - - // Ищем закрывающую кавычку после =' - ПозицияНачала = 3; // после =' + + ПозицияНачала = 3; ПозицияКонца = СтрНайти(ТекстФормулы, "'!", , , ПозицияНачала); Если ПозицияКонца = 0 Тогда Возврат ""; КонецЕсли; - + Возврат Сред(ТекстФормулы, ПозицияНачала, ПозицияКонца - ПозицияНачала); КонецФункции -// Ищет лист по имени (регистронезависимо). &НаКлиенте Функция НайтиЛист(Книга, ИмяЛиста) Для Каждого Лист Из Книга.Sheets Цикл @@ -147,12 +138,9 @@ // Ответственность: сопоставление имён колонок Excel с полями данных // ============================================================================ -// Создаёт Соответствие для поиска колонок по имени заголовка. -// Ключ — имя заголовка (нижний регистр), значение — номер колонки (1-based). &НаКлиенте Функция СоздатьИндексЗаголовков(Лист, НомерСтрокиЗаголовков) Индекс = Новый Соответствие; - // Определяем количество колонок: ищем последнюю непустую колонку ПоследняяКолонка = 1; Для Колонка = 1 По 100 Цикл Значение = Лист.Cells(НомерСтрокиЗаголовков, Колонка).Value; @@ -160,7 +148,7 @@ ПоследняяКолонка = Колонка; КонецЕсли; КонецЦикла; - + Для Колонка = 1 По ПоследняяКолонка Цикл Имя = СокрЛП(НРег(Лист.Cells(НомерСтрокиЗаголовков, Колонка).Value)); Если ЗначениеЗаполнено(Имя) Тогда @@ -170,8 +158,6 @@ Возврат Индекс; КонецФункции -// Находит номер колонки по ключевому слову в заголовке. -// Возвращает номер колонки (1-based) или -1 если не найдено. &НаКлиенте Функция НайтиКолонкуПоЗаголовку(ИндексЗаголовков, КлючевоеСлово) Ключ = НРег(КлючевоеСлово); @@ -183,19 +169,14 @@ Возврат -1; КонецФункции -// Находит строку заголовков таблицы данных. -// Ищет строку, содержащую ХОТЯ БЫ 2 ключевых слова из списка одновременно. -// Это исключает ложные срабатывания на метаданных (строки 0-14). &НаКлиенте Функция НайтиСтрокуЗаголовков(ЛистРасчет) - // Ключевые слова заголовков (в нижнем регистре) КлючевыеСлова = Новый Массив; КлючевыеСлова.Добавить("сотрудник"); КлючевыеСлова.Добавить("должность"); КлючевыеСлова.Добавить("счет"); КлючевыеСлова.Добавить("вид занятости"); - - // Ищем строку с минимум 2 совпадениями ключевых слов + Для Ном = 1 По Мин(30, ЛистРасчет.Rows.Count) Цикл КоличествоСовпадений = 0; Для Каждого КлючевоеСлово Из КлючевыеСлова Цикл @@ -209,53 +190,44 @@ КонецЕсли; КонецЦикла; КонецЦикла; - - // Строка заголовков содержит 2+ ключевых слова + Если КоличествоСовпадений >= 2 Тогда Возврат Ном; КонецЕсли; КонецЦикла; - - // Fallback: строка 15 (для старых xls) + Возврат 15; КонецФункции -// Читает данные из листа сотрудника напрямую из ячеек. -// Формулы в РАСЧЕТ ссылаются на: C2(ФИО), C3(Должность), D3(Счет), A5(Вид занятости), -// и OFFSET-формулы для месячных данных (строка 12 + смещение по A4). &НаКлиенте Функция ПрочитатьДанныеИзЛистаСотрудника(ЛистСотрудника, ФИО) Строка = Новый Структура; Строка.Вставить("ФИО", ФИО); - Строка.Вставить("Должность", ПрочитатьЯчейкуСтрока(ЛистСотрудника, 3, 3)); // C3 - Строка.Вставить("СчетБК", ПрочитатьЯчейкуСтрока(ЛистСотрудника, 3, 4)); // D3 - Строка.Вставить("ВидЗанятости", ПрочитатьЯчейкуСтрока(ЛистСотрудника, 5, 1)); // A5 - - // Месяц загрузки определяет смещение для OFFSET-формул - // Базовая строка данных — строка 12, смещение = месяц + Строка.Вставить("Должность", ПрочитатьЯчейкуСтрока(ЛистСотрудника, 3, 3)); + Строка.Вставить("СчетБК", ПрочитатьЯчейкуСтрока(ЛистСотрудника, 3, 4)); + Строка.Вставить("ВидЗанятости", ПрочитатьЯчейкуСтрока(ЛистСотрудника, 5, 1)); + БазоваяСтрока = 12; Смещение = Объект.МесяцЗагрузки; Если Не ЗначениеЗаполнено(Смещение) Тогда Смещение = 1; КонецЕсли; СтрокаДанных = БазоваяСтрока + Смещение; - - // Часовая норма (столбец C, т.е. колонка 3) + Строка.Вставить("Оклад", ПрочитатьЯчейкуЧисло(ЛистСотрудника, СтрокаДанных, 3, ФИО + " Оклад")); Строка.Вставить("КоличествоСтавок", ПрочитатьЯчейкуЧисло(ЛистСотрудника, СтрокаДанных, 4, ФИО + " Ставки")); Строка.Вставить("ОтработаноДней", ПрочитатьЯчейкуЧисло(ЛистСотрудника, СтрокаДанных, 5, ФИО + " Отработано")); Строка.Вставить("ИтогоНачислено", ПрочитатьЯчейкуЧисло(ЛистСотрудника, СтрокаДанных, 10, ФИО + " Итого начислено")); - Строка.Вставить("НДФЛ", 0); // НДФЛ не на листе сотрудника - Строка.Вставить("ИтогоУдержано", 0); // Удержания не на листе сотрудника - Строка.Вставить("ВсегоНаРуки", 0); // Итого на руки не на листе сотрудника - + Строка.Вставить("НДФЛ", 0); + Строка.Вставить("ИтогоУдержано", 0); + Строка.Вставить("ВсегоНаРуки", 0); + Возврат Строка; КонецФункции -// Определяет номер строки первого сотрудника (первая непустая строка после заголовков). &НаКлиенте Функция НайтиСтрокуПервогоСотрудника(ЛистРасчет, НомерСтрокиЗаголовков) Для Ном = НомерСтрокиЗаголовков + 1 По ЛистРасчет.Rows.Count Цикл Значение = ЛистРасчет.Cells(Ном, 1).Value; - Если ЗначениеЗаполнено(Значение) + Если ЗначениеЗаполнено(Значение) И Не СтрНачинаетсяС(ВРег(СокрЛП("" + Значение)), "ИТОГО") Тогда Возврат Ном; КонецЕсли; @@ -263,7 +235,6 @@ Возврат -1; КонецФункции -// Извлекает значение из текста вида "ИНН 990100718541" → "990100718541" &НаКлиенте Функция ИзвлечьЗначениеИзТекста(Текст, Префикс) Если Не ЗначениеЗаполнено(Текст) Тогда Возврат ""; КонецЕсли; @@ -278,18 +249,14 @@ // ============================================================================ // ДОМЕН: Чтение данных сотрудника -// Ответственность: извлечение данных из строки листа "Расчет" +// Ответственность: извлечение данных из строки листа "Расчет" и листов сотрудников // ============================================================================ -// Читает одну строку данных сотрудника из листа "Расчет". -// ИндексЗаголовков — Соответствие для динамического поиска колонок. &НаКлиенте Функция ПрочитатьСтрокуРасчета(ЛистРасчет, Ном, ФИО, ИндексЗаголовков) Строка = Новый Структура; Строка.Вставить("ФИО", ФИО); - - // Поиск колонок по заголовкам - // Маппинг: "Счет БК" в Excel = "Лицевой счет сотрудника в банке для зарплатного проекта" в 1С ЗиКГУ + КолДолжность = НайтиКолонкуПоЗаголовку(ИндексЗаголовков, "должность"); КолСчетБК = НайтиКолонкуПоЗаголовку(ИндексЗаголовков, "счет"); КолВидЗанятости = НайтиКолонкуПоЗаголовку(ИндексЗаголовков, "вид занятости"); @@ -300,7 +267,7 @@ КолНДФЛ = НайтиКолонкуПоЗаголовку(ИндексЗаголовков, "ндфл"); КолИтогоУдержано = НайтиКолонкуПоЗаголовку(ИндексЗаголовков, "итого удержано"); КолВсегоНаРуки = НайтиКолонкуПоЗаголовку(ИндексЗаголовков, "всего на руки"); - + Строка.Вставить("Должность", ПрочитатьЯчейкуСтрока(ЛистРасчет, Ном, КолДолжность)); Строка.Вставить("СчетБК", ПрочитатьЯчейкуСтрока(ЛистРасчет, Ном, КолСчетБК)); Строка.Вставить("ВидЗанятости", ПрочитатьЯчейкуСтрока(ЛистРасчет, Ном, КолВидЗанятости)); @@ -311,11 +278,10 @@ Строка.Вставить("НДФЛ", ПрочитатьЯчейкуЧисло(ЛистРасчет, Ном, КолНДФЛ, ФИО + " НДФЛ")); Строка.Вставить("ИтогоУдержано", ПрочитатьЯчейкуЧисло(ЛистРасчет, Ном, КолИтогоУдержано, ФИО + " Итого удержано")); Строка.Вставить("ВсегоНаРуки", ПрочитатьЯчейкуЧисло(ЛистРасчет, Ном, КолВсегоНаРуки, ФИО + " Всего на руки")); - + Возврат Строка; КонецФункции -// Читает страховые взносы из строки листа "Расчет". &НаКлиенте Функция ПрочитатьСтрокуВзносов(ЛистРасчет, Ном, ФИО, ИндексЗаголовков) КолДТ6 = НайтиКолонкуПоЗаголовку(ИндексЗаголовков, "дт 6"); @@ -324,10 +290,10 @@ КолФСС = НайтиКолонкуПоЗаголовку(ИндексЗаголовков, "фсс"); КолНСПЗ = НайтиКолонкуПоЗаголовку(ИндексЗаголовков, "нс и пз"); КолСВИтого = НайтиКолонкуПоЗаголовку(ИндексЗаголовков, "св итого"); - + СВ = ПрочитатьЯчейкуЧисло(ЛистРасчет, Ном, КолСВИтого, ФИО + " СВ итого"); Если СВ <= 0 Тогда Возврат Неопределено; КонецЕсли; - + СтрВзносов = Новый Структура; СтрВзносов.Вставить("ФИО", ФИО); СтрВзносов.Вставить("ДТ6Процент", ПрочитатьЯчейкуЧисло(ЛистРасчет, Ном, КолДТ6, ФИО + " ДТ 6%")); @@ -336,20 +302,19 @@ СтрВзносов.Вставить("ФССФОМС", ПрочитатьЯчейкуЧисло(ЛистРасчет, Ном, КолФСС, ФИО + " ФСС/ФОМС")); СтрВзносов.Вставить("НСПЗ", ПрочитатьЯчейкуЧисло(ЛистРасчет, Ном, КолНСПЗ, ФИО + " НС и ПЗ")); СтрВзносов.Вставить("Итого", СВ); - + Возврат СтрВзносов; КонецФункции -// Читает личные данные из листа сотрудника (шапка строк 0-3). &НаКлиенте Функция ПрочитатьЛичныеДанные(Книга, ФИО) ЛичныеДанные = Новый Структура("ИНН, СНИЛС, Паспорт, Адрес", "", "", "", ""); - + ЛистСотрудника = НайтиЛист(Книга, ФИО); Если ЛистСотрудника = Неопределено Тогда Возврат ЛичныеДанные; КонецЕсли; - + ЛичныеДанные.ИНН = ИзвлечьЗначениеИзТекста( ПрочитатьЯчейкуСтрока(ЛистСотрудника, 0, 10), "ИНН"); ЛичныеДанные.СНИЛС = ИзвлечьЗначениеИзТекста( @@ -357,56 +322,183 @@ ЛичныеДанные.Паспорт = ИзвлечьЗначениеИзТекста( ПрочитатьЯчейкуСтрока(ЛистСотрудника, 2, 10), "Паспорт"); ЛичныеДанные.Адрес = ПрочитатьЯчейкуСтрока(ЛистСотрудника, 3, 10); - + Возврат ЛичныеДанные; КонецФункции +// Читает данные НДФЛ и страховых взносов из блока внизу листа сотрудника. +// Блок НДФЛ/СВ начинается с строки 31 ("Расчет НДФЛ и страховых взносов (СВ)"). +// Заголовки: строки 33-35. Данные по месяцам: строки 36-47 (Январь-Декабрь). Итого: строка 48. +// Месяц выбирается по Объект.МесяцЗагрузки: строка = 35 + Месяц. +&НаКлиенте +Функция ПрочитатьНДФЛСоЛистаСотрудника(ЛистСотрудника, ФИО) + Если ЛистСотрудника = Неопределено Тогда + Возврат Неопределено; + КонецЕсли; + + // Проверяем наличие блока НДФЛ: ячейка A31 содержит "Расчет НДФЛ" + ЗаголовокБлока = ПрочитатьЯчейкуСтрока(ЛистСотрудника, 31, 1); + Если СтрНайти(НРег(ЗаголовокБлока), "ндфл") = 0 + И СтрНайти(НРег(ЗаголовокБлока), "расчет") = 0 Тогда + // Проверяем строку 33 (альтернативное расположение) + ЗаголовокБлока33 = ПрочитатьЯчейкуСтрока(ЛистСотрудника, 33, 1); + Если СтрНайти(НРег(ЗаголовокБлока33), "период") = 0 Тогда + Возврат Неопределено; + КонецЕсли; + КонецЕсли; + + // Определяем строку данных для выбранного месяца: 35 + МесяцЗагрузки + Смещение = Объект.МесяцЗагрузки; + Если Не ЗначениеЗаполнено(Смещение) Тогда Смещение = 1; КонецЕсли; + СтрокаДанных = 35 + Смещение; // Строка 36 = Январь, 37 = Февраль, ... + + // Названия месяцев для проверки + МассивМесяцев = Новый Массив; + МассивМесяцев.Добавить("январь"); + МассивМесяцев.Добавить("февраль"); + МассивМесяцев.Добавить("март"); + МассивМесяцев.Добавить("апрель"); + МассивМесяцев.Добавить("май"); + МассивМесяцев.Добавить("июнь"); + МассивМесяцев.Добавить("июль"); + МассивМесяцев.Добавить("август"); + МассивМесяцев.Добавить("сентябрь"); + МассивМесяцев.Добавить("октябрь"); + МассивМесяцев.Добавить("ноябрь"); + МассивМесяцев.Добавить("декабрь"); + + // Проверяем что в найденной строке действительно данные за месяц + ТекстМесяца = НРег(ПрочитатьЯчейкуСтрока(ЛистСотрудника, СтрокаДанных, 1)); + Если МассивМесяцев.Найти(ТекстМесяца) = Неопределено Тогда + // Попробуем найти строку по названию месяца + Для Строка = 36 По 47 Цикл + Текст = НРег(ПрочитатьЯчейкуСтрока(ЛистСотрудника, Строка, 1)); + Если МассивМесяцев.Найти(Текст) <> Неопределено Тогда + СтрокаДанных = Строка; + Прервать; + КонецЕсли; + КонецЦикла; + КонецЕсли; + + НДФЛ = Новый Структура; + НДФЛ.Вставить("ФИО", ФИО); + НДФЛ.Вставить("МесяцРасчета", ПрочитатьЯчейкуСтрока(ЛистСотрудника, СтрокаДанных, 1)); + + // Колонки блока НДФЛ/СВ на листе сотрудника: + // col 4: Сумма валового дохода (за месяц) + // col 6: Сумма валового дохода с начала года + // col 7: Сумма дохода (за месяц) + // col 8: Скидка для НДФЛ (за месяц) + // col 9: Скидка для НДФЛ (за год) + // col 10: Скидка для СВ (за месяц) + // col 11: Скидка для СВ (за год) + // col 12: Вычет на работника (за месяц) + // col 13: Вычет на иждивенцев (за месяц) + // col 14: Всего вычетов (за год) + // col 15: Облагаемый доход (за год) + // col 16: Начисленный НДФЛ (за год) + // col 17: Удержанный НДФЛ (за год) + // col 18: Итого НДФЛ (за месяц) + // col 19: Выплаты по больничному (за год) + // col 20: Налоговая база по СВ (за год) + // col 23: Налоговая база по СВ (за месяц) + // col 24: Страховая часть ПФ + // col 25: Накопительная часть ПФ + // col 31: ФСС начислено с начала года + // col 33: ФСС Итого (за месяц) + // col 34: НС и ПЗ начислено с начала года + // col 36: НС и ПЗ Итого (за месяц) + // col 37: Страховые взносы Итого (за месяц) + + НДФЛ.Вставить("СуммаВаловогоДохода", + ПрочитатьЯчейкуЧисло(ЛистСотрудника, СтрокаДанных, 4, ФИО + " Сумма валового дохода")); + НДФЛ.Вставить("СуммаВаловогоДоходаСНачалаГода", + ПрочитатьЯчейкуЧисло(ЛистСотрудника, СтрокаДанных, 6, ФИО + " Доход с начала года")); + НДФЛ.Вставить("СкидкаДляНДФЛЗаМесяц", + ПрочитатьЯчейкуЧисло(ЛистСотрудника, СтрокаДанных, 8, ФИО + " Скидка НДФЛ месяц")); + НДФЛ.Вставить("СкидкаДляНДФЛЗаГод", + ПрочитатьЯчейкуЧисло(ЛистСотрудника, СтрокаДанных, 9, ФИО + " Скидка НДФЛ год")); + НДФЛ.Вставить("СкидкаДляСВЗаМесяц", + ПрочитатьЯчейкуЧисло(ЛистСотрудника, СтрокаДанных, 10, ФИО + " Скидка СВ месяц")); + НДФЛ.Вставить("ВычетНаРаботникаЗаМесяц", + ПрочитатьЯчейкуЧисло(ЛистСотрудника, СтрокаДанных, 12, ФИО + " Вычет работника")); + НДФЛ.Вставить("ВычетНаРаботникаЗаГод", + 0); // не отображается отдельно, входит в ВсегоВычетовЗаМесяц + НДФЛ.Вставить("ВычетНаИждивенцевЗаМесяц", + ПрочитатьЯчейкуЧисло(ЛистСотрудника, СтрокаДанных, 13, ФИО + " Вычет иждивенцев")); + НДФЛ.Вставить("ВсегоВычетовЗаМесяц", + ПрочитатьЯчейкуЧисло(ЛистСотрудника, СтрокаДанных, 14, ФИО + " Всего вычетов")); + НДФЛ.Вставить("ОблагаемыйДоходЗаМесяц", + 0); // за месяц не выделяется отдельно + НДФЛ.Вставить("ОблагаемыйДоходЗаГод", + ПрочитатьЯчейкуЧисло(ЛистСотрудника, СтрокаДанных, 15, ФИО + " Облагаемый доход")); + НДФЛ.Вставить("НачисленныйНДФЛЗаГод", + ПрочитатьЯчейкуЧисло(ЛистСотрудника, СтрокаДанных, 16, ФИО + " НДФЛ начисленный")); + НДФЛ.Вставить("ИтогоНДФЛЗаМесяц", + ПрочитатьЯчейкуЧисло(ЛистСотрудника, СтрокаДанных, 18, ФИО + " Итого НДФЛ")); + НДФЛ.Вставить("НалоговаяБазаПоСВЗаМесяц", + ПрочитатьЯчейкуЧисло(ЛистСотрудника, СтрокаДанных, 23, ФИО + " База СВ месяц")); + НДФЛ.Вставить("ИтогоФСС", + ПрочитатьЯчейкуЧисло(ЛистСотрудника, СтрокаДанных, 37, ФИО + " Итого СВ")); + + Возврат НДФЛ; +КонецФункции + +&НаКлиенте +Функция ПрочитатьДанныеСотрудника(ЛистСотрудника) + Данные = Новый Структура; + Данные.Вставить("ФИО", ПрочитатьЯчейкуСтрока(ЛистСотрудника, 2, 3)); // C3: ФИО + Данные.Вставить("ИНН", ИзвлечьЗначениеИзТекста( + ПрочитатьЯчейкуСтрока(ЛистСотрудника, 0, 10), "ИНН")); + Данные.Вставить("СНИЛС", ИзвлечьЗначениеИзТекста( + ПрочитатьЯчейкуСтрока(ЛистСотрудника, 1, 10), "СНИЛС")); + Данные.Вставить("ЛицевойСчетВБанке", ПрочитатьЯчейкуСтрока(ЛистСотрудника, 3, 4)); // D4: Номер + Данные.Вставить("ВидЗанятости", ПрочитатьЯчейкуСтрока(ЛистСотрудника, 5, 1)); // A6 + Данные.Вставить("АдресРегистрации", ПрочитатьЯчейкуСтрока(ЛистСотрудника, 3, 10)); // K4 + + Возврат Данные; +КонецФункции + #КонецОбласти #Область ОсновнаяПроцедура_Домен // ============================================================================ // ДОМЕН: Оркестрация загрузки -// Ответственность: координация всех этапов загрузки // ============================================================================ &НаКлиенте Процедура ПрочитатьExcelНаКлиенте() - - // Этап 1: Подготовка файла + СтатусЗагрузки = "Копирование файла..."; Состояние("Копирование файла...", 5); - + ВременныйФайл = КаталогВременныхФайлов() + "temp_data.xls"; КопироватьФайл(Объект.ПутьКФайлу, ВременныйФайл); - - // Этап 2: Открытие Excel + СтатусЗагрузки = "Открытие Excel..."; Состояние("Открытие Excel...", 10); - + КонтекстExcel = ОткрытьФайлExcel(ВременныйФайл); Если КонтекстExcel = Неопределено Тогда УдалитьФайлы(ВременныйФайл); Возврат; КонецЕсли; - - // Этап 3: Поиск листа "Расчет" + СтатусЗагрузки = "Поиск листа 'Расчет'..."; Состояние("Поиск листа 'Расчет'...", 15); - + ЛистРасчет = НайтиЛист(КонтекстExcel.Книга, "Расчет"); Если ЛистРасчет = Неопределено Тогда Сообщить("Лист 'Расчет' не найден!"); ЗакрытьExcel(КонтекстExcel, ВременныйФайл); СтатусЗагрузки = "Ошибка: лист не найден"; - Состояние("Ошибка: лист 'Расчет' не найден", 0); Возврат; КонецЕсли; - - // Этап 4: Определение месяца + СтатусЗагрузки = "Определение месяца..."; Состояние("Определение месяца...", 20); - + МесяцЗначение = ЛистРасчет.Cells(4, 1).Value; Если ЗначениеЗаполнено(МесяцЗначение) Тогда Попытка @@ -415,37 +507,37 @@ Сообщить("Не удалось определить месяц: " + МесяцЗначение); КонецПопытки; КонецЕсли; - - // Этап 5: Построение индекса заголовков + СтатусЗагрузки = "Анализ заголовков..."; Состояние("Анализ заголовков...", 22); - + НомерСтрокиЗаголовков = НайтиСтрокуЗаголовков(ЛистРасчет); ИндексЗаголовков = СоздатьИндексЗаголовков(ЛистРасчет, НомерСтрокиЗаголовков); - - // Этап 6: Определение строки первого сотрудника + СтатусЗагрузки = "Поиск данных..."; Состояние("Поиск данных...", 25); - + Ном = НайтиСтрокуПервогоСотрудника(ЛистРасчет, НомерСтрокиЗаголовков); Если Ном = -1 Тогда Сообщить("Данные сотрудников не найдены!"); ЗакрытьExcel(КонтекстExcel, ВременныйФайл); Возврат; КонецЕсли; - - // Этап 7: Чтение данных + + // Основные массивы данных МассивДанных = Новый Массив; МассивВзносов = Новый Массив; + МассивНДФЛ = Новый Массив; + МассивСотрудников = Новый Массив; + СчетчикСотрудников = 0; + ПустыхПодряд = 0; МаксПустых = 5; КоличествоСтрок = ЛистРасчет.Rows.Count; - + Пока Истина Цикл - // Попытка 1: читаем значение напрямую (xls) ФИО = ПрочитатьЯчейкуСтрока(ЛистРасчет, Ном, 1); - - // Попытка 2: если значение пустое — проверяем формулу (xlsm) + ЛистСотрудника = Неопределено; Если Не ЗначениеЗаполнено(ФИО) Тогда Формула = ЛистРасчет.Cells(Ном, 1).Formula; @@ -459,64 +551,90 @@ КонецЕсли; КонецЕсли; КонецЕсли; - + Если Не ЗначениеЗаполнено(ФИО) Тогда ПустыхПодряд = ПустыхПодряд + 1; Если ПустыхПодряд >= МаксПустых Тогда Прервать; КонецЕсли; Ном = Ном + 1; Продолжить; КонецЕсли; - + ПустыхПодряд = 0; - Если СтрНачинаетсяС(ВРег(ФИО), "ИТОГО") Тогда Прервать; КонецЕсли; - + + // Уникальный идентификатор сотрудника + СчетчикСотрудников = СчетчикСотрудников + 1; + ИДСотрудника = СчетчикСотрудников; + ПрогрессЗаписи = 25 + 65 * (Ном - 1) / Макс(КоличествоСтрок, 1); Состояние("Расчет ЗП: " + ФИО, ПрогрессЗаписи); - - // Если есть лист сотрудника — читаем напрямую из его ячеек + + // Чтение данных ЗП Если ЛистСотрудника <> Неопределено Тогда Строка = ПрочитатьДанныеИзЛистаСотрудника(ЛистСотрудника, ФИО); Иначе Строка = ПрочитатьСтрокуРасчета(ЛистРасчет, Ном, ФИО, ИндексЗаголовков); КонецЕсли; - - // Чтение личных данных + Строка.Вставить("ИдентификаторСотрудника", ИДСотрудника); + + // Личные данные (ИНН, СНИЛС, адрес) ЛичныеДанные = ПрочитатьЛичныеДанные(КонтекстExcel.Книга, ФИО); Для Каждого КлючЗначение Из ЛичныеДанные Цикл - Строка.Вставить(КлючЗначение.Ключ, КлючЗначение.Значение); + Если Не Строка.Свойство(КлючЗначение.Ключ) Тогда + Строка.Вставить(КлючЗначение.Ключ, КлючЗначение.Значение); + КонецЕсли; КонецЦикла; - + МассивДанных.Добавить(Строка); - - // Чтение страховых взносов (из РАСЧЕТ, т.к. на листах сотрудников взносов нет) - СтрВзносов = ПрочитатьСтрокуВзносов(ЛистРасчет, Ном, ФИО, ИндексЗаголовков); + + // Данные сотрудников (скрытая таблица для связи) + Если ЛистСотрудника <> Неопределено Тогда + ДанныеСотр = ПрочитатьДанныеСотрудника(ЛистСотрудника); + Иначе + ДанныеСотр = Новый Структура; + ДанныеСотр.Вставить("ФИО", ФИО); + ДанныеСотр.Вставить("ИНН", ?(ЛичныеДанные <> Неопределено, ЛичныеДанные.ИНН, "")); + ДанныеСотр.Вставить("СНИЛС", ?(ЛичныеДанные <> Неопределено, ЛичныеДанные.СНИЛС, "")); + ДанныеСотр.Вставить("ЛицевойСчетВБанке", ""); + ДанныеСотр.Вставить("ВидЗанятости", ""); + ДанныеСотр.Вставить("АдресРегистрации", ""); + КонецЕсли; + ДанныеСотр.Вставить("Идентификатор", ИДСотрудника); + МассивСотрудников.Добавить(ДанныеСотр); + + // Данные НДФЛ из блока внизу листа сотрудника + Если ЛистСотрудника <> Неопределено Тогда + ДанныеНДФЛ = ПрочитатьНДФЛСоЛистаСотрудника(ЛистСотрудника, ФИО); + Если ДанныеНДФЛ <> Неопределено Тогда + ДанныеНДФЛ.Вставить("ИдентификаторСотрудника", ИДСотрудника); + МассивНДФЛ.Добавить(ДанныеНДФЛ); + КонецЕсли; + КонецЕсли; + + // Страховые взносы из листа "Расчет" + СтрВзносов = ПрочитатьСтрокуВзносов(ЛистРасчет, Ном, ФИО, ИндексЗаголовков); Если СтрВзносов <> Неопределено Тогда + СтрВзносов.Вставить("ИдентификаторСотрудника", ИДСотрудника); МассивВзносов.Добавить(СтрВзносов); КонецЕсли; - + Ном = Ном + 1; КонецЦикла; - - // Этап 8: Закрытие Excel + СтатусЗагрузки = "Закрытие Excel..."; Состояние("Закрытие Excel...", 95); ЗакрытьExcel(КонтекстExcel, ВременныйФайл); - - // Этап 9: Передача на сервер + СтатусЗагрузки = "Передача данных на сервер..."; Состояние("Передача данных на сервер...", 98); - ЗаполнитьТаблицыНаСервере(МассивДанных, МассивВзносов); - - // Этап 10: Обновление формы + ЗаполнитьТаблицыНаСервере(МассивДанных, МассивВзносов, МассивНДФЛ, МассивСотрудников); + Прогресс = 100; СтатусЗагрузки = "Готово! Загружено: " + МассивДанных.Количество() + " сотрудников"; - Состояние("Готово! Загружено: " + МассивДанных.Количество() + " сотрудников", 100); Сообщить(СтатусЗагрузки); - - // Обновление формы + ОбновитьОтображениеДанных(); - + КонецПроцедуры #КонецОбласти @@ -525,34 +643,43 @@ // ============================================================================ // ДОМЕН: Серверная обработка данных -// Ответственность: заполнение таблиц значений, запись в 1С // ============================================================================ &НаСервере Процедура ИнициализироватьТаблицы() КонецПроцедуры -// Заполняет табличные части объекта из массивов структур. -// TabularSection не поддерживает РеквизитФормыВЗначение — работаем через Объект напрямую. &НаСервере -Процедура ЗаполнитьТаблицыНаСервере(МассивДанных, МассивВзносов) - // Очистка и заполнение ДанныеРасчетаЗП +Процедура ЗаполнитьТаблицыНаСервере(МассивДанных, МассивВзносов, МассивНДФЛ, МассивСотрудников) + // Таблица расчета ЗП Объект.ДанныеРасчетаЗП.Очистить(); Для Каждого Эл Из МассивДанных Цикл Строка = Объект.ДанныеРасчетаЗП.Добавить(); ЗаполнитьЗначенияСвойств(Строка, Эл); КонецЦикла; - - // Очистка и заполнение ДанныеСтраховыхВзносов + + // Таблица страховых взносов Объект.ДанныеСтраховыхВзносов.Очистить(); Для Каждого Эл Из МассивВзносов Цикл Строка = Объект.ДанныеСтраховыхВзносов.Добавить(); ЗаполнитьЗначенияСвойств(Строка, Эл); КонецЦикла; + + // Таблица НДФЛ + Объект.ДанныеНДФЛ.Очистить(); + Для Каждого Эл Из МассивНДФЛ Цикл + Строка = Объект.ДанныеНДФЛ.Добавить(); + ЗаполнитьЗначенияСвойств(Строка, Эл); + КонецЦикла; + + // Таблица сотрудников (скрытая, для связи между ТЧ) + Объект.Сотрудники.Очистить(); + Для Каждого Эл Из МассивСотрудников Цикл + Строка = Объект.Сотрудники.Добавить(); + ЗаполнитьЗначенияСвойств(Строка, Эл); + КонецЦикла; КонецПроцедуры -// Записывает данные в документы 1С. -// Работает напрямую с TabularSection через Объект. &НаСервере Процедура ЗаписатьДанныеНаСервере() ГодЗагрузки = ТекущаяДата().Год(); @@ -561,6 +688,9 @@ Если Объект.ДанныеСтраховыхВзносов.Количество() > 0 Тогда Объект.ЗаписатьДанныеСтраховыхВзносов(Объект.ДанныеСтраховыхВзносов, Период); КонецЕсли; + Если Объект.ДанныеНДФЛ.Количество() > 0 Тогда + Объект.ЗаписатьДанныеНДФЛ(Объект.ДанныеНДФЛ, Период); + КонецЕсли; КонецПроцедуры #КонецОбласти