Учебный курс: Подготовка на 1С:Специалист по платформе 1С:Предприятие 8.3

Общие приемы и механизмы решения задач – Тема № 26:
Как погасить задолженность в разных валютах оплатой в рублях

В этой главе разберем практический пример задачи с переменным списком валют – реализуем требование задачи по погашению задолженности оплатой по методу FIFO. Переплату будем отражать как аванс по контрагенту. Сделаем это в обработке проведения документа «Поступление денег».

Проведение документа «Поступление денег»

Алгоритм погашения задолженности в разрезе накладных по FIFO уже разбирали ранее в блоке «Взаиморасчеты – автоматический зачет оплат и/или погашение задолженности».

Особенность данной задачи состоит в том, что отличаются валюты оплаты и задолженности. Все оплаты оформляются в рублях. Задолженности могут быть в любых валютах. Погашать их нужно по курсу на дату оплаты.

Чтобы понимать, какие суммы долга по накладной можно погасить, будем сравнивать задолженности по накладным с суммой оплаты. Для этого сначала нужно сделать суммы соизмеримыми, т.е. выразить в одной валюте.

Для всех имеющихся задолженностей будем определять курс валюты на дату оплаты и рассчитывать рублевый эквивалент. Для этого при получении данных по остаткам задолженностей в запросе будем делать соединение таблицы остатков РН «Взаиморасчеты» с данными виртуальной таблицы срез последних РС «Курсы валют»:

Схема соединения таблиц для получения данных о задолженностях

Рисунок 1 – Схема соединения таблиц для получения данных о задолженностях

При поступлении оплаты возможны три варианта:

  • Оплата меньше рублевого эквивалента долга по накладной
  • Оплата равна рублевому эквиваленту долга по накладной
  • Оплата больше рублевого эквивалента долга по накладной.

Разберем, как определить сумму погашения долга в валюте для каждого из вариантов.

Вариант №1

Вариант №2

Вариант №3

Оплата меньше рублевого эквивалента долга
Оплата равна рублевому эквиваленту долга Оплата больше рублевого эквивалента долга
Сумму погашения долга в валюте нужно рассчитать по курсу исходя из суммы оплаты:

Сумма погашения = СуммаОплаты/Курс.

Алгоритм на этом закончен, т.к. вся оплата была израсходована.

Сумма погашения долга будет равна остатку долга:

Сумма погашения = Сумма долга (вал.)

В данном случае не нужно рассчитывать сумму по курсу, чтобы избежать возможных ошибок округления при пересчете.

На этом алгоритм обхода задолженностей закончится, т.к. остатка оплаты для погашения следующей задолженности уже не будет.

Сумма погашения долга будет равна остатку долга:

Сумма погашения = Сумма долга (вал.)

Также пересчет по курсу не выполняем.

Алгоритм на этом не заканчивается. Рассчитываем остаток оплаты после погашения долга: для этого от суммы оплаты отнимаем рублевый эквивалент погашенного долга.

Далее применяем ту же логику для анализа остатка оплаты.

Если окажется, что задолженностей для погашения больше нет, а остаток оплаты есть, то остаток оплаты станет авансом.

Cумма погашения долга по Накладной № 1 составит 1 000 / 20 = 50,00 долларов.

Cумма погашения долга по Накладной № 1 равна Сумме долга (вал.) и составит 100,00 долларов.

Cумма погашения долга по Накладной № 1 равна Сумме долга (вал.) и составит 100,00 долларов. Cумма погашения долга по Накладной № 2 составит 1 000 / 30 = 33,33 Евро.

Обращаем внимание, что с точки зрения определения суммы погашения долга варианты 2 и 3 при программной реализации можно объединить в одно условие: если сумма оплаты больше или равна рублевому эквиваленту долга, то сумма погашения равна остатку долга.


Из схемы на Рисунке № 1 видим, что для того, чтобы полностью погасить задолженность по Накладной № 1, покупатель должен перечислить 2 000,00 руб.

А что будет, если он перечислит на несколько копеек больше или меньше? Какое поведение программы будет правильным? Рассмотрим на примерах.

Оплата на несколько копеек меньше рублевого эквивалента долга
Оплата на несколько копеек больше рублевого эквивалента долга

Сумма оплаты меньше суммы рублевого эквивалента долга.

Согласно алгоритму сумму погашения рассчитаем по курсу:

1 999,90 / 20 = 99,995 дол.

Но, как мы знаем, при записи в ресурс регистра произойдет округление и будет записано значение 100 долларов, т.е. произойдет полное погашение задолженности.

Это будет правильно, т.к. недостающая сумма оплаты в несколько копеек в валютном эквиваленте меньше одного цента, т.е. нельзя сказать, что остался хотя бы один цент задолженности.

Сумма оплаты больше суммы рублевого эквивалента долга.

Согласно алгоритму сумма погашения долга будет равна остатку долга и составит 100 долларов. Остаток оплаты после погашения долга составит 8 копеек.

Если Накладная была единственная, то на 8 копеек должно быть сделано движение по авансу.

А что, если следующая накладная есть и для этой накладной валютный эквивалент для 8 копеек будет равен нулю?


Итак, обнаружена проблема: оплата или остаток оплаты в рублях могут быть настолько маленькие, что при расчете суммы погашения долга (вал.) получаем сумму, меньшую, чем самая маленькая единица валюты. Как в примере №5: 8 копеек меньше одного цента.

Проанализируем возможные варианты решения этой проблемы.

Вариант №1: «присоединять» их к предыдущему погашению задолженности (в примере №5 списать 2 000,08 рублей, а не 2 000 рублей). Будет неверно, т.к. следующая накладная может быть в рублях и аванс в 8 копеек без проблем на нее зачтется. И даже если бы «присоединили», то для оплаты в 2 000,10 рублей (100,01 доллар по курсу 20) совершенно правильно остался бы аванс в 10 копеек. Если бы следующая накладная была в евро, например, 10 копеек по курсу 30 рублей составили бы меньше одного евро-цента – имеем все ту же проблему.

Вариант №2: просто списать 8 копеек на следующую накладную и считать, что произошел зачет с суммой погашения 0 валюте. Ноль так ноль, все честно рассчитано по курсу. А теперь гипотетически представим, что покупатель сделал 10 таких оплат по 8 копеек, а это уже математически вполне значимая сумма для погашения долга 0,80/20 = 4 цента. Но при таком подходе, если между оплатами были накладные, окажется, что каждая накладная спишет такую оплату, и задолженность при этом по накладным не уменьшится, т.е. как будто никаких оплат и не было. Такое поведение программы кажется сомнительным.

Вариант №3: оставлять такие «хвостики» оплат в виде авансов. Они будут накапливаться до тех пор, пока не накопится значимая сумма для какой-либо валюты из очередной накладной. По условию задачи авансы нужно учитывать в целом по контрагенту, а не в разрезе поступивших оплат. Как только сумма станет значимой, произойдет погашение аванса одной суммой. В итоге все хорошо: не будет никаких исчезнувших копеек. Это и есть правильный вариант решения проблемы слишком маленькой оплаты.

Листинг кода обработки проведения документа «Поступление оплаты»:

Процедура ОбработкаПроведения(Отказ, РежимПроведения) // 1. Подготовка наборов записей регистра Движения.Взаиморасчеты.Записывать = Истина; Движения.Записать(); // 2. Восстановление для свойства набора движений Записывать значения Истина Движения.Взаиморасчеты.Записывать = Истина; // 3. Запрос по остаткам задолженности по накладным Запрос = Новый Запрос; Запрос.Текст = "ВЫБРАТЬ | ВзаиморасчетыОстатки.ДокументРасчетов, | ВзаиморасчетыОстатки.СуммаОстаток КАК СуммаДолгаВал, | ВзаиморасчетыОстатки.Валюта, | ЕСТЬNULL(КурсыВалютСрезПоследних.Курс, 1) КАК Курс, | ЕСТЬNULL(КурсыВалютСрезПоследних.Курс, 1) * ВзаиморасчетыОстатки.СуммаОстаток КАК СуммаДолгаРуб |ИЗ | РегистрНакопления.Взаиморасчеты.Остатки(&МоментВремени, Контрагент = &Контрагент | И НЕ ДокументРасчетов.Ссылка = ЗНАЧЕНИЕ(Документ.РасходнаяНакладная.ПустаяСсылка)) КАК ВзаиморасчетыОстатки | ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют.СрезПоследних(&МоментВремени, ) КАК КурсыВалютСрезПоследних | ПО ВзаиморасчетыОстатки.Валюта = КурсыВалютСрезПоследних.Валюта | |УПОРЯДОЧИТЬ ПО | ВзаиморасчетыОстатки.ДокументРасчетов.МоментВремени"; //4. Установка параметров запроса Запрос.УстановитьПараметр("Контрагент", Контрагент); Запрос.УстановитьПараметр("МоментВремени", МоментВремени()); // 5. Переменная для отслеживания нераспределенного остатка оплаты ОплатаКРаспределению = Сумма; // 6. Выполняем запрос, обходим выборку по накладным и //формируем движения по погашению задолженности РезультатЗапроса = Запрос.Выполнить(); Выборка = РезультатЗапроса.Выбрать(); Пока Выборка.Следующий() И ОплатаКРаспределению > 0 Цикл СуммаПогашенияДолгаРуб = МИН(Выборка.СуммаДолгаРуб, ОплатаКРаспределению); //7.1 полное погашение задолженности по накладной Если СуммаПогашенияДолгаРуб=Выборка.СуммаДолгаРуб Тогда СуммаПогашенияДолгаВал = Выборка.СуммаДолгаВал; Иначе //7.2 частичное погашение задолженности по накладной //сразу выполненим округления до 2 знаков, чтобы выяснить //значимая ли в валюте сумма погашения получилась СуммаПогашенияДолгаВал = Окр(СуммаПогашенияДолгаРуб/Выборка.Курс,2); КонецЕсли; //8. добавление движения в регистр накопления, только если //валютная сумма погашения долга не равна нулю Если СуммаПогашенияДолгаВал > 0 Тогда Движение = Движения.Взаиморасчеты.Добавить(); Движение.ВидДвижения = ВидДвиженияНакопления.Расход; Движение.Период = Дата; Движение.Контрагент = Контрагент; Движение.ДокументРасчетов = Выборка.ДокументРасчетов; Движение.Сумма = СуммаПогашенияДолгаВал; Движение.Валюта = Выборка.Валюта; //9. уменьшаем сумму нераспределенной оплаты на сумму погашения долга ОплатаКРаспределению = ОплатаКРаспределению - СуммаПогашенияДолгаРуб; КонецЕсли; КонецЦикла; //10. если после погашения всех имеющихся задолженностей //остался нераспределенный остаток, сформируем движение по авансу Если ОплатаКРаспределению > 0 Тогда Движение = Движения.Взаиморасчеты.Добавить(); Движение.ВидДвижения = ВидДвиженияНакопления.Расход; Движение.Период = Дата; Движение.Контрагент = Контрагент; Движение.Сумма = ОплатаКРаспределению; //11. Т.к. оплаты всегда поступают в рублях, то заполним валюту //предопределенным значением Движение.Валюта = Справочники.Валюты.РоссийскийРубль; Движение.ДокументРасчетов = Документы.РасходнаяНакладная.ПустаяСсылка(); КонецЕсли; КонецПроцедуры

Разберем ключевые точки алгоритма

Запрос для получения данных по задолженностям в разрезе накладных (п. 3)

Источником данных для получения информации по остаткам задолженностей будет виртуальная таблица остатков РН «Взаиморасчеты». Будем использовать следующие параметры виртуальной таблицы остатков:

  • В качестве периода задаем параметр МоментВремени
  • Используем отбор по Контрагенту.
  • Учтем, что для остатков задолженностей ДокументРасчетов содержит ссылку на документ «Расходная накладная» и не может быть пустой ссылкой.

Делаем левое соединение с виртуальной таблицей срез последних РС «Курсы валют» по полю Валюта. В качестве периода для виртуальной таблицы КурсыВалютСрезПоследних указываем параметр МоментВремени. Используем левое соединение на случай, если соответствующих записей в таблице КурсыВалютСрезПоследних не будет. По этой же причине используем функцию ЕстьNULL() применительно к полю Курс.

Будем считать, что если курс для валюты в РС «Курсы валют» не установлен, то значит курс равен 1. Например, так будет для валюты «Российский рубль».

Сразу же в запросе рассчитываем рублевый эквивалент задолженности: ЕСТЬNULL(КурсыВалютСрезПоследних.Курс, 1) * ВзаиморасчетыОстатки.СуммаОстаток.

Тут нужно помнить, что если в РС «Курсы валют» не установить проверку заполнения для ресурса Курс, то даже приняв, что если курс не установлен, то он равен единице, возможность ошибки деления на нуль не исключена, т.к. пользователь сможет в режиме 1С:Предприятие задать курс, равный нулю. Поэтому такое допущение выгоднее использовать совместно с проверкой заполнения курса в РС «Курсы валют».

Возможно использовать другое допущение: считать, что если курс валюты не задан, то он равен нулю, т.е. используем выражение ЕСТЬNULL(КурсыВалютСрезПоследних.Курс, 0). В этом случае рублевый эквивалент любой суммы задолженности в валюте с незаданным курсом будет равен нулю. Соответственно, необходимо позаботится в алгоритме о следующих моментах:

  1. Если рублевый эквивалент задолженности равен нулю, то ее не нужно погашать оплатой, т.к. невозможно определить сумму погашения
  2. Не допустить ошибку деления на нуль.

В режиме «1С:Предприятие» для предопределенной валюты «Российский рубль» нужно ввести курс, равный 1.

Упорядочим результат запроса по полю ДокументРасчетов.МоментВремени, чтобы реализовать требование задачи выполнять погашение задолженности по методу FIFO.

Используем именно МоментВремени, а не Дату документа, т.к. может быть внесено несколько документов с одной датой и временем, отличаться документы будут только моментом времени.

Алгоритм погашения задолженности (п. 5, 6, 7, 8, 9)

Алгоритм аналогичен тому, который разбирали в главе Взаиморасчеты. Поэтому подробный разбор кода алгоритма делать не будем. Отличается алгоритм только тем, что сравниваем остаток нераспределенной оплаты не с долгом по накладной, а с рублевым эквивалентом долга. Также при частичном погашении задолженности рассчитываем сумму погашения в валюте по курсу валюты.

Обратим внимание, что проверка курса валюты на равенство нулю не требуется, т.к. в РС «Курсы валют» нулевой курс ввести нельзя, поскольку установлена проверка заполнения. А при отсутствии записи в регистре для валюты в запросе устанавливаем, что курс равен 1.

Кроме того, СуммаПогашенияДолгаРуб может быть настолько маленькой, что при расчете по курсу СуммаПогашенияДолгаВал будет равна нулю после округления до 2 знаков после запятой. Поэтому выполняем проверку и не делаем в этом случае движение по погашению задолженности. Остаток нераспределенной оплаты в этом случае также не уменьшаем, т.к. не было погашения долга.

В п. 9 – как правильно уменьшить нераспределенный остаток оплаты?

Допустимы два варианта в зависимости от того, из каких допущений исходить:

Вариант в случае допущения №1: считаем, что один и тот же документ оплаты не может делать частичное погашение по нескольким накладным или частичное погашение + аванс. В этом случае расчет нового значения ОплатаКРаспределению будет следующим:

ОплатаКРаспределению = ОплатаКРаспределению - СуммаПогашенияДолгаРуб;

Вариант в случае допущения №2: считаем, что если остаток после частичного погашения задолженности меньше минимальной единицы валюты для текущей накладной, но вполне значим для следующей накладной, то выполняем еще одно погашение задолженности. В этом варианте допустимо одновременное наличие частично погашенной накладной и аванса или нескольких частично погашенных накладных.

Расчет нового значения ОплатаКРаспределению для этого варианта будет таким:

ОплатаКРаспределению = ОплатаКРаспределению - СуммаПогашенияДолгаВал*Выборка.Курс;

Алгоритм формирования движения по авансу (п. 10)

Алгоритм аналогичен разобранному в разделе Взаиморасчеты за исключением одного момента: по условию задачи аванс не нужно формировать в разрезе накладных, поэтому измерение ДокументРасчетов заполняем пустой ссылкой на документ «Расходная накладная». По условию оплата поступает всегда в рублях, поэтому измерение Валюта заполняем предопределенным значением Справочник.Валюты.РоссийскийРубль.

В данном примере измерение ДокументРасчетов заполнилось бы значением пустой ссылки на документ даже если бы в коде не было строки:
Движение.ДокументРасчетов = Документы.РасходнаяНакладная.ПустаяСсылка();
Т.к. тип измерения ДокументРасчетов не является составным и содержит только ссылку на документ «Расходная накладная»
Но если бы измерение ДокументРасчетов имело составной тип и включало ссылки на несколько видов документов, например, «ДокументСсылка.РасходнаяНакладная», «ДокументСсылка.ПриходДенег», или имело тип «ДокументСсылка», т.е. документ любого вида, то начальным значением для измерения в новой записи было бы значение Неопределено. В этом случае явная инициализация нужным значением была бы обязательна.В реальной практике рекомендуется такую инициализацию делать всегда, чтобы в дальнейшем при расширении типа не возникло скрытых ошибок в связи с тем, что вместо ПустойСсылки значением по-умолчанию теперь будет Неопределено.

Проверка погашения задолженности в режиме «1С:Предприятие»

Для тестовых примеров заполним курсы валют следующим образом:

Курсы валют для тестового примера

Рисунок 2 – Курсы валют для тестового примера

Для того, чтобы было удобно себя проверять в «Консоли запросов», создадим запрос, который сформирует нам такую же таблицу, как на рисунке № 1:

Запрос для получения данных о состоянии задолженности в разрезе накладных (состояние остатков по РН «Взаиморасчеты»

Рисунок 3 – Запрос для получения данных о состоянии задолженности в разрезе накладных (состояние остатков по РН «Взаиморасчеты»)

Тестовый пример № 1

Внесем документ оплаты на 3 000,00 руб. Ситуация аналогична Примеру № 3 в таблице вариантов оплаты.

Документ «Поступление денег № 1»

Рисунок 4 – Документ «Поступление денег № 1»

Движения документа после проведения:

Движения документа «Поступление денег № 1»

Рисунок 5 – Движения документа «Поступление денег № 1»

Видим, что суммы погашения долга рассчитались верно.

В «Консоли запросов» посмотрим состояние задолженности, выполнив запрос, приведенный на рис. № 3:

Состояние остатков по РН «Взаиморасчеты»

Рисунок 6 – Состояние остатков по РН «Взаиморасчеты»

Создадим еще один документ оплаты на 10 000 рублей, чтобы проверить, как сформируются движения по авансу:

Документ «Поступление денег № 2»

Рисунок 7 – Документ «Поступление денег № 2»

Движения:

Движения документа «Поступление денег № 2»

Рисунок 8 – Движения документа «Поступление денег № 2»

В строке № 1 движений видим, что оплата полностью погасила долг по «Расходной накладной № 2».

В строке № 2 движений – полное погашение долга по «Расходной накладной № 3».

Строка № 3 движений отражает формирование аванса. Документ расчетов не заполнен, валюта – рубль. Все как требуется по условию задачи.

Тестовый пример № 2

Промоделируем ситуацию для примера № 5 из таблицы № 2.

Внесем расходную накладную, но теперь по контрагенту «Купим всегда»:

Документ «Расходная накладная № 5»

Рисунок 9 – Документ «Расходная накладная № 5»

Проводим документ и смотрим его движения:

Движения документа «Расходная накладная № 5»

Рисунок 10 – Движения документа «Расходная накладная № 5»

В движениях видим, что сформировалась задолженность по накладной в размере 100 долларов.

Теперь внесем оплату на сумму 2 000,08 рублей:

Документ «Поступление денег № 4»

Рисунок 11 – Документ «Поступление денег № 4»

Проводим оплату и смотрим движения документа:

Движения документа «Поступление денег № 4»

Рисунок 12 – Движения документа «Поступление денег № 4»

Строка № 2 движений отражает полное погашение оплатой долга по «Расходной накладной № 5».

Строка № 3 движений отражает формирование аванса на 8 копеек. Документ расчетов не заполнен, валюта – рубль.

Алгоритм проведения отработал верно.

Смотрим остатки задолженности в «Консоли запросов»:

Состояние остатков по РН «Взаиморасчеты»

Рисунок 13 – Состояние остатков по РН «Взаиморасчеты»

Видим, что авансы представляют собой отрицательные суммы в таблице остатков.

Подведем итоги

Мы разобрали технологию погашения задолженностей в разных валютах при поступлении оплаты в рублях.

В следующей главе продолжим решение практического примера. Разберем, как реализовать зачет аванса в рублях на накладную в валюте.

Перейти к следующей теме:
“Как корректно зачесть аванс в рублях на накладную в валюте” (№ 27)

Комментарии закрыты