Как корректно перезаписывать элементы справочника и реализовать учет FIFO для ценных бумаг в 1С v7.7?

Программист 1С v7.7 Бухгалтерский учет Финансы и профессиональные услуги
← На главную

В этой статье мы подробно разберем, как решить типичные проблемы при работе со справочниками и реализовать сложный учет по методу FIFO (First-In, First-Out) для ценных бумаг в 1С v7.7. Мы рассмотрим не только исправление ошибок в коде, но и фундаментальные архитектурные решения, которые позволят создать надежную и прозрачную систему учета.

Исправление ошибки с перезаписью элемента справочника

Наш читатель столкнулся с проблемой: элемент справочника не перезаписывался, хотя, по отладчику, код выполнялся. Причина крылась в некорректном использовании методов для поиска и выбора элементов.

Изначальный код выглядел так:


TransactionReference = CreateObject("Reference.Transaction");
...
Buys.Rest = Buys.Rest - Sales.Rest;
TransactionReference.UseOwner(Sales.Ticker);
TransactionReference.SelectItems();
TransactionReference.FindByCode(Buys.TransactionID.Код);
If TransactionReference.Selected() = 1 Then 
    TransactionReference.Rest = Buys.Rest;
    TransactionReference.Write(); 
EndIf;

Анализируем проблему

  1. Метод SelectItems(): Этот метод предназначен для начала циклической выборки элементов справочника. При его использовании без последующего цикла по элементам (например, Пока TransactionReference.GetItem() = 1 Цикл ... КонецЦикла) он бесполезен, а в данном контексте даже может сбивать с толку. Нам нужно не выбирать элементы для перебора, а найти конкретный по коду.
  2. Метод FindByCode(): Сам по себе метод правильный, но его использование может быть неточным, если не указать контекст поиска. В случае подчиненных справочников или справочников с неуникальными кодами, необходимо уточнять, где именно искать.
  3. Владелец справочника: Вы правильно использовали TransactionReference.UseOwner(Sales.Ticker), чтобы ограничить поиск элементами, подчиненными конкретному владельцу. Однако, комбинация SelectItems() и FindByCode() может некорректно работать.

Предлагаем решение

Для корректного поиска и перезаписи элемента справочника следует:

  1. Удалить TransactionReference.SelectItems(), так как оно лишнее.
  2. Уточнить параметр поиска для FindByCode(). Метод FindByCode() может принимать второй необязательный параметр, указывающий режим поиска: 0 — во всем справочнике, 1 — среди элементов текущего владельца, 2 — среди элементов текущего владельца и в его иерархии. Если ваш справочник подчинен, и вы хотите искать только среди элементов конкретного владельца, используйте 1.

Исправленный код будет выглядеть так:


TransactionReference = CreateObject("Reference.Transaction");
...
Buys.Rest = Buys.Rest - Sales.Rest;
TransactionReference.UseOwner(Sales.Ticker);
// TransactionReference.SelectItems(); // Эту строку удаляем
TransactionReference.FindByCode(Buys.TransactionID.Код, 1); // Добавляем параметр 1
If TransactionReference.Selected() = 1 Then 
    TransactionReference.Rest = Buys.Rest;
    TransactionReference.Write(); 
EndIf;

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

Фундаментальный выбор: Бухгалтерский учет (счета) или Оперативный учет (регистры)?

Изначальная проблема с перезаписью остатков в справочнике — это лишь вершина айсберга. Более глубокая проблема заключалась в самой архитектуре учета. Хранение остатков (таких как Buys.Rest) напрямую в элементах справочника является некорректным подходом для систем 1С. Такой метод не обеспечивает:

Для учета остатков и оборотов в 1С v7.7 существуют два основных механизма:

1. Бухгалтерский учет (БУ)

Этот механизм основан на привычном для бухгалтеров Плане счетов и концепции двойной записи (дебет/кредит). Если вы знакомы с бухгалтерским учетом и вам важна полноценная двойная запись для контроля активов и пассивов, этот вариант будет наиболее понятным и надежным.

  1. Счета: Мы создаем активный счет для учета ценных бумаг, например, SHARES. Также потребуются счета для денежных средств (BANK, BROKER), комиссий (FEES), налогов (TAXES) и финансовых результатов (PL).
  2. Субконто: Это аналитические разрезы, по которым ведется учет на счете. Для счета SHARES нам понадобятся следующие субконто:
    • Субконто 1: Брокер (тип "Справочник.Брокеры" или "Перечисление.Брокеры"). Позволит группировать акции по брокерским счетам.
    • Субконто 2: Акция (тип "Справочник.Акции"). Позволит учитывать каждую ценную бумагу отдельно.
    • Субконто 3: ПартияЗакупа (тип "Документ.ПокупкаЦБ" или "Справочник.ТранзакцииПокупок"). Это критически важное субконто для FIFO. Каждая уникальная покупка (или часть покупки, если она имеет уникальную дату/время/цену) должна быть идентифицирована этим субконто. В качестве значения субконто удобнее всего использовать ссылку на сам документ покупки ценных бумаг.
  3. Остатки и обороты: Система 1С автоматически поддерживает остатки и обороты по счетам с учетом всех субконто, что значительно упрощает получение данных для отчетов и расчетов.

2. Оперативный учет (ОУ, Регистры)

Регистры — это более гибкий механизм, предназначенный для хранения и обработки больших объемов данных без привязки к двойной записи. Регистры можно представить как "многомерные кубы" данных, которые позволяют получать информацию в различных разрезах. Если двойная запись не является критичной (например, для чисто управленческого учета без строгой привязки к балансу), регистры могут быть более производительными и гибкими.

  1. Измерения: Аналоги субконто, определяющие аналитические разрезы. Для учета ценных бумаг можно настроить измерения: Брокер, Акция, ПартияЗакупа (ссылка на документ покупки или уникальный идентификатор лота).
  2. Ресурсы: Численные показатели, которые суммируются по измерениям. В нашем случае это будут Количество (акций) и Сумма (стоимость покупки). Важно: не храним `ЕдиничнаяЦена` как ресурс, поскольку она не суммируется. Единичная цена всегда вычисляется как Сумма / Количество.
  3. Реквизиты: Дополнительные описательные данные, которые не участвуют в суммировании (например, комментарий к сделке). Реквизит ВидОперации (BUY/SALE) для регистра остатков излишен, так как тип движения (+/-) уже указывает на это.

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

Реализация метода FIFO с использованием Бухгалтерского учета

Мы рассмотрим, как реализовать метод FIFO, используя рекомендованную структуру бухгалтерского учета с тремя субконто на счете SHARES: Брокер, Акция, ПартияЗакупа.

1. Документ "Покупка ЦБ" (или универсальный "Операция ЦБ" с видом "Покупка")

При проведении документа покупки формируется проводка, увеличивающая остаток акций на счете SHARES:


// В модуле проведения документа "ПокупкаЦБ"

// Создаем объект проводки
Проводка = CreateObject("Проводка");
Проводка.New();
Проводка.Дебет = Счет.Shares; // Счет учета акций
Проводка.Субконто(1, Док.Брокер); // Субконто 1: Брокер из шапки документа
Проводка.Субконто(2, Док.Акция); // Субконто 2: Акция из табличной части
Проводка.Субконто(3, Док); // Субконто 3: Ссылка на текущий документ покупки (ПартияЗакупа)
Проводка.Кредит = Счет.Broker; // Счет брокера, откуда ушли деньги
Проводка.Сумма = Док.СуммаСделки; // Сумма покупки
Проводка.Количество = Док.Количество; // Количество акций
Проводка.Валюта = Док.ВалютаСделки; // Валюта, если счет валютный
Проводка.Записать();

Важный момент: В качестве третьего субконто (ПартияЗакупа) мы используем ссылку на сам документ Док. Это позволяет однозначно идентифицировать каждую партию акций по уникальному документу покупки.

2. Документ "Продажа ЦБ" (или универсальный "Операция ЦБ" с видом "Продажа")

Этот документ будет сложнее, так как необходимо реализовать логику FIFO. При проведении документа продажи:

  1. Получаем остатки партий: Мы запрашиваем остатки по счету SHARES на момент проведения документа, детализируя их до третьего субконто (ПартияЗакупа) и сортируя по дате/времени документа покупки (для FIFO).
  2. 
    // В модуле проведения документа "ПродажаЦБ"
    
    // Параметры для получения остатков
    ДатаПроведения = Док.Дата;
    Брокер = Док.Брокер;
    Акция = Док.Акция;
    КоличествоПродано = Док.Количество; // Общее количество акций, которое хотим продать
    СуммаПродажи = Док.СуммаСделки;
    
    // Получаем остатки по партиям закупа
    ТЗОстаткиПартий = CreateObject("ТаблицаЗначений");
    // Создаем запрос к БухИтогам, который вернет остатки по счету SHARES
    // в разрезе Брокера, Акции и ПартииЗакупа (ДокументаПокупки)
    // Отсортировать нужно по дате документа ПартииЗакупа (Субконто3)
    // Примерный запрос (реализация через запрос к БухИтогам более сложна,
    // здесь приводим концепцию)
    //
    // Для упрощения примера, представим, что мы получаем ТЗ с колонками:
    // "ДокументЗакупа", "ОстатокКоличество", "ОстатокСумма"
    
    // Это концептуальный блок, который на реальном 1С v7.7 будет реализован
    // через объект "БухгалтерскиеИтоги" или запрос к ним.
    // Пример получения остатков:
    // БухИт = CreateObject("БухгалтерскиеИтоги");
    // БухИт.ИспользоватьСубконто(ВидСубконто.Брокер, Брокер);
    // БухИт.ИспользоватьСубконто(ВидСубконто.Акция, Акция);
    // БухИт.ИспользоватьСубконто(ВидСубконто.ПартияЗакупа); // Без указания значения, чтобы получить по всем партиям
    // БухИт.ВыбратьСубконто(ВидСубконто.ПартияЗакупа);
    // БухИт.НаДату(ДатаПроведения); // На дату документа продажи
    
    // ... далее цикл по полученным партиям для сортировки и списания
    // Для примера создадим фиктивную ТЗ
    ТЗОстаткиПартий.НоваяКолонка("ДокументЗакупа", "Документ");
    ТЗОстаткиПартий.НоваяКолонка("ОстатокКоличество", "Число");
    ТЗОстаткиПартий.НоваяКолонка("ОстатокСумма", "Число");
    
    // Заполняем ТЗ данными (на практике это будут реальные остатки по БухИтогам)
    // Представим, что у нас есть 2 партии закупа:
    // ДокументПокупки1 (дата 01.01.21), 10 шт, 1000 руб.
    // ДокументПокупки2 (дата 05.01.21), 15 шт, 1650 руб.
    Строка = ТЗОстаткиПартий.НоваяСтрока();
    Строка.ДокументЗакупа = ПолучитьДокумент(ПолучитьДату(2021,1,1), "ПокупкаЦБ", "000000001"); // Пример ссылки
    Строка.ОстатокКоличество = 10;
    Строка.ОстатокСумма = 1000;
    
    Строка = ТЗОстаткиПартий.НоваяСтрока();
    Строка.ДокументЗакупа = ПолучитьДокумент(ПолучитьДату(2021,1,5), "ПокупкаЦБ", "000000002"); // Пример ссылки
    Строка.ОстатокКоличество = 15;
    Строка.ОстатокСумма = 1650;
    
    // Сортировка (если БухИтоги не вернули отсортированными)
    ТЗОстаткиПартий.Сортировать("ДокументЗакупа.ДатаДок"); // Сортируем по дате документа закупа
    
    ОсталосьПродать = КоличествоПродано;
    ОбщаяСебестоимостьПродажи = 0;
    
    ТЗОстаткиПартий.ВыбратьСтроки();
    Пока ТЗОстаткиПартий.ПолучитьСтроку() = 1 И ОсталосьПродать > 0 Цикл
        ДокументЗакупа = ТЗОстаткиПартий.ДокументЗакупа;
        КоличествоПартии = ТЗОстаткиПартий.ОстатокКоличество;
        СуммаПартии = ТЗОстаткиПартий.ОстатокСумма;
    
        // Определяем, сколько акций из текущей партии списываем
        СписываемоеКоличество = Мин(ОсталосьПродать, КоличествоПартии);
            
        // Рассчитываем себестоимость списываемой части
        // ЕдиницаЦенаПартии = СуммаПартии / КоличествоПартии;
        СебестоимостьСписания = (СуммаПартии / КоличествоПартии) * СписываемоеКоличество;
        ОбщаяСебестоимостьПродажи = ОбщаяСебестоимостьПродажи + СебестоимостьСписания;
    
        // Формируем проводку списания с SHARES
        Проводка = CreateObject("Проводка");
        Проводка.New();
        Проводка.Дебет = Счет.Broker; // Счет брокера, куда приходят деньги (или SHARES_Продажи)
        Проводка.Кредит = Счет.Shares; // Счет учета акций
        Проводка.Субконто(1, Брокер);
        Проводка.Субконто(2, Акция);
        Проводка.Субконто(3, ДокументЗакупа); // Указываем конкретную партию закупа
        Проводка.Сумма = СебестоимостьСписания;
        Проводка.Количество = СписываемоеКоличество;
        Проводка.Записать();
    
        // Рассчитываем финансовый результат по списанной части
        ЦенаПродажиЕд = СуммаПродажи / КоличествоПродано; // Усредненная цена продажи за единицу
        ПрибыльУбытокПоЧасти = (ЦенаПродажиЕд * СписываемоеКоличество) - СебестоимостьСписания;
    
        Если ПрибыльУбытокПоЧасти > 0 Тогда
            // Прибыль
            Проводка = CreateObject("Проводка");
            Проводка.New();
            Проводка.Дебет = Счет.Shares; // Или счет Брокера, в зависимости от методологии
            Проводка.Кредит = Счет.PL; // Счет прибыли/убытка
            Проводка.Субконто(1, Брокер);
            Проводка.Субконто(2, Акция);
            Проводка.Субконто(3, ДокументЗакупа); // Для детализации П/У по партиям
            Проводка.Сумма = ПрибыльУбытокПоЧасти;
            Проводка.Записать();
        ИначеЕсли ПрибыльУбытокПоЧасти < 0 Тогда
            // Убыток
            Проводка = CreateObject("Проводка");
            Проводка.New();
            Проводка.Дебет = Счет.PL; // Счет прибыли/убытка
            Проводка.Субконто(1, Брокер);
            Проводка.Субконто(2, Акция);
            Проводка.Субконто(3, ДокументЗакупа); // Для детализации П/У по партиям
            Проводка.Кредит = Счет.Shares; // Или счет Брокера
            Проводка.Сумма = Абс(ПрибыльУбытокПоЧасти);
            Проводка.Записать();
        КонецЕсли;
    
        ОсталосьПродать = ОсталосьПродать - СписываемоеКоличество;
    КонецЦикла;
    
    // Проводка на общую сумму поступления денег от продажи (для баланса)
    // Она может быть одной на весь документ продажи
    Проводка = CreateObject("Проводка");
    Проводка.New();
    Проводка.Дебет = Счет.Broker; // Счет брокера, куда приходят деньги
    Проводка.Кредит = Счет.Shares; // Или счет для учета доходов от продажи акций (90.1)
    Проводка.Субконто(1, Брокер);
    Проводка.Сумма = СуммаПродажи;
    Проводка.Записать();
    
  3. Формируем проводки списания: Для каждой списанной части партии формируем проводку по дебету счета BROKER (или временного счета продаж) и кредиту счета SHARES, указывая ту конкретную ПартиюЗакупа, из которой происходит списание. Сумма списания рассчитывается исходя из себестоимости этой партии.
  4. Учитываем финансовый результат: Одновременно рассчитываем прибыль или убыток по каждой списанной части (разница между ценой продажи этой части и ее себестоимостью FIFO) и формируем проводки на счет PL с детализацией до ПартииЗакупа. Это позволяет видеть прибыль/убыток по каждой конкретной партии акций.

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

Организация ввода данных: Брокерский отчет как обработка/контейнер

Для обеспечения максимальной точности FIFO, особенно для внутридневных операций, критически важно, чтобы каждая торговая операция (покупка или продажа) была отражена в системе с точной датой и временем. Если брокерский отчет содержит несколько операций за один день, и их последовательность имеет значение, то обрабатывать его как единый документ с одной табличной частью будет сложно и рискованно.

Мы рекомендуем следующий подход:

  1. Отдельные документы для каждой транзакции: Каждая строка брокерского отчета, представляющая отдельную покупку или продажу ценной бумаги, должна создавать и проводить свой собственный документ в 1С (например, Документ.ПокупкаЦБ или Документ.ПродажаЦБ). Эти документы будут иметь точную дату и время операции, указанные в брокерском отчете.
  2. Брокерский отчет как "контейнер" или "обработка": Вместо того чтобы делать "БрокерскийОтчет" документом с табличной частью, используйте его как обработку или документ-контейнер без собственной табличной части. Эта обработка будет выполнять следующие функции:
    • Загружать данные из Excel-файла брокерского отчета.
    • Парсить каждую строку отчета.
    • Для каждой торговой операции (покупки/продажи) создавать новый документ (например, Документ.ОперацияЦБ) с точной датой и временем из отчета.
    • Проводить созданный документ.
    • Отображать список созданных документов для визуального контроля, но не хранить сами транзакции в своей табличной части.

Преимущества такого подхода:

Вы можете посмотреть, как реализован подобный механизм агрегации документов в типовой конфигурации 1С v7.7 "Торговля и Склад", в обработке "Банковская выписка". Она загружает множество банковских операций и создает для каждой отдельные документы, а сама выступает в роли удобного интерфейса для работы с ними.

Корректная установка даты и времени документа

Еще одна распространенная проблема при создании документов "на лету" — это некорректная установка даты и времени. Наш читатель столкнулся с тем, что документ всегда создавался с временем 12:00:00, несмотря на использование УстановитьВремя().

Выясняем причину

Метод УстановитьВремя() в 1С v7.7 предназначен для установки только временной части уже существующей даты документа. Когда вы создаете новый документ с помощью BuyDoc.Новый(), его дата по умолчанию устанавливается на текущую системную дату (обычно с нулевым временем, т.е. 00:00:00). Если вы затем используете УстановитьВремя(), вы устанавливаете время на 12:00:00 (или другое значение по умолчанию), но дата документа остается текущей системной датой. Если вы хотите установить дату и время, отличающиеся от текущей системной даты, этого метода недостаточно.

Предлагаем решение

Для корректной установки даты и времени нового документа необходимо сначала сформировать полную дату со временем (объект Дата) из ваших строковых данных и затем присвоить ее свойству Дата документа. Это обеспечит, что документ будет записан с точной датой и временем, что критически важно для хронологического учета FIFO.

Рассмотрим пример кода:


// Предположим, у нас есть строковые переменные
// DocDateStr (например, "02.02.21") и DocTimeStr (например, "21:59:06")

// 1. Распознаем составляющие даты:
День = Число(Сред(DocDateStr, 1, 2));
Месяц = Число(Сред(DocDateStr, 4, 2));
Год = Число(Сред(DocDateStr, 7, 2)); 
// Внимательно с форматом года: если "21", то это 2021.
// Если в вашем отчете год указан полностью "2021", то берем 4 символа.
// Если "21" и нужно трактовать как 2021: Год = Число(Сред(DocDateStr, 7, 2)); If Год < 50 Then Год = Год + 2000 Else Год = Год + 1900;

// 2. Распознаем составляющие времени:
Час = Число(Сред(DocTimeStr, 1, 2));
Минута = Число(Сред(DocTimeStr, 4, 2));
Секунда = Число(Сред(DocTimeStr, 7, 2));

// 3. Создаем объект Дата со всеми компонентами:
ПолнаяДатаВремя = Дата(Год, Месяц, День, Час, Минута, Секунда);

// 4. Создаем новый документ:
BuyDoc = CreateObject("Document.ПокупкаЦБ"); // Или любой другой тип документа
BuyDoc.Новый();

// 5. Присваиваем полную дату и время документу:
BuyDoc.Дата = ПолнаяДатаВремя;

// ... Здесь заполняем остальные реквизиты документа ...

// 6. Записываем документ:
BuyDoc.Записать();

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

Выводы и рекомендации

Мы рассмотрели ключевые аспекты реализации учета ценных бумаг по методу FIFO в 1С v7.7. Наши основные рекомендации:

  1. Не храните остатки в справочниках. Это ведет к потере контроля над данными и усложняет учет. Используйте для этих целей бухгалтерские счета или регистры.
  2. Используйте Бухгалтерский учет (БУ) с Планом счетов, если вам нужна двойная запись. Настройте счет SHARES с тремя субконто: Брокер, Акция, ПартияЗакупа (ссылка на документ покупки).
  3. Реализуйте FIFO в модуле проведения документа продажи, получая остатки партий по бухгалтерским итогам и последовательно списывая их.
  4. Организуйте ввод данных через обработку-контейнер. Пусть брокерский отчет создает и проводит отдельные документы для каждой транзакции с точной датой и временем.
  5. Корректно устанавливайте дату и время документов, присваивая свойству Дата документа полноценный объект Дата, содержащий все компоненты (год, месяц, день, час, минута, секунда).

Следуя этим рекомендациям, вы сможете построить надежную, прозрачную и эффективную систему учета ценных бумаг в 1С v7.7, способную корректно рассчитывать финансовые результаты по методу FIFO.

← На главную