При разработке самописных конфигураций или доработке существующих систем под нужды малого бизнеса разработчики часто сталкиваются с запросом заказчика сделать «всё как можно проще». В сфере розничной и мелкооптовой торговли (например, в магазинах сантехники, строительных материалов или автозапчастей) владельцы бизнеса часто практикуют продажу товаров «в долг» постоянным клиентам. Заказчик формулирует задачу максимально лаконично: «Мне нужен один документ "Долговая книжка", где я выберу клиента, запишу сумму продажи, сумму фактической оплаты, а система сама посчитает и покажет мне его долг при следующей сделке».
На первый взгляд задача кажется тривиальной, однако попытка реализовать учет долгов исключительно внутри реквизитов одного документа без использования регистров накопления — это серьезная архитектурная ошибка. Со временем база данных разрастется, и расчет актуального баланса «на лету» простым перебором документов начнет сильно тормозить систему. Кроме того, возникнут проблемы при возврате товаров, оформлении скидок или зачете авансов.
В этой статье мы подробно и по шагам разберем, как правильно спроектировать и реализовать механизм «долговой книжки» в среде «1С:Предприятие» с точки зрения правильной архитектуры баз данных и удобства пользователя.
---
Выясним причину, почему нельзя хранить взаиморасчеты только в документах. Документы в 1С предназначены для фиксации совершения хозяйственных операций в определенный момент времени. Для получения оперативных остатков (кто, кому и сколько должен в данный момент) платформа предоставляет специальный объект метаданных — Регистр накопления. Использование регистра позволяет мгновенно получать остатки долгов на любую дату без необходимости пересчитывать терабайты документов продажи и оплаты.
Для нашей задачи создадим новый регистр накопления. Разберем его структуру:
Взаиморасчеты.Контрагент (тип: СправочникСсылка.Контрагенты) — основной разрез учета, по которому мы будем контролировать долги.ДокументРасчетов (тип: ДокументСсылка.ДолговаяКнижка или составной тип) — рекомендуется добавить это измерение для ведения детального учета по конкретным сделкам (отгрузкам). Это позволит понять, по какой именно накладной клиент остался должен.Сумма (тип: Число, длина 15, точность 2) — величина долга. Положительный остаток (дебет) будет означать долг клиента перед магазином, отрицательный (кредит) — переплату или аванс от клиента.Проанализируем ситуацию: если вести учет только по измерению Контрагент, система будет работать быстрее, но при возникновении спорных ситуаций продавец не сможет доказать покупателю, за какую именно партию товара тот не доплатил. Поэтому аналитика по документам расчетов крайне желательна.
---
Теперь спроектируем сам документ, в котором менеджер или продавец будет фиксировать операции. Чтобы пользователю не приходилось вводить отдельно документ продажи и отдельно документ оплаты, мы объединим эти функции в одном интерфейсе, как и просил заказчик.
Создадим документ ДолговаяКнижка со следующими реквизитами шапки:
Контрагент (тип: СправочникСсылка.Контрагенты, проверка заполнения: Выдавать ошибку).СуммаПродажи (тип: Число, длина 15, точность 2) — общая стоимость отгруженных товаров.СуммаОплаты (тип: Число, длина 15, точность 2) — сумма наличных или безналичных средств, внесенных клиентом в момент сделки.Комментарий (тип: Строка) — для произвольных заметок продавца.На форму документа мы также выведем несколько расчетных полей, которые не будут сохраняться в базе данных, но помогут пользователю ориентироваться «здесь и сейчас»:
СуммаДолгаПоСделке (вычисляется на клиенте: СуммаПродажи - СуммаОплаты).ТекущийДолгКлиента (информационное поле, отображающее общий накопленный долг контрагента с учетом предыдущих сделок).---
Рассмотрим подробнее логику проведения документа. При проведении «Долговой книжки» программа должна одновременно зафиксировать два события в регистре накопления Взаиморасчеты: возникновение долга на сумму покупки и мгновенное его погашение на сумму внесенной оплаты.
Откроем модуль объекта документа ДолговаяКнижка и напишем обработчик проведения. Разберем по шагам программный код:
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// Установка флага записи движений по регистру Взаиморасчеты
Движения.Взаиморасчеты.Записывать = Истина;
// Очищаем движения, созданные документом ранее, чтобы избежать зацикливания при перепроведении
Движения.Взаиморасчеты.Очистить();
// 1. Фиксируем увеличение долга покупателя (Приход в регистр)
Если СуммаПродажи > 0 Тогда
Движение = Движения.Взаиморасчеты.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Приход;
Движение.Период = Дата;
Движение.Контрагент = Контрагент;
Движение.ДокументРасчетов = Ссылка;
Движение.Сумма = СуммаПродажи;
КонецЕсли;
// 2. Фиксируем оплату от покупателя (Расход из регистра)
Если СуммаОплаты > 0 Тогда
Движение = Движения.Взаиморасчеты.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
Движение.Период = Дата;
Движение.Контрагент = Контрагент;
Движение.ДокументРасчетов = Ссылка;
Движение.Сумма = СуммаОплаты;
КонецЕсли;
КонецПроцедуры
Важное примечание: Обратите внимание, что обе записи делаются с привязкой к текущему документу в измерении ДокументРасчетов. Это позволяет сохранять полную историю расчетов в разрезе каждой сделки.
---
Реализуем ключевое требование заказчика: «При выборе клиента должна отображаться сумма его долга». Для этого нам потребуется связать клиентскую часть формы документа с серверной функцией запроса к регистру накопления.
Разберем, как это сделать на управляемых формах 1С v8.3:
1. Откроем форму документа ДолговаяКнижка и добавим на форму реквизит ТекущийДолгКлиента (тип: Число). Разместим его на форме рядом с полем выбора контрагента.
2. Для элемента формы Контрагент создадим обработчик события ПриИзменении на клиенте.
3. Напишем код вызова серверного метода для получения актуального остатка по регистру:
&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
// Проверяем, выбран ли контрагент на форме
Если ЗначениеЗаполнено(Объект.Контрагент) Тогда
// Вызываем серверную функцию для получения баланса
ТекущийДолгКлиента = ПолучитьОстатокДолгаНаСервере(Объект.Contragent);
Иначе
ТекущийДолгКлиента = 0;
КонецЕсли;
КонецПроцедуры
4. Теперь реализуем функцию ПолучитьОстатокДолгаНаСервере в модуле формы. Мы будем использовать эффективный запрос к виртуальной таблице остатков регистра накопления:
&НаСервереБезКонтекста
Функция ПолучитьОстатокДолгаНаСервере(Знач ВыбранныйКонтрагент)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЕСТЬNULL(ВзаиморасчетыОстатки.СуммаОстаток, 0) КАК ОстатокДолга
|ИЗ
| РегистрНакопления.Взаиморасчеты.Остатки(&Период, Контрагент = &Контрагент) КАК ВзаиморасчетыОстатки";
// Передаем в параметры запроса текущую дату и выбранного контрагента
Запрос.УстановитьПараметр("Период", ТекущаяДата());
Запрос.УстановитьПараметр("Контрагент", ВыбранныйКонтрагент);
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Выборка.Следующий();
Возврат Выборка.ОстатокДолга;
КонецЕсли;
Возврат 0;
КонецФункции
Благодаря использованию конструкции ЕСТЬNULL, если у выбранного контрагента еще нет истории взаиморасчетов, функция корректно вернет 0, предотвращая появление неопределенных значений на форме.
---
Посмотрим на перспективу развития созданного нами решения. Простой учет хорош на старте, но бизнес-процессы розничного магазина неизбежно усложняются. Рассмотрим типичные ситуации, к которым разработчик должен быть готов заранее:
1. Отражение возвратов товара.
Что делать, если клиент вернул часть закупленной сантехники? Если использовать только документ «Долговая книжка» в режиме продажи, продавцу придется вручную корректировать прошлые документы, что нарушит отчетность закрытых периодов. Рекомендуется сразу добавить в документ реквизит ВидОперации (со значениями: Продажа, Возврат, Оплата) либо спроектировать отдельный простейший документ «Возврат товаров», который будет делать движение «Расход» по регистру Взаиморасчеты.
2. Контроль лимита задолженности.
Рано или поздно ростовщичество или излишнее доверие покупателям приведет к кассовому разрыву. Чтобы застраховать владельца магазина от убытков, добавьте в справочник Контрагенты реквизит РазрешенныйЛимитДолга. В модуле проведения документа ДолговаяКнижка реализуйте проверку:
// Пример проверки лимита при проведении
ТекущийДолг = ПолучитьОстатокДолгаНаСервере(Контрагент);
НовыйДолг = ТекущийДолг + (СуммаПродажи - СуммаОплаты);
Если НовыйДолг > Контрагент.РазрешенныйЛимитДолга Тогда
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Превышен допустимый лимит долга для покупателя! Документ не может быть проведен.";
Сообщение.Сообщить();
Отказ = Истина;
КонецЕсли;
3. Метод FIFO для распределения «свободных» платежей.
Часто покупатели приносят деньги не под конкретную накладную, а просто закрывают накопившуюся сумму круглыми суммами (например, заносят 10 000 рублей). В таком случае, если у вас ведется детальный учет в разрезе документов сделок, в коде проведения необходимо реализовать списание долгов по методу FIFO — программа должна автоматически находить самые старые неоплаченные документы продажи данного контрагента и закрывать долг по ним поочередно. Это избавит продавца от необходимости вручную распределять платежи по накладным.
---
Мы спроектировали и реализовали гибкую и производительную систему ведения долговой книги в среде 1С. Объединив логику фиксации продажи и оплаты в рамках одного интерфейсного документа, мы удовлетворили требования заказчика к простоте работы. При этом использование регистра накопления Взаиморасчеты гарантирует высокое быстродействие системы, корректность отчетов и закладывает прочный фундамент для дальнейшего масштабирования конфигурации.