Нужно бежать со всех ног, чтобы
только оставаться на месте,
а чтобы куда-то попасть, надо бе-
жать как минимум вдвое быстрее!
Льюис Кэрролл "Алиса в Зазеркалье"
Всем привет!
Представляю вашему вниманию обработку для загрузки банковских выписок Альфа-Банка. Подобные обработки в 1С-программах называются "клиентом банка". Для упрощения, я буду называть "Клиент-банком" или обработка "Клиент-банк".
Данная обработка не является универсальной для всех конфигураций 1С. Разработка обработки проходила в программе "Бухгалтерия предприятия, ред. 1.6", на платформе 1С:Предприятие 8.3 (8.3.27.1936) !
Изначально обработка была разработана как самостоятельная внешняя обработка - в этом виде она как раз и прикреплена к публикации - рис. 4. В последствии механизм обработки был встроен в типовую обработку "КлиентБанк" для удобной работы пользователей и бесшовной интеграции с типовой БП ред.1.6 - рис. 1 и 2.
Загрузка выписок делится на два этапа: чтение файла загрузки *.csv и создание входящих платежек (или в терминах других приложений 1С - документов "Поступление денежных средств").
Чтение файла *.csv
Обработка строк файла *.csv происходит "построчно": из строки по Инн определяется Контрагент, по НомеруСчета - Банковский расчетный счет. Обращаться в цикле по строкам в каждой итерации к базе данных для определения Контрагента по ИНН и для определения БанкСчета по НомеруСчету - это очень длительная операция, и по своей сути, это исполнение "запроса в цикле".
Как ускорить процесс и обойти "запрос в цикле", я описал в статье Алгоритмы: улучшаем загрузку из эксель (см. пункт 2). По сути я использую Соответствие, заранее заполненное вне цикла. Плюс в комментариях к той статье предложен другой вариант обхода проблемы - использование всего одного запроса к базе вне цикла, но с двумя проходами по циклу (подобный вариант реализован мною на этапе "Загрузка платежек").
Ниже представлен листинг кода. Предварительно собираем Соответствие контрагентов по ИНН, но не по всему справочнику "Контрагенты", а с отбором - и это ключевое.
Загрузка банковских выписок подразумевает, что мы будет загружать только оплаты от покупателей. В моем случае все плательщики объединены в отдельном регистре сведений, общее количество актуальных плательщиков намного меньше, чем общий список контрагентов за несколько лет - см. листинг. Поэтому создание соответствия контрагентов по ИНН происходит быстро, получение контрагента по ИНН - также быстро.
Листинг кода 1. Чтение файла *.csv
СоответствиеКонтрагентов = Новый Соответствие;
СоотРасчСчетов = Новый Соответствие;
Попытка
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| СписокПлательщиков.Контрагент.ИНН КАК ИНН,
| СписокПлательщиков.Контрагент КАК Контрагент
|ИЗ
| РегистрСведений.СписокПлательщиков КАК СписокПлательщиков";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл СоответствиеКонтрагентов.Вставить(Выборка.ИНН, Выборка.Контрагент); КонецЦикла;
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| СписокПлательщиков.Контрагент КАК Контрагент
|ПОМЕСТИТЬ СписокКонтрагентов
|ИЗ
| РегистрСведений.СписокПлательщиков КАК СписокПлательщиков
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| БанковскиеСчета.НомерСчета КАК НомерСчета,
| БанковскиеСчета.Ссылка КАК Ссылка
|ИЗ
| Справочник.БанковскиеСчета КАК БанковскиеСчета
|ГДЕ
| БанковскиеСчета.Владелец В
| (ВЫБРАТЬ
| СписокКонтрагентов.Контрагент КАК Контрагент
| ИЗ
| СписокКонтрагентов КАК СписокКонтрагентов)
| И БанковскиеСчета.ПометкаУдаления = ЛОЖЬ";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл СоотРасчСчетов.Вставить(Выборка.НомерСчета, Выборка.Ссылка); КонецЦикла;
Исключение
КонецПопытки;
КоличествоСтрок = СтрЧислоСтрок(ТекстДляРазбора);
ТекущаяСтрока = 3;
Пока ТекущаяСтрока <= КоличествоСтрок Цикл
Стр = СокрЛП(СтрПолучитьСтроку(ТекстДляРазбора, ТекущаяСтрока));
МасЭлементов = СтрРазделить(Стр, Символы.Таб);
РасСчет = МасЭлементов[7];
Если БанковскийСчет.НомерСчета <> РасСчет Тогда
Сообщить("Не совпадает расчетный счет: " + РасСчет);
ТекущаяСтрока = ТекущаяСтрока + 1;
Продолжить;
КонецЕсли;
Загружать = Истина;
РасходПриход = МасЭлементов[26]; //С латинская
ВидОперации = МасЭлементов[27]; //01 - не анализируем
НомерДок = Формат(МасЭлементов[29],"ЧГ=0");
ДатаДок = ДатаИзСтроки(МасЭлементов[28]); //дата учета операции по счету !
Сумма = МасЭлементов[31];
ИННКонтрагента = МасЭлементов[34];
РасчСчетКонтрагента = МасЭлементов[36];
Назначение = МасЭлементов[64];
НоваяСтрокаДокументов = СписокДокументов.Добавить();
НоваяСтрокаДокументов.Дата = ДатаДок;
НоваяСтрокаДокументов.НомерДок = НомерДок;
НоваяСтрокаДокументов.СуммаДокумента = Сумма;
Если РасходПриход = "C" Тогда
НоваяСтрокаДокументов.СуммаПоступило = Сумма;
НоваяСтрокаДокументов.ВидОперации = Перечисления.ВидыОперацийПоступлениеБезналичныхДенежныхСредств.ОплатаПокупателя;
Иначе
НоваяСтрокаДокументов.СуммаСписано = Сумма;
Загружать = Ложь; //документы списания денежных средств не загружаем !
КонецЕсли;
Если НомерДок = "" Тогда
Загружать = Ложь;
КонецЕсли;
Если Сумма = 0 Тогда
Загружать = Ложь;
КонецЕсли;
Контрагент = СоответствиеКонтрагентов.Получить(ИННКонтрагента);
Если Контрагент = Неопределено Тогда
//длительная операция чтения данных из базы, в моем случае редкая по исполнению
//поскольку в моем случае все контрагенты, являющиеся плательщиками, находятся в отдельном регистре.
//для общего случая нужно создать СоответствиеКонтрагентов с отбором
//по признаку Покупатель = Истина, ПометкаУдаления = Ложь, может еще что исходя из вашей специфики
Контрагент = Справочники.Контрагенты.НайтиПоРеквизиту("ИНН", ИННКонтрагента);
КонецЕсли;
НоваяСтрокаДокументов.Контрагент = Контрагент;
Попытка
НоваяСтрокаДокументов.Договор = Контрагент.ОсновнойДоговорКонтрагента; //не у всех есть такой реквизит!
Исключение
КонецПопытки;
Если НЕ ЗначениеЗаполнено(Контрагент) Тогда
//контрагентов нужно заранее создавать
//либо вручную, либо дописать здесь программное создание новых контрагентов,
//как, например, программное создание банковских счетов контрагентов
Загружать = Ложь;
КонецЕсли;
РасСчетКонтр = СоотРасчСчетов.Получить(РасчСчетКонтрагента);
Если РасСчетКонтр = Неопределено Тогда
//длительная операция чтения данных из базы, в моем случае редкая по исполнению
//поскольку в моем случае все контрагенты, являющиеся плательщиками, находятся в отдельном регистре
//и связанные банковские расчетные счета тоже легко отфильтровать.
//для общего случая нужно создать СоответствиеБанкСчетов с отбором
//по контрагентам-владельцам, может еще что исходя из вашей специфики
РасСчетКонтр = Справочники.БанковскиеСчета.НайтиПоРеквизиту("НомерСчета", РасчСчетКонтрагента);
КонецЕсли;
НоваяСтрокаДокументов.СчетКонтрагента = РасСчетКонтр;
Если НЕ ЗначениеЗаполнено(РасСчетКонтр) Тогда
Если ЗначениеЗаполнено(Контрагент) Тогда
НовыйРасчСчет = Справочники.БанковскиеСчета.СоздатьЭлемент();
НовыйРасчСчет.Владелец = Контрагент;
НовыйРасчСчет.НомерСчета = МасЭлементов[36];
Банк = Справочники.Банки.НайтиПоКоду(МасЭлементов[38]);
Если Не ЗначениеЗаполнено(Банк) Тогда
Банк = Справочники.Банки.СоздатьЭлемент();
Банк.Наименование = МасЭлементов[37];
Банк.Код = МасЭлементов[38];
Банк.КоррСчет = МасЭлементов[39];
Банк.Записать();
КонецЕсли;
НовыйРасчСчет.Банк = Банк.Ссылка;
//НовыйРасчСчет.БанкДляРасчетов = Банк.Ссылка; //это не нужно!
НовыйРасчСчет.ВидСчета = "Расчетный";
НовыйРасчСчет.ВалютаДенежныхСредств = мВалютаРегламентированногоУчета;
НовыйРасчСчет.Наименование = Лев(МасЭлементов[36] + ", " + Банк, 100);
НовыйРасчСчет.Записать();
НоваяСтрокаДокументов.СчетКонтрагента = НовыйРасчСчет.Ссылка;
КонецЕсли;
КонецЕсли;
НоваяСтрокаДокументов.НазначениеПлатежа = СокрЛП(Назначение);
НоваяСтрокаДокументов.Загружать = Загружать;
ТекущаяСтрока = ТекущаяСтрока + 1;
КонецЦикла;
Для нас интерес представляет расшифровка показателей файла *.csv - по формату Альфа-Банка.
Формат Альфа-Банка в первых двух строках представляет собой заголовки показателей - на англ. языке на 1ой строке и на русском языке на 2ой строке. Поэтому обработка строк начинается с 3ьей строки !
Также обратите внимание, что "дата платежки" - берется из показателя "дата учета операции по счету", а не из "дата документа" - см. рис. 3. Остальные поля и показатели смотрите в Листинге 1.
Также, при обходе строк файла *.csv я сразу программно создаю банковский расчетный счет контрагента, но не создаю контрагента. В моем случае контрагент должен создаваться изначально вручную, это предполагает ряд дополнительных проверок со стороны пользователя 1С - просто так устроен процесс заведения новых контрагентов в базе, не более.
Чтение файла *.csv происходит по кнопке "Прочитать данные из файла". Создание входящих платежек происходит по кнопке "Загрузить".
Загрузка платежек
Платежки (входящие платежные поручения / поступления денежных средств) ищем по четырем полям: номер входящего документа, дата входящего документа, контрагент и сумма документа. Если в базе платежки не найдено, то создаем новую. Код представлен ниже в Листинге 2.
Листинг кода 2. Загрузка платежек
КолонкиТЗ = "НомерДок, Дата, Контрагент, СуммаПоступило";
КопияТЗ = СписокДокументов.СкопироватьКолонки(КолонкиТЗ);
Для Каждого Стр Из СписокДокументов Цикл
Если Стр.Загружать Тогда
ЗаполнитьЗначенияСвойств(КопияТЗ.Добавить(), Стр);
КонецЕсли;
КонецЦикла;
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| ВнешняяТаблица.НомерДок КАК НомерДок,
| ВнешняяТаблица.Дата КАК Дата,
| ВнешняяТаблица.Контрагент КАК Контрагент,
| ВнешняяТаблица.СуммаПоступило КАК СуммаПоступило
|ПОМЕСТИТЬ СписокДокументов
|ИЗ
| &ВнешняяТаблица КАК ВнешняяТаблица
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| СписокДокументов.НомерДок КАК НомерДок,
| СписокДокументов.Дата КАК Дата,
| СписокДокументов.Контрагент КАК Контрагент,
| СписокДокументов.СуммаПоступило КАК СуммаПоступило,
| ЕСТЬNULL(ПлатежноеПоручениеВходящее.Ссылка, НЕОПРЕДЕЛЕНО) КАК Документ
|ИЗ
| СписокДокументов КАК СписокДокументов
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПлатежноеПоручениеВходящее КАК ПлатежноеПоручениеВходящее
| ПО СписокДокументов.НомерДок = ПлатежноеПоручениеВходящее.НомерВходящегоДокумента
| И СписокДокументов.Дата = ПлатежноеПоручениеВходящее.ДатаВходящегоДокумента
| И СписокДокументов.Контрагент = ПлатежноеПоручениеВходящее.Контрагент
| И СписокДокументов.СуммаПоступило = ПлатежноеПоручениеВходящее.СуммаДокумента";
Запрос.УстановитьПараметр("ВнешняяТаблица", КопияТЗ);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Выборка.Сбросить();
КонецЕсли;
Для Каждого СтрокаТЗ Из СписокДокументов Цикл
Если НЕ СтрокаТЗ.Загружать Тогда
Продолжить;
КонецЕсли;
//загружаем только приходы денег !
Если СтрокаТЗ.СуммаПоступило <= 0 Тогда
Продолжить;
КонецЕсли;
ПараметрыОтбора = Новый Структура(КолонкиТЗ);
ЗаполнитьЗначенияСвойств(ПараметрыОтбора, СтрокаТЗ);
НайденныйДокумент = Неопределено;
Если Выборка.НайтиСледующий(ПараметрыОтбора) Тогда
НайденныйДокумент = Выборка.Документ;
Иначе
НовДок = Документы.ПлатежноеПоручениеВходящее.СоздатьДокумент();
НовДок.Дата = СтрокаТЗ.Дата;
НовДок.НомерВходящегоДокумента = СтрокаТЗ.НомерДок;
НовДок.ДатаВходящегоДокумента = СтрокаТЗ.Дата;
НовДок.Контрагент = СтрокаТЗ.Контрагент;
НовДок.СчетКонтрагента = СтрокаТЗ.СчетКонтрагента;
НовДок.Оплачено = Истина;
НовДок.СуммаДокумента = СтрокаТЗ.СуммаПоступило;
НовДок.Организация = Организация;
НовДок.СчетОрганизации = БанковскийСчет;
НовДок.ВалютаДокумента = мВалютаРегламентированногоУчета;
НовДок.ВидОперации = Перечисления.ВидыОперацийПоступлениеБезналичныхДенежныхСредств.ОплатаПокупателя;
НовДок.НазначениеПлатежа = СтрокаТЗ.НазначениеПлатежа;
НовДок.СчетБанк = ПланыСчетов.Хозрасчетный.НайтиПоКоду("51.09");
НовДок.Комментарий = "#Загружен через клиент-банк";
Попытка
НовДок.Записать(); //мы не проводим документ ! специально для дальнейшей ручной обработки
Исключение
КонецПопытки;
НайденныйДокумент = НовДок.Ссылка;
КонецЕсли;
Выборка.Сбросить();
СтрокаТЗ.Документ = НайденныйДокумент;
КонецЦикла;
Созданные платежки не проводим сразу, но указываем ссылку на них. Далее можно открыть связанную платежку, дозаполнить ее, и после уже провести. В моем случае пользователям еще нужно к платежке прикрепить счет на оплату.
Данную публикацию можно использовать как готовый шаблон для своих изысканий и разработок по загрузке банковских выписок. Обработку можно открыть в любой конфигурации на обычных формах, предварительно заменив названия соответствующих таблиц (контрагентов, платежек).
На этом все. Всем добра!