Логирование средствами MS SQL

В данной статье описывается вариант минимальной реализации, а конкретнее только самого факта изменения реквизитов справочников и шапок документов. Спросите зачем? А все очень просто.

По материалам статьи Владимира Сердюка "Реализация системы логирования для 1С(SQL)"

В данной статье описывается вариант минимальной реализации, а конкретнее только <отлавливание> самого факта изменения реквизитов справочников и шапок документов. Спросите зачем?

Автор: Андрей Вахрин
Источник : 1CSQL

А все очень просто. Эта статья может вполне восприниматься как продолжение Получение данных из внешней базы 1С. Т.е. если использовать механизмы описанные в упомянутой статье не для разового переноса, а для постоянного обмена данными между базами, то возникает несколько проблем: Нам постоянно необходимо перетягивать ВСЕ содержимое ВСЕХ справочников. Если документ не входит в указанный нами диапазон загрузки, но был в этот период изменен. Тут нас и спасет логирование. Ну и, естественно, непосредственное отслеживание вносимых пользователями изменений.

Итак, начнем.

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

CREATE Table dbo.CurrentUsers 
(SPID int NOT NULL CONSTRAINT PK_SPID PRIMARY KEY CLUSTERED, 
User1S char (20), UserNT char(20), UserSQL char(20), ClientSQL char(20))

Вторая будет содержать в себе непосредственно лог изменений.

CREATE Table dbo.ЖурналИзменений 
(Тип Char(1), Время DateTime, Пользователь Char(60), ИД Char(9), Таблица Char(80), Поля Char(500))

Вторым шагом будет запись идентификатора процесса, при входе пользователя в 1С и удаление этого идентификатора при выходе.

Я делаю это следующим образом:

//_____________________________________________________________________________
Процедура ПриНачалеРаботыСистемы()
    Загрузчик = CreateObject("Toy.Loader");

    Хэндл = Загрузчик.LoadLibrary(КаталогИБ() + "ExtCompsToysql21.dll");

    глТой = СоздатьОбъект("ToyQuery");

    // запись SPID в служебную таблицу
    WshShell = CreateObject("WScript.Shell");

    глТой.Соединиться(0);
    глТой.Выполнить("INSERT INTO dbo.CurrentUsers (SPID, User1S, UserNT, UserSQL, ClientSQL)
        | VALUES (@@SPID,'"+ ИмяПользователя() + "','"+

    WshShell.ExpandEnvironmentStrings("%username%") + "',SYSTEM_USER,'"+
    WshShell.ExpandEnvironmentStrings("%clientname%") + "')");

КонецПроцедуры //ПриНачалеРаботыСистемы

//_____________________________________________________________________________
Процедура ПриЗавершенииРаботыСистемы()
    глТой.Соединиться(0);

    глТой.Выполнить("DELETE FROM dbo.CurrentUsers WHERE SPID = @@SPID");

    Загрузчик.FreeLibrary(Хэндл);

КонецПроцедуры //ПриЗавершенииРаботыСистемы

Ну вот вроде бы подготовительные работы и закончены. Теперь приступим к основному. Т.е. к созданию механизма, который и будет осуществлять запись лога в созданную таблицу. Это, конечно, можно сделать и поправив все модули объектов 1С, добавив в процедуру ПриЗаписи() некоторое количество кода. Ах, да! И еще не забыть отловить во всех других возможных местах конфигурации и внешних обработках, где возможны изменения объектов с последующей записью. Но, как говорил некогда любимый всеми детишками некогда большой страны дедушка Ленин, <мы пойдем другим путем>! Мы <нарисуем> триггеры на необходимые таблицы.

Какие же таблицы мы выберем? Ну, мне кажется естественным, что прежде всего это будут таблицы справочников (SC*)

Для инд = 1 По Метаданные.Справочник() Цикл
    тСпр = Метаданные.Справочник(инд);

    Попытка
        СКЛ.Выполнить("DROP TRIGGER [TR_D_SC"+ МД.ИДОбъекта(тСпр) + "]");

    Исключение
    КонецПопытки;
    Попытка
        СКЛ.Выполнить("DROP TRIGGER [TR_I_SC"+ МД.ИДОбъекта(тСпр) + "]");

    Исключение
    КонецПопытки;
    Попытка
        СКЛ.Выполнить("DROP TRIGGER [TR_U_SC"+ МД.ИДОбъекта(тСпр) + "]");

    Исключение
    КонецПопытки;
    // удаление
    стр = "CREATE TRIGGER [TR_D_SC"+ МД.ИДОбъекта(тСпр) + "] ON [dbo].[SC"+ МД.ИДОбъекта(тСпр) + "]
    |        FOR DELETE
    |        AS
    |        SET NOCOUNT ON
    |        DECLARE @CurrentUser char(60)
    |        DECLARE @Поля    char(500)
    |        DECLARE @ИД        char(29)
    |
    |        SELECT @CurrentUser = ltrim(rtrim(User1S)) + ';' + ltrim(rtrim(UserNT)) + ';' + ltrim(rtrim(UserSQL)) + ';'
    |        + ltrim(rtrim(ClientSQL)) FROM CurrentUsers WHERE SPID = @@SPID
    |        SET @Поля = ''
    |        SELECT @ИД = ID FROM DELETED
    |
    |        INSERT INTO ЖурналИзменений(Тип,Время,Пользователь,ИД,Таблица,Поля)
    |        VALUES('D', GetDate(), @CurrentUser, @ИД, 'Спр"+ тСпр.Идентификатор + "', @Поля)
    |
    |        SET NOCOUNT OFF";

    Попытка
        СКЛ.Выполнить(стр);
    Исключение
    КонецПопытки;

    // вставка
    стр = "CREATE TRIGGER [TR_I_SC"+ МД.ИДОбъекта(тСпр) + "] ON [dbo].[SC"+ МД.ИДОбъекта(тСпр) + "]
    |        FOR INSERT
    |        AS
    |        SET NOCOUNT ON
    |        DECLARE @CurrentUser char(60)
    |        DECLARE @Поля char(500)
    |        DECLARE @Ид char(29)
    |
    |        SELECT @CurrentUser = ltrim(rtrim(User1S)) + ';' + ltrim(rtrim(UserNT)) + ';' + ltrim(rtrim(UserSQL)) + ';'
    |        + ltrim(rtrim(ClientSQL)) FROM CurrentUsers WHERE SPID = @@SPID
    |        SET @Поля = ''
    |        SELECT @ИД = ID FROM INSERTED
    |
    |        INSERT INTO ЖурналИзменений(Тип,Время,Пользователь,ИД,Таблица,Поля)
    |        VALUES('I', GetDate(), @CurrentUser, @ИД, 'Спр"+ тСпр.Идентификатор + "', @Поля)
    |
    |        SET NOCOUNT OFF";

    Попытка
        СКЛ.Выполнить(стр);
    Исключение
    КонецПопытки;

    // изменение
    стр = "CREATE TRIGGER [TR_U_SC"+ МД.ИДОбъекта(тСпр) + "] ON [dbo].[SC"+ МД.ИДОбъекта(тСпр) + "]
    |        FOR UPDATE
    |        AS
    |        SET NOCOUNT ON
    |        DECLARE @CurrentUser char(60)
    |        DECLARE @Поля char(500)
    |        DECLARE @Ид char(29)
    |
    |        SELECT @CurrentUser = ltrim(rtrim(User1S)) + ';' + ltrim(rtrim(UserNT)) + ';' + ltrim(rtrim(UserSQL)) + ';'
    |        + ltrim(rtrim(ClientSQL)) FROM CurrentUsers WHERE SPID = @@SPID
    |        SELECT @Поля = ''
    |";

    Если тСпр.ДлинаКода <> 0 Тогда
        стр = стр + "
        |        +CASE
        |        WHEN i.CODE <> d.CODE  THEN 'Код,'
        |        ELSE ''
        |        END
        |";

    КонецЕсли;
    Если тСпр.ДлинаНаименования <> 0 Тогда
        стр = стр + "
        |        +CASE
        |        WHEN i.DESCR <> d.DESCR  THEN 'Наименование,'
        |        ELSE ''
        |        END
        |";

    КонецЕсли;
    Если СокрЛП(Метаданные.Справочник(инд).Владелец) <> "Метаданные"Тогда

        стр = стр + "
        |        +CASE
        |        WHEN i.PARENTEXT <> d.PARENTEXT  THEN 'Владелец,'
        |        ELSE ''
        |        END
        |";
    КонецЕсли;

    Если Метаданные.Справочник(инд).КоличествоУровней <> 1 Тогда
        стр = стр + "
        |        +CASE
        |        WHEN i.PARENTID <> d.PARENTID  THEN 'Родитель,'
        |        ELSE ''
        |        END
        |";

    КонецЕсли;
    стр = стр + "
    |        +CASE
    |        WHEN i.ISMARK <> d.ISMARK  THEN 'ПометкаУдаления,'
    |        ELSE ''
    |        END
    |";

    Для чПоле = 1 По тСпр.Реквизит() Цикл

        тПоле = тСпр.Реквизит(чПоле);
        Если тПоле.Периодический = 1 Тогда

            Продолжить;
        КонецЕсли;
        стр = стр + "
        |        +CASE
        |        WHEN i.SP" + МД.ИДОбъекта(тПоле) + " <> d.SP"+ МД.ИДОбъекта(тПоле) + " THEN '"+ тПоле.Идентификатор + ",'
        |        ELSE ''
        |        END
        |";

    КонецЦикла;
    стр = стр + "
    |        FROM INSERTED i, DELETED d
    |        WHERE i.Row_ID = d.Row_ID
    |
    |        SELECT @ИД = ID from DELETED
    |
    |        IF @Поля <> ''
    |        BEGIN
    |            INSERT INTO ЖурналИзменений(Тип,Время,Пользователь,ИД,Таблица,Поля)
    |            VALUES('U', GetDate(), @CurrentUser, @ИД, 'Спр"+ тСпр.Идентификатор + "', @Поля)
    |        END
    |
    |        SET NOCOUNT OFF";

    Попытка
        СКЛ.Выполнить(стр);
    Исключение
    КонецПопытки;

КонецЦикла;

Что за таинственные объекты СКЛ и МД, я надеюсь, вы помните из предыдущей статьи.

Теперь возьмемся за документы. Было бы просто великолепно, если бы все необходимые нам изменения находились только в Журнале (_1SJOURN) (кстати, те изменения, которые влияют на необходимость выгрузки/загрузки именно там и находятся, в основном), но придется еще и окучивать таблицы шапок документов (DH*)

// журнал документов (создание нового  удаление документа)
Попытка
    СКЛ.Выполнить("DROP TRIGGER [TR_D_JOURN]");

Исключение
КонецПопытки;
стр = "CREATE TRIGGER [TR_D_JOURN] ON [dbo].[_1SJOURN]
|        FOR DELETE
|        AS
|        SET NOCOUNT ON
|        DECLARE @CurrentUser char(60)
|        DECLARE @Поля    char(500)
|        DECLARE @ИД        char(29)
|
|        SELECT @CurrentUser = ltrim(rtrim(User1S)) + ';' + ltrim(rtrim(UserNT)) + ';' + ltrim(rtrim(UserSQL)) + ';'
|        + ltrim(rtrim(ClientSQL)) FROM CurrentUsers WHERE SPID = @@SPID
|        SET @Поля = ''
|        SELECT @ИД = IDDOC FROM DELETED
|
|        INSERT INTO ЖурналИзменений(Тип,Время,Пользователь,ИД,Таблица,Поля)
|        VALUES('D', GetDate(), @CurrentUser, @ИД, 'Журнал', @Поля)
|
|        SET NOCOUNT OFF";
Попытка
    СКЛ.Выполнить(стр);

Исключение
КонецПопытки;

// вставка
Попытка
    СКЛ.Выполнить("DROP TRIGGER [TR_I_JOURN]");

Исключение
КонецПопытки;
стр = "CREATE TRIGGER [TR_I_JOURN] ON [dbo].[_1SJOURN]
|        FOR INSERT
|        AS
|        SET NOCOUNT ON
|        DECLARE @CurrentUser char(60)
|        DECLARE @Поля char(500)
|        DECLARE @Ид char(29)
|
|        SELECT @CurrentUser = ltrim(rtrim(User1S)) + ';' + ltrim(rtrim(UserNT)) + ';' + ltrim(rtrim(UserSQL)) + ';'
|        + ltrim(rtrim(ClientSQL)) FROM CurrentUsers WHERE SPID = @@SPID
|        SET @Поля = ''
|        SELECT @ИД = IDDOC FROM INSERTED
|
|        INSERT INTO ЖурналИзменений(Тип,Время,Пользователь,ИД,Таблица,Поля)
|        VALUES('I', GetDate(), @CurrentUser, @ИД, 'Журнал', @Поля)
|
|        SET NOCOUNT OFF";
Попытка
    СКЛ.Выполнить(стр);

Исключение
КонецПопытки;

// update общих реквизитов документов
Попытка
    СКЛ.Выполнить("DROP TRIGGER [TR_U_JOURN]");

Исключение
КонецПопытки;
стр = "CREATE TRIGGER [TR_U_JOURN] ON [dbo].[_1SJOURN]
|        FOR UPDATE
|        AS
|        SET NOCOUNT ON
|        DECLARE @CurrentUser char(60)
|        DECLARE @Поля char(500)
|        DECLARE @Ид char(29)
|
|        SELECT @CurrentUser = ltrim(rtrim(User1S)) + ';' + ltrim(rtrim(UserNT)) + ';' + ltrim(rtrim(UserSQL)) + ';'
|        + ltrim(rtrim(ClientSQL)) FROM CurrentUsers WHERE SPID = @@SPID
|        SELECT @Поля = ''
|
|        +CASE
|        WHEN i.DATE_TIME_IDDOC <> d.DATE_TIME_IDDOC  THEN 'ДатаДок,'
|        ELSE ''
|        END
|
|        +CASE
|        WHEN i.DOCNO <> d.DOCNO  THEN 'Номер,'
|        ELSE ''
|        END
|
|        +CASE
|        WHEN i.CLOSED <> d.CLOSED  THEN 'Проведен,'
|        ELSE ''
|        END
|
|        +CASE
|        WHEN i.ISMARK <> d.ISMARK  THEN 'ПометкаУдаления,'
|        ELSE ''
|        END
|";

Для инд = 1 По Метаданные.ОбщийРеквизитДокумента() Цикл
    тПоле = Метаданные.ОбщийРеквизитДокумента(инд);

    Если тПоле.Сортировка = 0 Тогда
        Продолжить;
    КонецЕсли;

    стр = стр + "
    |        +CASE
    |        WHEN i.SP" + МД.ИДОбъекта(тПоле) + " <> d.SP"+ МД.ИДОбъекта(тПоле) + " THEN '"+ тПоле.Идентификатор + ",'
    |        ELSE ''
    |        END
    |";

КонецЦикла;
стр = стр + "
|        FROM INSERTED i, DELETED d
|        WHERE i.Row_ID = d.Row_ID
|
|        SELECT @ИД = IDDOC from DELETED
|
|        IF @Поля <> ''
|        BEGIN
|            INSERT INTO ЖурналИзменений(Тип,Время,Пользователь,ИД,Таблица,Поля)
|            VALUES('U', GetDate(), @CurrentUser, @ИД, 'Журнал', @Поля)
|        END
|
|        SET NOCOUNT OFF";
Попытка

    СКЛ.Выполнить(стр);
Исключение
КонецПопытки;

// документы
Для инд = 1 По Метаданные.Документ() Цикл

    тДок = Метаданные.Документ(инд);
    // удаление  вставка отслеживаются пожурналу
    // изменение

    Попытка
        СКЛ.Выполнить("DROP TRIGGER [TR_U_DH"+ МД.ИДОбъекта(тДок) + "]");

    Исключение
    КонецПопытки;
    стр = "CREATE TRIGGER [TR_U_DH"+ МД.ИДОбъекта(тДок) + "] ON [dbo].[DH"+ МД.ИДОбъекта(тДок) + "]
    |        FOR UPDATE
    |        AS
    |        SET NOCOUNT ON
    |        DECLARE @CurrentUser char(60)
    |        DECLARE @Поля char(500)
    |        DECLARE @Ид char(29)
    |
    |        SELECT @CurrentUser = ltrim(rtrim(User1S)) + ';' + ltrim(rtrim(UserNT)) + ';' + ltrim(rtrim(UserSQL)) + ';'
    |        + ltrim(rtrim(ClientSQL)) FROM CurrentUsers WHERE SPID = @@SPID
    |        SELECT @Поля = ''
    |";

    Для чПоле = 1 По Метаданные.ОбщийРеквизитДокумента() Цикл

        тПоле = Метаданные.ОбщийРеквизитДокумента(чПоле);
        Если тПоле.Сортировка = 1 Тогда

            Продолжить;
        КонецЕсли;
        стр = стр + "
        |        +CASE
        |        WHEN i.SP"+ МД.ИДОбъекта(тПоле) + " <> d.SP"+ МД.ИДОбъекта(тПоле) + " THEN '"+ тПоле.Идентификатор + ",'
        |        ELSE ''
        |        END
        |";

    КонецЦикла;
    Для чПоле = 1 По тДок.РеквизитШапки() Цикл

        тПоле = тДок.РеквизитШапки(чПоле);
        стр = стр + "
        |        +CASE
        |        WHEN i.SP"+ МД.ИДОбъекта(тПоле) + " <> d.SP"+ МД.ИДОбъекта(тПоле) + " THEN '"+ тПоле.Идентификатор + ",'
        |        ELSE ''
        |        END
        |";

    КонецЦикла;
    Для чПоле = 1 По тДок.РеквизитТабличнойЧасти() Цикл

        тПоле = тДок.РеквизитТабличнойЧасти(чПоле);
        Если тПоле.ИтогПоКолонке = 1 Тогда

            стр = стр + "
            |        +CASE
            |        WHEN i.SP"+ МД.ИДОбъекта(тПоле) + " <> d.SP"+ МД.ИДОбъекта(тПоле) + " THEN '"+ тПоле.Идентификатор + ",'
            |        ELSE ''
            |        END
            |";

        КонецЕсли;
    КонецЦикла;
    стр = стр + "
    |        FROM INSERTED i, DELETED d
    |        WHERE i.IDDOC = d.IDDOC
    |
    |        SELECT @ИД = IDDOC from DELETED
    |
    |        IF @Поля <> ''
    |        BEGIN
    |            INSERT INTO ЖурналИзменений(Тип,Время,Пользователь,ИД,Таблица,Поля)
    |            VALUES('U', GetDate(), @CurrentUser, @ИД, 'Док" + тДок.Идентификатор + "', @Поля)
    |        END
    |
    |        SET NOCOUNT OFF";

    Попытка
        СКЛ.Выполнить(стр);
    Исключение
    КонецПопытки;

КонецЦикла;

Вот и все.

З.Ы. Некоторые таки могут спросить: и что теперь делать? Как связать это логирование с загрузкой из другой базы?

1. Выполняем все требования статьи <Получение данных из внешней базы 1С>. 2. Запускаем на той же базе логирование согласно установкам текущей статьи. 3. И спокойно, для каждого загружаемого объекта, добавляем в запросы проверку на его измененность в течении определенного периода. Вот пример получение Кода и Наименования из справочника Фирмы, элементы которого были изменены в период с 01.10.2004:

 select distinct Спр.Код
, Спр.Наименование
from СпрФирмы Спр
, ЖурналИзменений Ж
where Ж.Время >= '2004-10-01'
  and Ж.Таблица LIKE '%СпрФирмы%'
  and Ж.ИД = Спр.Ссылка

Оригинал статьи: http://1csql.ru/materials/articles/develop/018.htm
Опубликована: 9 ноября 2004

Начать дискуссию

OpenAI объявила о запуске поисковой системы SearchGPT на базе искусственного интеллекта

Разработка SearchGPT ставит OpenAI в прямую конкуренцию с компанией Google, которая также спешит внедрить функции искусственного интеллекта в свою поисковую систему

Не успеваете составить отчеты и собрались временно сдать нулевки? Будьте осторожны! «Ночной бухгалтер» № 1732

Если бухгалтер не успевает вовремя сформировать правильную отчетность, то может показаться, что хороший вариант — сдать нулевки, а чуть позже отправить уже корректировочные отчеты. Узнали, как будем отдыхать в 2025 году, грозит ли нейросеть стать конкурентом бухгалтеру и что происходит с YouTube.

Иллюстрация: создано с помощью ИИ OpenAI © Вера Ревина/Клерк.ру
Пенсии

Законодатели взялись за многодетных отцов-одиночек

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

Курсы повышения
квалификации

18
Официальное удостоверение с занесением в госреестр Рособрнадзора
Законопроекты

Россияне смогут установить самозапрет на ставки

Жители страны смогут установить запрет на участие в азартных играх на 4 и 12 месяцев. От таких клиентов букмекеры не смогут принимать ставки и предлагать им фишки в казино.

Маркетинг

Рынок блогосферы восстановился

Большинство рекламных агентств расшили штат сотрудников и повысили зарплаты на 13%.

Как строить карьеру в ИТ через эффективный нетворкинг

Как строить карьеру в ИТ через эффективный нетворкинг
Опытом делятся эксперты-практики, без воды
Импорт

Финансовое сопровождение импортеров стало самой востребованной услугой ВЭД в первом полугодии 2024

Участники внешнеэкономической деятельности выбирают компании с официальным представительством в Китае, чтобы избежать проблем с оплатой поставок.

До конца недели скорость загрузки YouTube на стационарных компьютерах может снизиться до 40%, а к концу следующей — уже до 70%

Мера затронет только десктопную версию. Мобильную замедлять пока не будут

Бесплатно с ФСБУ Документооборот

ФСБУ 27/2021 «Документы и документооборот в бухгалтерском учете»

Кто должен применять ФСБУ 27/2021, какие требования предъявляются к бухгалтерским документам и документообороту, как правильно составить документ и исправить ошибку.

ФСБУ 27/2021 «Документы и документооборот в бухгалтерском учете»
Реклама

Депутаты выступили против рекламы на запрещенных ресурсах

Ответственность за нарушение закона будут нести и рекламодатели, и распространители сообщений.

Поручение

Как составить договор поручения: суть, условия и образец

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

Как составить договор поручения: суть, условия и образец
Маркетплейсы

Маркетплейсы будут размещать ссылки на сертификаты качества

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

НДС

Все коды видов операций по НДС: таблица

Чтобы отчетность по НДС была корректной, нужно верно отражать данные о действиях, произведенных с этим налогом. Такие действия иначе называют операциями по НДС. Они должны быть документально зафиксированы. Для этого используется специальное обозначение в виде кода вида операции или сокращенно КВО. В материале повествуется о том, что это такое, как оно применяется и какие недавно произошли изменения.

Все коды видов операций по НДС: таблица

Нейросеть для бухгалтеров или психиатр для нейросети

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

Нейросеть для бухгалтеров или психиатр для нейросети
2
Учет в торговле

Товар просрочен и подлежит утилизации: как учитывать расходы

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

Товар просрочен и подлежит утилизации: как учитывать расходы
Криптовалюта

О подозрительных операциях нотариусы и майнеры будут сообщать в Росфинмониторинг

Майнеров криптовалют и нотариусов обязали соблюдать антиотмывочный закон 115-ФЗ.

Законопроекты

Застройщиков обяжут рядом с ЖК возводить школы

Вместе с жилищными комплексами застройщики будут стоить объекты социальной и транспортной инфраструктуры.

Обзоры новостей

⚡️ Итоги дня: ФАС раскрыла картель на торгах соцтоварами, активисты приклеили себя к взлетной полосе, а спортсмены жалуются на еду в Олимпийской деревне

Подготовили обзор главных событий дня — 25 июля 2024 года. Все самое интересное, что писали и обсуждали в сети, в одной подборке.

Банки

У ВТБ появился сервис покупки автомобилей частями

Сервис «ВТБ Авто» позволяет разделить покупку машины на части: сначала взять авто в аренду, а в конце срока выкупить его по остаточной стоимости.

Интересные материалы

Штрафы ГАИ

Сервисы по аренде электросамокатов наказали нарушителей больше 400 тысяч раз

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