Решаем задачу для подготовки к экзамену "1С:Специалист" (1.25)

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

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

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

Для расчета пеней пользователями раз в неделю формируется документ «Пени», в котором автоматически должны рассчитываться пени по формуле: «количество дней просрочки» * «% пени» * «оставшаяся сумма задолженности по счету».

Просрочка отсчитывается от даты полной отгрузки плюс срок оплаты (из счета). Процент пеней также указывается в каждом счете. Пени на пени не начисляются.

Складской учет товаров не ведется.

Можно считать, что документы задним числом не вводятся, но существующие документы могут перепроводиться.

При поступлении оплаты (документ «Приход денег») в первую очередь покрывается задолженность по счетам с самой ранней датой полной отгрузки. В документе «Приход денег» указывается только покупатель и сумма, а распределение по задолженности должно происходить автоматически при проведении документа.

Необходимо построить отчет для анализа состояния счетов на выбранную дату и анализ счета за период."

Анализ состояния счетов на 26.05.18
Счёт Дата полной отгрузки Задолженность по счёту
Счёт №1 01.05.18 700
Счёт №2 300
Счёт №3 25.05.18 200

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

Анализ счета №3 за период от на 12.02.18 по 27.05.18
Документ Задолженность Оплачено
Расходная накладная №1 200
Расходная накладная №2 300
Приход денег №1 400
Расходная накладная №4 700
Пени №2 35
Приход денег №3 500
Пени №3 25

Создание реквизитов у документов

Давайте начнём решение с того, что определимся, какие должны быть реквизиты у документов. Судя по условию. надо создать документ "Счёт". Какие у него должны быть реквизиты?

Счёт выставляется покупателю, поэтому должен быть реквизит "Покупатель", тип "справочник Контрагенты". У этого реквизита я настроил свойство "Параметры выбора" - "Отбор.Родитель(Покупатели)", чтобы при заполнении можно было бы выбирать только покупателей. Также я добавил три числовых реквизита: "Сумма" (точность 2), "срок" (точность 0), то бишь количество дней, и "процент пеней" (тоже точность 0 сделаем для простоты). Числовые реквизиты можно сделать неотрицательными.

В условии упоминается документ "Расходная накладная". Такой документ имеется в каркасной конфигурации. Какие реквизиты необходимо ему добавить?

Согласно условию, "накладные могут быть только на основании счета". Поэтому сделаем соответствующий реквизит, типа "документ Счёт". Чтобы упростить решение задачи, учёт номенклатуры, количества я решил не делать. Но, согласно условию задачи, "накладных по одному счету может быть несколько". Поэтому используем реквизит "СуммаПоДокументу", чтобы по сумме его значений можно было узнать о полной отгрузке товара. Документ "Приходная накладная" мы в решении использовать не будем.

Какие реквизиты создадим у докумета "Пени"? Или, может быть, никаких не создадим?

Я решил никаких регистров у документа "Пени" не создавать. Всю необходимую информацию он будет брать из регистров.

Какие реквизиты сделаем у документа "Приход денег?"

Согласно условию. в нём "указывается только покупатель и сумма". Сделаем у него соответствующие реквизиты. У реквизита "Покупатель" соответствующим образом настроим свойство "Параметры выбора", чтобы из контрагентов можно было выбирать только покупателей.

Создаём регистры

Сколько мы создадим регистров? Какие у них (у него) будут измерения, ресурсы, реквизиты?

Рассмотрев несколько вариантов, решил я пока сделать два регистра накопления остатков. Первый регистр назовём его "Взаиморасчёты". В нём будем фиксировать приход и расход денег по счетам. Сделаем у него одно измерение - "Счёт", и один ресурс - Сумма. Регистраторами у него будут три вида документов: "Расходная Накладная", "Пени", "Приход денег".

Второй регистр назовём "Счета". Он будет хранить значения реквизитов документов "Счёт". У регистра будет два измерения: "Счёт" и "Покупатель", и три ресурса, в которых будем хранить числовые данные: Сумма", "Срок", "Процент Пеней". Такой выбор я сделал потому, что так будет удобнее получать информацию при помощи виртуальных таблиц регистра накопления, а именно таблицы остатков. Ресурсы у неё могут иметь только числовой тип, а значения реквизитов она не содержит.

Было бы удобно получать данные счёта соединением таблицы регистра с таблицей документа, однако, цитирую правила сдачи: "Единственной достоверной информацией в системе учета следует считать информацию регистров. Информация из документов может рассматриваться лишь как вспомогательная и не может быть абсолютно достоверной."

Регистраторы: Документы "Счёт", "Расходная накладная", "Пени", "Приход денег". Приходом денег мы будем "гасить" записи регистра "Счета".

Пишем обработку проведения документа "Счёт".

Что в ней должно быть?

Я её сделал конструктором.


Процедура ОбработкаПроведения(Отказ, Режим)
	
	Движения.Счета.Записывать = Истина;
	Движение = Движения.Счета.Добавить();
	Движение.ВидДвижения = ВидДвиженияНакопления.Приход;
	Движение.Период = Дата;
	Движение.Счёт = Ссылка;
	Движение.Сумма = Сумма;
	Движение.Срок = Срок;
	Движение.ПроцентПеней = ПроцентПеней;
	Движение.Покупатель = Покупатель;
	
КонецПроцедуры

Пишем обработку проведения документа "Расходная накладная".

В ней данные документа должны записываться в регистр "Взаиморасчёты". Какую методику проведения будем использовать?

Я решил использовать новую методику, то есть сначала запишем данные документа в регистр "Взаиморасчёты", а затем сделаем запрос и проверим правильность записанного. Какие действия произведём до записи в регистр?

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


Процедура ОбработкаПроведения(Отказ, Режим)
	Движения.Взаиморасчёты.Записывать = Истина;
        Движения.Записать();	
	Движения.Взаиморасчёты.Записывать = Истина;
	
	Движение=Движения.Взаиморасчёты.ДобавитьРасход();
	Движение.Счёт=Счёт;
	Движение.Период=Дата;
	Движение.Сумма=СуммаПоДокументу;
		
	Движения.Взаиморасчёты.БлокироватьДляИзменения = Истина;
	 
	Движения.Записать();	 
	 

Теперь мы сделаем запрос. Для чего он нам нужен?

Запрос нужен, чтобы подсчитать сумму всех расходных накладных, включая данную и проверить, не превышает ли она сумму договора. Тогда надо отменить трансакцию, потому что лишние деньги некуда оприходовать. А если сумма всех накладных по договору и сумма договора равны, значит отгрузка произведена полностью, и с даты этого документа начинается отсчёт срока, по истечении котрого начисляются пени. Факт полной отгрузки следует зафиксировать.

Здесь давайте определимся, исходя из условия задачи, когда мы начнём принимать оплату: сразу после регистрации счёта (авансом), или принимать по мере отгрузки (на какую сумму отгружено, столько и принимаем), или же принимать только после полной отгрузки (то есть когда сумма всех расходных накладных по счёту будет равна сумме по счёту)?

Исходя из условия задачи, покупатель сам не выбирает, какие счета оплачивать. Оплата покупателя автоматически распределяется по всем счетам, причём " в первую очередь покрывается задолженность по счетам с самой ранней датой полной отгрузки". Это подразумевает приём оплаты только после полной отгрузки.

Однако возникает вопрос: а где хранить и откуда брать информацию, что отгрузка по счёту произведена полностью, в частности, дату полной отгрузки? Подходят ли для этого регистры "Счета" и "Взаиморасчёты"?

Ресурсы у регистра накопления могут быть только числовые, дату в них не запишешь, а если хранить в измерении, то это усложнит нам отбор по значениям измерений. Можно добавить в регистр реквизит типа "Дата" (или типа Булево "Отгрузка произведена полностью", а дату отгрузки брать из стандартного реквизита "Период" записи). Недостаток таких решений в том, что придётся обращаться не к виртуальным таблицам регистра, а к таблицам его движений, что весьма неудобно.

Поэтому я создал ещё один регистр накопления - "ОтгруженныеСчета", с двумя измерениями: "Счёт" и "ДатаПолнойОтгрузки", и одним ресурсом "ОтгрузкаПроизведенаПолностью". При полной отгрузке будем записывать в приход какое-нибудь число, например, 1, а при полной оплате то же число в расход. Регистраторами у него будут документы "Расходная накладная" и "Приход денег".

Вернёмся к запросу в обработке проведения "Расходной накладной". Какие таблицы будем использовать?

Нам нужна сумма по счёту. Возьмём её из виртуальной таблицы остатков регистра "Счета". Также нам нужна сумма всех расходных накладных по данному счёту. Возьмём её из виртуальной таблицы оборотов (СуммаРасход) регистра "Взаиморасчёты".

Как будем соединять таблицы? Какие у них пропишем параметры? Какие данные мы из них выберем?


	Запрос.Текст = 
		"ВЫБРАТЬ
		|	СчетаОстатки.СуммаОстаток КАК СуммаПоСчёту,
		|	ЕСТЬNULL(ВзаиморасчётыОбороты.СуммаРасход, 0) КАК СуммаРасхода
		|ИЗ
		|	РегистрНакопления.Счета.Остатки(&ГМВ, Счёт = &Счёт) КАК СчетаОстатки
		|	 ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.Взаиморасчёты.Обороты(, &ГМВ, , Счёт = &Счёт) КАК ВзаиморасчётыОбороты
		|	 ПО СчетаОстатки.Счёт = ВзаиморасчётыОбороты.Счёт";
	
	Запрос.УстановитьПараметр("ГМВ", Новый Граница(МоментВремени(), ВидГраницы.Включая));
	Запрос.УстановитьПараметр("Счёт", Счёт);

Здесь нам требуется по одной записи из каждой таблицы, поэтому представляется уместным внутреннее соединение таблиц. Обратите внимание, что СуммаРасход выбирается "в обёртке" функции ЕСТЬNULL. Так сделано потому, что расхода может и не быть (когда расходная накладная по данному счёту первая). Обратите внимание на параметр "ГМВ". Запрос учитывает только что сделанное движение документа.

Теперь давайте обработаем результат запроса. Сделаем проверку на превышение суммой всех расходных накладных (включая данную) суммы по счёту.


	
	РезультатЗапроса = Запрос.Выполнить();
	
	Выборка = РезультатЗапроса.Выбрать();
	
	Выборка.Следующий();

	Если Выборка.СуммаПоСчёту < Выборка.СуммаРасхода Тогда
	
		Отказ = Истина;
		Превышение = Выборка.СуммаРасхода- Выборка.СуммаПоСчёту;
		Сообщение = Новый СообщениеПользователю;
		Сообщение.Текст = "Сумма по документу превышает сумму по счёту на " +Превышение;
		Сообщение.Сообщить(); 


Если же наоборот, сумма по счёту больше суммы расхода, нам больше ничего делать не нужно. Если же сумма по счёту равна сумме расхода, тогда отгрузка произведена полностью, и с момента проведения этой накладой начинается отсчёт срока, по истечении которого пойдут пени. Надо этот факт зафиксировать. Что пишем дальше?


ИначеЕсли 
        Выборка.СуммаПоСчёту =Выборка.СуммаРасхода Тогда
    		
		Движения.ОтгруженныеСчета.Записывать = Истина;
		Движение = Движения.ОтгруженныеСчета.ДобавитьПриход();
		Движение.Период = Дата;
		Движение.Счёт = Счёт;
		Движение.ДатаПолнойОтгрузки = Дата;
		Движение.ОтгрузкаПроизведенаПолностью = 1;
			//Проинформируем пользователя
		Сообщение = Новый СообщениеПользователю;
		Сообщение.Текст = "Отгрузка по счёту произведена полностью";
		Сообщение.Сообщить(); 		

Чтобы увидеть полный код обработки проведения документа ""Расходная накладная"", кликните здесь.

Пишем обработку проведения документа "Приход денег".

Что должен делать данный документ?

Он должен найти все отгруженные, но неоплаченные счета данного покупателя, упорядочить их по дате полной отгрузки и распределить по ним указанную в документе сумму (в порядке очерёдности, на те счета, на которые хватит этой суммы). Если сумма превышает задолженность по всем счетам, документ не должен проводиться. Если задолженность по счёту полностью погашается, должны "погашаться" и соответствующие записи в регистрах.

Начало обработки проведения я сделал традиционно:

Процедура ОбработкаПроведения(Отказ, РежимПроведения)
	Движения.Взаиморасчёты.Записывать=Истина;
	Движения.Счета.Записывать=Истина;
	Движения.ОтгруженныеСчета.Записывать=Истина;
		
	Движения.Записать();
	Движения.Взаиморасчёты.Записывать=Истина;	
        Движения.Счета.Записывать=Истина;
	Движения.ОтгруженныеСчета.Записывать=Истина;
		
	Блокировка = Новый БлокировкаДанных;
	ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.Счета");
	ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
	ЭлементБлокировки.УстановитьЗначение("Покупатель", Покупатель);
	Блокировка.Заблокировать();	

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

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

	Запрос = Новый Запрос;
	Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
	Запрос.Текст = 
		"ВЫБРАТЬ
		|	СчетаОстатки.Счёт КАК Счёт,
		|	СчетаОстатки.Покупатель,
		|	СчетаОстатки.СуммаОстаток КАК СуммаПоСчёту,
		|	СчетаОстатки.ПроцентПенейОстаток КАК ПроцентПеней,
		|	СчетаОстатки.СрокОстаток КАК Срок,
		|	ОтгруженныеСчетаОстатки.ДатаПолнойОтгрузки КАК ДатаПолнойОтгрузки
		|ПОМЕСТИТЬ ДанныеОтгруженныхСчетов
		|ИЗ
		|	РегистрНакопления.Счета.Остатки(&МоментВремени, Покупатель = &Покупатель) КАК СчетаОстатки
		|		ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.ОтгруженныеСчета.Остатки(&МоментВремени, ) КАК ОтгруженныеСчетаОстатки
		|		ПО СчетаОстатки.Счёт = ОтгруженныеСчетаОстатки.Счёт
		|
		|ИНДЕКСИРОВАТЬ ПО
		|	Счёт
		|;
		|
		|////////////////////////////////////////////////////////////////////////////////
		|ВЫБРАТЬ
		|	ДанныеОтгруженныхСчетов.Счёт
		|ИЗ
		|	ДанныеОтгруженныхСчетов КАК ДанныеОтгруженныхСчетов";
		
		Запрос.УстановитьПараметр("Покупатель", Покупатель);
		Запрос.УстановитьПараметр("МоментВремени", МоментВремени());

Во втором запросе пакета я выбрал счета, чтобы установить блокировку регистра "Взаиморасчёты"

	
		Блокировка = Новый БлокировкаДанных;
		ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.Взаиморасчёты");
		ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
		ЭлементБлокировки.ИсточникДанных = РезультатЗапроса;
		ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Счёт", "Счёт");
		Блокировка.Заблокировать();		

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

	Запрос.Текст = "ВЫБРАТЬ
		               |	ВзаиморасчётыОстатки.СуммаОстаток,
		               |	ДанныеОтгруженныхСчетов.Покупатель,
		               |	ДанныеОтгруженныхСчетов.ДатаПолнойОтгрузки,
		               |	ДанныеОтгруженныхСчетов.Счёт,
		               |	ДанныеОтгруженныхСчетов.СуммаПоСчёту,
		               |	ДанныеОтгруженныхСчетов.Срок,
		               |	ДанныеОтгруженныхСчетов.ПроцентПеней,
		               |	ВзаиморасчётыОстатки.Счёт.Представление
		               |ИЗ
		               |	ДанныеОтгруженныхСчетов КАК ДанныеОтгруженныхСчетов
		               |		ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.Взаиморасчёты.Остатки(
		               |				&МоментВремени,
		               |				Счёт В
		               |					(ВЫБРАТЬ
		               |						ДанныеОтгруженныхСчетов.Счёт
		               |					ИЗ
		               |						ДанныеОтгруженныхСчетов)) КАК ВзаиморасчётыОстатки
		               |		ПО ДанныеОтгруженныхСчетов.Счёт = ВзаиморасчётыОстатки.Счёт
		               |
		               |УПОРЯДОЧИТЬ ПО
		               |	ДанныеОтгруженныхСчетов.ДатаПолнойОтгрузки";
	
		

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

	РезультатЗапроса = Запрос.Выполнить();
	
	Выборка = РезультатЗапроса.Выбрать();
	
	СуммаКРаспределению=Сумма;
	
	Пока Выборка.Следующий() И СуммаКРаспределению > 0 Цикл

	Движение=Движения.Взаиморасчёты.ДобавитьПриход();
	Движение.Период = Дата;
	Движение.Счёт = Выборка.Счёт;
	Движение.Сумма = Мин(СуммаКРаспределению, -Выборка.СуммаОстаток);
	ОплаченнаяСумма = Движение.Сумма;
	
	Если  Движение.Сумма>=-Выборка.СуммаОстаток 
		
		Тогда
	 Движение=Движения.Счета.ДобавитьРасход();
	 Движение.Период = Дата;
	 Движение.Счёт = Выборка.Счёт;
	 Движение.Покупатель = Выборка.Покупатель;	 
	 Движение.Сумма = Выборка.СуммаПоСчёту;
	 Движение.Срок = Выборка.Срок;
	 Движение.ПроцентПеней = Выборка.ПроцентПеней;
	 
	 Движение=Движения.ОтгруженныеСчета.ДобавитьРасход();
	 Движение.Период = Дата;
	 Движение.Счёт = Выборка.Счёт;
	 Движение.ДатаПолнойОтгрузки = Выборка.ДатаПолнойОтгрузки;	 
	 Движение.ОтгрузкаПроизведенаПолностью = 1;
	 	 	 
	 Сообщение = Новый СообщениеПользователю;
	 Сообщение.Текст = "Счёт "+Выборка.СчётПредставление+"полностью оплачен";
	 Сообщение.Сообщить(); 
	КонецЕсли;
		
	СуммаКРаспределению=СуммаКРаспределению - ОплаченнаяСумма;

	КонецЦикла;	

    Если СуммаКРаспределению>0 Тогда
	 Отказ = Истина;
	 Сообщение = Новый СообщениеПользователю;
	 Сообщение.Текст = "Сумма оплаты превышает сумму задолженностей данного покупателя по всем счетам на "+ СуммаКРаспределению;
	 Сообщение.Сообщить(); 
		
	
	КонецЕсли; 


КонецПроцедуры

В данном коде хочу прокомментировать, что в регистре "Взаиморасчёты" Расходная накладная делает расходное же движение, а Приход денег, соответственно, приходное. Поэтому задолженность покупателя имеет отрицательный знак.

Чтобы увидеть полный код обработки проведения документа "Приход денег", кликните здесь.

Обработка проведения документа "Пени".

В условии задачи указано, что "пени на пени не начисляются". В этом её отличие от задачи 1.25 из сборника задач для подготовки к экзамену 1С:Специалист. Собственно, разница в коде между обоими вариантами невелика, и мы её ниже рассмотрим.

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

Прежде всего нам нужен список всех полностью отгруженных счетов. Мы возьмём его из таблицы остатков регистра "Отгруженные счета". Также возьмём оттуда дату полной отгрузки.

 "ВЫБРАТЬ
	               |	ОтгруженныеСчетаОстатки.Счёт КАК Счёт,
	               |	ОтгруженныеСчетаОстатки.ДатаПолнойОтгрузки
	               |ПОМЕСТИТЬ ПолностьюОтгруженныеСчета
	               |ИЗ
	               |	РегистрНакопления.ОтгруженныеСчета.Остатки(&МоментВремени) КАК ОтгруженныеСчетаОстатки
	               |
	               |ИНДЕКСИРОВАТЬ ПО
	               |	Счёт
	               |;


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

    |ВЫБРАТЬ
	               |	ПолностьюОтгруженныеСчета.Счёт
	               |ИЗ
	               |	ПолностьюОтгруженныеСчета КАК ПолностьюОтгруженныеСчета";
				   
	Запрос.УстановитьПараметр("МоментВремени", МоментВремени());			   
	РезультатЗапроса = Запрос.Выполнить();
	
		Блокировка = Новый БлокировкаДанных;
		ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.Взаиморасчёты");
		ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
		ЭлементБлокировки.ИсточникДанных = РезультатЗапроса;
		ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Счёт", "Счёт");
		
		ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.Счета");
		ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
		ЭлементБлокировки.ИсточникДанных = РезультатЗапроса;
		ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Счёт", "Счёт");
		
		ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ОтгруженныеСчета");
		ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
		ЭлементБлокировки.ИсточникДанных = РезультатЗапроса;
		ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Счёт", "Счёт");
		Блокировка.Заблокировать();		

Теперь давайте выделим из этих счетов те счета, по которым следует начислять пени, заодно получим те данные, которые нам дальше понадобятся и поместим всё это в другую временную таблицу. С какой таблицей, какое и по какому условию (или, может быть, условиям) будем делать соединение? Как вычислить количество дней от даты документа до той даты, от которой начисляются пени (количество дней просрочки)?

   |ВЫБРАТЬ
	               |	СчетаОстатки.Счёт КАК Счёт,
	               |	СчетаОстатки.СуммаОстаток КАК СуммаПоСчёту,
	               |	СчетаОстатки.ПроцентПенейОстаток КАК ПроцентПеней,
	               |	СчетаОстатки.СрокОстаток КАК Срок,
	               |	ПолностьюОтгруженныеСчета.ДатаПолнойОтгрузки,
	               |	РАЗНОСТЬДАТ(ПолностьюОтгруженныеСчета.ДатаПолнойОтгрузки, &Дата, ДЕНЬ) - СчетаОстатки.СрокОстаток КАК КоличествоДнейПросрочки,
	               |	СчетаОстатки.Покупатель
	               |ПОМЕСТИТЬ ДанныеПросроченныхСчетов
	               |ИЗ
	               |	РегистрНакопления.Счета.Остатки(
	               |			&МоментВремени,
	               |			Счёт В
	               |				(ВЫБРАТЬ
	               |					ПолностьюОтгруженныеСчета.Счёт
	               |				ИЗ
	               |					ПолностьюОтгруженныеСчета)) КАК СчетаОстатки
	               |		ВНУТРЕННЕЕ СОЕДИНЕНИЕ ПолностьюОтгруженныеСчета КАК ПолностьюОтгруженныеСчета
	               |		ПО СчетаОстатки.Счёт = ПолностьюОтгруженныеСчета.Счёт
	               |			И (СчетаОстатки.СрокОстаток < РАЗНОСТЬДАТ(ПолностьюОтгруженныеСчета.ДатаПолнойОтгрузки, &Дата, ДЕНЬ))
	               |
	               |ИНДЕКСИРОВАТЬ ПО
	               |	Счёт
	               |;

Обратите внимание на второе условие соединения. Оно весьма непростое. В нём имеется функция РазностьДат(). Я не уверен, что запрос будет работать оптимально. Если запрос будет тормозить, условие соединения можно заменить аналогичным условием ГДЕ.

Во временной таблице "Данные просроченных счетов" есть почти все нужные нам данные. Теперь нам надо получить сумму недоплаты по каждому из счетов. С какой таблицей, какое (левое или внутреннее) и по какому условию ПО (или, может быть, условиям) будем делать соединение? Как вычислить недоплату? Рассмотрите варианты с включением и без включения пеней в сумму задолженности.

    |ВЫБРАТЬ
	               |	ДанныеПросроченныхСчетов.Счёт,
	               |	ДанныеПросроченныхСчетов.ПроцентПеней,
	               |	ДанныеПросроченныхСчетов.КоличествоДнейПросрочки,
	               |	ДанныеПросроченныхСчетов.СуммаПоСчёту - ЕСТЬNULL(ВзаиморасчётыОбороты.СуммаПриход, 0) КАК Недоплата,
	               |	ДанныеПросроченныхСчетов.Покупатель
	               |ИЗ
	               |	ДанныеПросроченныхСчетов КАК ДанныеПросроченныхСчетов
	               |		ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Взаиморасчёты.Обороты(
	               |				,
	               |				&МоментВремени,
	               |				,
	               |				Счёт В
	               |					(ВЫБРАТЬ
	               |						ДанныеПросроченныхСчетов.Счёт
	               |					ИЗ
	               |						ДанныеПросроченныхСчетов КАК ДанныеПросроченныхСчетов)) КАК ВзаиморасчётыОбороты
	               |		ПО ДанныеПросроченныхСчетов.Счёт = ВзаиморасчётыОбороты.Счёт
	               |			И (ДанныеПросроченныхСчетов.СуммаПоСчёту > ЕСТЬNULL(ВзаиморасчётыОбороты.СуммаПриход, 0))";
		

В запросе я использовал левое соединение, потому что оборотов по счёту может и не быть. Обратите внимание на второе условие соединения ПО (Сумма по счёту больше суммы прихода). Оно отсекает те полностью оплаченные счета, по которым ещё не уплачены пени.

Чтобы начислять пени и на ранее начисленные пени тоже, следует вместо таблицы оборотов регистра "Взаимозачёты" использовать таблицу остатков этого же регистра.

Давайте теперь напишем цикл по результату запроса, начисляющий пени.


	Пока Выборка.Следующий() Цикл
		
		Движение=Движения.Взаиморасчёты.ДобавитьРасход();
		Движение.Период = Дата;
		Движение.Счёт=Выборка.Счёт;
		Движение.Сумма= Выборка.КоличествоДнейПросрочки*(Выборка.ПроцентПеней/100)*Выборка.Недоплата;
		
		КонецЦикла;


Однако если на этом закончить процедуру, то выяснится следующее - если провести документ "Пени", и сразу после этого снова создать и провести другой документ "Пени", то второй документ снова начислит пени на те же счета, хотя пени уже были начислены ранее. Как нам это исправить?

Добавим ещё одно движение в цикл:

	Движение=Движения.Счета.ДобавитьПриход();
		Движение.Период = Дата;
		Движение.Счёт=Выборка.Счёт;
		Движение.Покупатель= Выборка.Покупатель;
	    Движение.Сумма = 0;
		Движение.ПроцентПеней= 0;
		Движение.Срок= Выборка.КоличествоДнейПросрочки;

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

Чтобы увидеть полный код обработки проведения документа "Пени", кликните здесь.

Как правильно перепроводить документы

В условии есть следущая фраза - "Можно считать, что документы задним числом не вводятся, но существующие документы могут перепроводиться". Как обеспечить правильное перепроведение документов?

Я создал последовательность документов, в которую включил документы "Счёт", "Расходная накладная", "Приход денег" и "Пени". В движения, влияющие на последовательность я включил движения по регистрам "Счета", "ОтгруженныеСчета", "Взаиморасчёты". "Перемещение границы при проведении" я установил в значение "Перемещать".

Делаем отчёты

Отчёт "Анализ состояния счетов"

Анализ состояния счетов на 26.05.18
Счёт Дата полной отгрузки Задолженность по счёту
Счёт №1 01.05.18 700
Счёт №2 300
Счёт №3 25.05.18 200

Попробуйте придумать запрос для отчёта. Какие таблицы и с какими соединениями будем использовать? Обратите внимание, что у даты полной отгрузки заполнены не все клеточки, а у задолженности все. Может быть, что по счёту ничего не отгружено и ничего не заплачено. Давайте, пока отгрузка не произведена полностью, записывать в задолженность сумму по счёту, а после отгрузки уже собственно задолженность.

"ВЫБРАТЬ
	СчетаОстатки.Счёт,
	ОтгруженныеСчетаОстатки.ДатаПолнойОтгрузки,
	ВЫБОР
		КОГДА ОтгруженныеСчетаОстатки.ДатаПолнойОтгрузки ЕСТЬ NULL 
			ТОГДА СчетаОстатки.СуммаОстаток
		ИНАЧЕ -ВзаиморасчётыОстатки.СуммаОстаток
	КОНЕЦ КАК ЗадолженностьПоСчёту
ИЗ
	РегистрНакопления.Счета.Остатки КАК СчетаОстатки
		ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОтгруженныеСчета.Остатки КАК ОтгруженныеСчетаОстатки
			ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Взаиморасчёты.Остатки КАК ВзаиморасчётыОстатки
			ПО ОтгруженныеСчетаОстатки.Счёт = ВзаиморасчётыОстатки.Счёт
		ПО СчетаОстатки.Счёт = ОтгруженныеСчетаОстатки.Счёт"

Задолженность (-ВзаиморасчётыОстатки.СуммаОстаток) мы взяли со знаком минус, потому что отгрузка записываетия в регистр в минус (расход), а оплата в плюс.

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

Отчёт "Анализ счёта"

Анализ счета №3 за период от на 12.02.18 по 27.05.18
Документ Задолженность Оплачено
Расходная накладная №1 200
Расходная накладная №2 300
Приход денег №1 400
Расходная накладная №4 700
Пени №2 35
Приход денег №3 500
Пени №3 25

Какой запрос потребуется для получения данных отчёта?

ВЫБРАТЬ
	ВзаиморасчётыОбороты.Регистратор,
	ВЫБОР
		КОГДА ВзаиморасчётыОбороты.СуммаРасход > 0
			ТОГДА ВзаиморасчётыОбороты.СуммаРасход
		ИНАЧЕ 0
	КОНЕЦ КАК Задолженность,
	ВЫБОР
		КОГДА ВзаиморасчётыОбороты.СуммаПриход > 0
			ТОГДА ВзаиморасчётыОбороты.СуммаПриход
		ИНАЧЕ 0
	КОНЕЦ КАК Оплачено,
	ВзаиморасчётыОбороты.Период
ИЗ
	РегистрНакопления.Взаиморасчёты.Обороты(, , Регистратор, Счёт = &Счёт) КАК ВзаиморасчётыОбороты

Нам потребовалась таблица оборотов с разворотом по регистратору. Я добавил в запрос поле "Период", хотя его нет в выводимой таблице отчёта. Оно позволяет упорядочить записи по времени. Параметры "Счёт", "Начало периода", "Конец периода" создались автоматически.

Настройки отчёта я сделал конструктором.

Конфигурацию решения задачи можно скачать здесь. Об ошибках сообщайте по электронной почте obuchmat@mail.ru

Вот ещё одна задача

Наверх

На домашнюю страницу