Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/ivan816/simple-1c

Транслятор запросов и Linq-провайдер для 1С-Бухгалтерии
https://github.com/ivan816/simple-1c

1c csharp linq sql

Last synced: about 2 months ago
JSON representation

Транслятор запросов и Linq-провайдер для 1С-Бухгалтерии

Awesome Lists containing this project

README

        

#Simple1C

*This project is licensed under the terms of the MIT license.*

Упрощаем интеграцию с 1С за счет двух основных функций:

* умеем исполнять обычные запросы языка 1С (`выбрать * из Справочник.Контрагенты`)
без участия самой 1С - напрямую через СУБД. Это может быть удобно при использовании
[механизма разделения данных](http://v8.1c.ru/overview/Term_000000788.htm) чтобы
выполнить запрос сразу на всех [областях](http://v8.1c.ru/overview/Term_000000790.htm)
информационной базы или даже на нескольких информационных базах, если
используется более одного сервера СУБД. Такая возможность становится особенно актуальной при
большом числе различных организаций, работающих с одной типовой конфигурацией,
например через технологию [1C-фреш](http://v8.1c.ru/fresh/whatis.htm).

* если есть потребность заинтегрировать с 1С внешнее приложение, и это
приложение написано на .NET, то одним из вариантов может быть использование 1С COM апи.
Это апи основано на интерфейсе [IDispatch](https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms221608(v=vs.85).aspx)
и позднем связывании, поэтому любая опечатка в имени метода или свойства приводит к ошибке во время выполнения.
Мы решаем эту проблему путем генерации оберточных классов для всех объектов конфигурации. Эти
классы можно использовать как для сохранения новых данных, так и для выполнения запросов в
стиле [LINQ](https://www.google.ru/url?sa=t&rct=j&q=&esrc=s&source=web&cd=2&cad=rja&uact=8&ved=0ahUKEwili-f_2tfPAhVICiwKHaHPB7QQFggnMAE&url=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FLanguage_Integrated_Query&usg=AFQjCNEjvId7H8I66uhYbueZxwszChwoyg&bvm=bv.135475266,d.bGg).

##NuGet

Для установки [Simple1C пакета](https://www.nuget.org/packages/Simple1C),
выполните следующую команду в [NuGet-консоли](http://docs.nuget.org/docs/start-here/using-the-package-manager-console)

PM> Install-Package Simple1C

##Simple1C.SQL

Преобразуем запрос в формате языка запросов 1С в чистый sql и исполняем его на реальной СУБД.
Используем [Irony](https://irony.codeplex.com) для синтаксического анализа и
построения [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree).

На практике работает это следующим образом. Первым делом с помощью команды `gen-sql-meta`
достаем через COM метаданные по объектам конфигурации и сохраняем их
в отдельную [схему](https://www.postgresql.org/docs/9.1/static/ddl-schemas.html) `simple1c` в sql базе данных
(пока поддерживается только PostgreSQL).
```
Generator.exe
-cmd gen-sql-meta
-connection-string <будет передан методу Connect объекта V83.COMConnector>
-db-connection-string <для подключения к sql СУБД>
```
Эти метаданные нужны в первую очередь для установления соответствия между именами объектов и реквизитов
конфигурации 1С и именами таблиц и колонок в БД. Затем для выполнения запроса
используем команду `run-sql` следующим образом:
```
Generator.exe
-cmd run-sql
-connection-strings <на какой базе выполнять запрос, можно передать несколько через запятую>
-query-file <имя файла с запросом>
-result-connection-string <куда поместить результат>
-dump-sql true <опционально, выводит фактически исполняемый sql-запрос для отладки>
```
Результат выполнения запроса помещается в таблицу в базе, указанной параметром result-connection-string.
Пока поддерживается только MS SQL Server в качестве такой базы. Имя таблицы берется из имени файла с запросом.
Основные возможности:

* Поддерживается большая часть конструкций языка - все стандартные элементы sql (подзапросы, union, group by, order by и т.п.),
операторы Значение и Ссылка, функции ПРЕДСТАВЛЕНИЕ, ДАТАВРЕМЯ, ГОД, КВАРТАЛ и другие.

* Поддерживается синтаксис для доступа к вложенным реквизитам через точку
```
select ДоговорКонтрагента.Наименование from Документ.ПоступлениеТоваровУслуг
where Контрагент.ГоловнойКонтрагент.ИНН = "7710967300"
```
При трансляции в sql к такому запросу будут добавлены необходимые цепочки join-ов на справочники
контрагентов и договоров.

* Можно использовать как русский, так и аглийский варианты синтаксиса - `select * from Справочник.Контрагенты`
эквиваленто `выбрать * из Справочник.Контрагенты`.

* Если вы используете 1С-Фреш и у вас несколько sql баз, то можно передать их все в параметре connection-strings.
В этом случае запрос будет выпоняться параллельно на всех базах, а все результаты будут объеденены в одной общей таблице.
Можно потом, например, через SQL Server Management Studio применить group by к этой таблице и получить
группировку данных между различными базами.

* Чтобы понять, какие объекты и реквизиты можно использовать в запросе, можно посмотреть в сгенерированные командой gen-sql-meta
метаданные в схеме `simple1c`, а именно в таблицу tableMappings. В ней есть имена объектов, имена
соответствующих им таблиц и такое же соответствие для реквизитов и колонок в таблицах.

* Оператор `Ссылка` можно использовать для ускорения выполнения запроса. Дело в том, что в 1С
реквизит может не иметь фиксированного статического типа, а допускать сразу несколько типов.
Основной пример - это реквизит субконто, который для разных счетов может ссылается на разные
объекты конфигурации: Контрагент, Статья учета и т.п. Если в запросе
есть обращение через точку к свойствам такого реквизита (`doc.Субконто1.Наименование`), то
единственный вариант получить нужные данные - это заджойниться на все возможные таблицы,
в которых есть свойство `Наименование`. Если в запросе имеется в виду какой-то конкретный
тип субконто, то можно воспользоваться оператором `Ссылка` следующим образом:
```
select * from Документ.ПоступлениеНаРасчетныйСчет where Субконто1 Ссылка Справочник.Контрагенты
```
В этом примере будет сгенерирован только один джойн - с таблицей контрагентов, что существенно быстрее.

* В схеме `simple1c` есть пара функций для работы с Guid-ами. `to_guid` преобразует байтовый массив,
в котором 1С сохраняет ссылки между объектами, в стандартное строковое представление guid-а.
`date_from_guid` получает дату из строкового представления guid-а. Так как 1С генерирует guid-ы на основе
текущего времени, по этой дате можно судить о том, когда был создан объект с данным идентификатором.
Эти функции можно использовать в любой части запроса, аналогично стандартным функциям языка запросов 1С.

* Можно получить данные по остаткам и оборотам аналогично стандартному отчету ОСВ в 1С. Для этого
доступны четыре таблицы. `РегистрБухгалтерии.Хозрасчетный.Остатки` - здесь лежат помесячные сальдо и обороты без субконто,
`РегистрБухгалтерии.Хозрасчетный.Субконто1`, `РегистрБухгалтерии.Хозрасчетный.Субконто2`, и
`РегистрБухгалтерии.Хозрасчетный.Субконто3` - здесь тоже самое, только с субконто.
Каждая транзакция по счету учитывается в остатках и в одной из таблиц Субконто,
в зависимости от количества субконт по данному счету.

Вот [статья на Хабре](https://habrahabr.ru/company/knopka/blog/314030/) о том, как все это работает.

##Simple1C.COM

Несмотря на то, что еще в 2013-м году 1С опубликовал REST апи
поверх протокола OData ([раз](https://wonderland.v8.1c.ru/blog/avtomaticheskiy-rest-interfeys-prikladnykh-resheniy),
[два](https://wonderland.v8.1c.ru/blog/rasshirenie-podderzhki-protokola-odata)),
многим и сегодня по разным причинам приходится использовать для интеграции
старые COM-интерфейсы. Мы упрощаем использование этих интерфейсов
за счет автоматической генерации классов по метаданным 1С
и реализации [LINQ-провайдера](https://msdn.microsoft.com/en-us/library/bb308959.aspx).
Используем C# как основной язык для автогенерации.
Механику провайдера упрощаем за счет [вот этой](https://relinq.codeplex.com) классной штуки.
Аналогичные проекты: http://www.linq-demo.1csoftware.com, http://www.vanessa-sharp.ru/reference-linq.html.
Основные возможности:

* Автоматическая генерация классов объектной модели по 1С-конфигурации :
```
Generator.exe
-cmd gen-cs-meta
-connection-string File=C:\my-1c-db;Usr=Администратор;Pwd=
-namespace-root MyNameSpace.ObjectModel1C
-scan-items Документ.СписаниеСРасчетногоСчета
-source-path C:\sources\AwesomeSolution\ObjectModel1CProject\AutoGenerated
```
Такая команда создаст класс СписаниеСРасчетногоСчета в папке Документы.
Этот класс будет обладать всеми свойствами исходного 1С-документа
СписаниеСРасчетногоСчета, включая все табличные части. По классу так же будет создано
для каждого из объектов конфигурации, на которые эти свойства
ссылаются (транзитивное замыкание).
Основные возможности:

* Для имен генерируемых классов используем исходные русскоязычные идентификаторы из 1С.
За счет этого модель становится проще - нет необходимости придумывать англоязычные
аналоги для весьма специфических терминов 1С, одни и те же вещи называются одинаково
по всей кодовой базе.

* Не навязываем механизм управления коннекциями. Здесь возможны различные варианты
(ThreadLocal, Pool, пересоздание по таймауту, пересоздание при обрыве и т.п.),
многое зависит от приложения и конкретных потребностей. Простейший пример чтобы начать работать:

```csharp
var connectorType = Type.GetTypeFromProgID("V83.COMConnector");
dynamic connector = Activator.CreateInstance(connectorType);
var globalContext = connector.Connect("File=C:\my-1c-db;Usr=Администратор;Pwd=");
var dataContext = DataContextFactory.CreateCOM(globalContext, typeof(Контрагенты).Assembly);
var контрагент = new Контрагенты
{
ИНН = "1234567890",
Наименование = "test-counterparty",
ЮридическоеФизическоеЛицо = ЮридическоеФизическоеЛицо.ЮридическоеЛицо
};
dataContext.Save(контрагент);
var контрагент2 = dataContext.Select<Контрагенты>().Single(x => x.Код == counterparty.Код);
Assert.That(контрагент2.ИНН, Is.EqualTo("1234567890"));
```

* Документы и справочники Generator.exe превращает в классы, а перечисления - в enum-ы языка C#.
В примере выше для создания контрагента мы используем enum ЮридическоеФизическоеЛицо.

* Абстрагируем ссылки, про них в прикладном коде можно просто
забыть - создаем экемпляры классов объектной модели, присваиваем их свойствам других классов,
при маппинге на соответствующие COM-объекты ссылки между ними будут проставлены автоматически.

```csharp
var контрагент = new Контрагенты
{
ИНН = "1234567890",
Наименование = "test-counterparty",
ЮридическоеФизическоеЛицо = ЮридическоеФизическоеЛицо.ЮридическоеЛицо
};
dataContext.Save(контрагент);
var организация = dataContext.Select<Организация>().Single();
var договор = new ДоговорыКонтрагентов
{
ВидДоговора = ВидыДоговоровКонтрагентов.СПокупателем,
Наименование = "test name",
Владелец = контрагент,
Организация = организация
};
dataContext.Save(договор);
```

* Метод Save сохраняет все объекты, до которых может добраться по ссылкам. Пример выше можно
упростить следующим образом

```
dataContext.Save(new ДоговорыКонтрагентов
{
ВидДоговора = ВидыДоговоровКонтрагентов.СПокупателем,
Наименование = "test name",
Владелец = new Контрагенты
{
ИНН = "1234567890",
Наименование = "test-counterparty",
ЮридическоеФизическоеЛицо = ЮридическоеФизическоеЛицо.ЮридическоеЛицо
},
Организация = dataContext.Select<Организация>().Single()
});
```

* В запросах можно использовать стандартные Linq-операторы (Join, GroupBy пока не реализованы)

```csharp
public decimal GetCurrencyRateToDate(string currencyCode, DateTime date)
{
return dataContext.Select<КурсыВалют>()
.Where(x => x.Валюта.Код == currencyCode)
.Where(x => x.Период <= date)
.OrderByDescending(x => x.Период)
.First()
.Курс;
}
```

* Умеем обновлять отдельные строки табличных частей. Так, например, в таком примере

```csharp
var поступлениеТоваровУслуг = new ПоступлениеТоваровУслуг
{
Дата = new DateTime(2016, 6, 1),
Контрагент = new Контрагенты
{
ИНН = "7711223344",
Наименование = "ООО Тестовый контрагент",
},
Услуги = new List<ПоступлениеТоваровУслуг.ТабличнаяЧастьУслуги>
{
new ПоступлениеТоваровУслуг.ТабличнаяЧастьУслуги
{
Номенклатура = new Номенклатура
{
Наименование = "стрижка"
},
Количество = 10,
Содержание = "стрижка с кудряшками"
},
new ПоступлениеТоваровУслуг.ТабличнаяЧастьУслуги
{
Номенклатура = new Номенклатура
{
Наименование = "стрижка усов"
},
Количество = 10,
Содержание = "стрижка бороды"
}
};
dataContext.Save(поступлениеТоваровУслуг);
var t = поступлениеТоваровУслуг.Услуги[0];
поступлениеТоваровУслуг.Услуги[0] = поступлениеТоваровУслуг.Услуги[1];
поступлениеТоваровУслуг.Услуги[1] = t;
dataContext.Save(поступлениеТоваровУслуг);
```

на втором вызове Save будет сгенерирован единственный вызов метода `Сдвинуть`, меняющий
местами две строки.

* Через метод DataContextFactory.CreateInMemory() можно получить inmemory-реализацию
интерфейса IDataContext. Это здорово помогает при автоматизированном тестировании
безнес-логики. Весь граф сервисных объектов поверх IDataContext можно создать
целиком в inmemory-режиме, значительно ускорив этим прохождение тестов по сравнению
с работой с настоящей 1С.