diff --git a/1CDataBus.sln b/1CDataBus.sln new file mode 100644 index 0000000..e090786 --- /dev/null +++ b/1CDataBus.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34728.123 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "1CDataBus", "1CDataBus\1CDataBus.csproj", "{B0551536-536F-4719-96E8-56848F4F4F26}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B0551536-536F-4719-96E8-56848F4F4F26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B0551536-536F-4719-96E8-56848F4F4F26}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B0551536-536F-4719-96E8-56848F4F4F26}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B0551536-536F-4719-96E8-56848F4F4F26}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {19101D4F-6085-422C-A340-C3DFEEDD930D} + EndGlobalSection +EndGlobal diff --git a/1CDataBus/1CDataBus.csproj b/1CDataBus/1CDataBus.csproj new file mode 100644 index 0000000..1ae6714 --- /dev/null +++ b/1CDataBus/1CDataBus.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + _1CDataBus + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/1CDataBus/1CDataBus.http b/1CDataBus/1CDataBus.http new file mode 100644 index 0000000..db3904f --- /dev/null +++ b/1CDataBus/1CDataBus.http @@ -0,0 +1,6 @@ +@_1CDataBus_HostAddress = http://localhost:5043 + +GET {{_1CDataBus_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/1CDataBus/Controllers/ContractsController.cs b/1CDataBus/Controllers/ContractsController.cs new file mode 100644 index 0000000..18ca13d --- /dev/null +++ b/1CDataBus/Controllers/ContractsController.cs @@ -0,0 +1,72 @@ +using _1CDataBus.RabbitMQ; +using _1CDataBus.Structure; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Simple.OData.Client; +using System.Text; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace _1CDataBus.Controllers +{ + [ApiController] + [Route("[controller]")] + public class ContractsController : ControllerBase + { + private readonly IConfiguration _config; + private readonly ILogger _logger; + private RabbitConnection _rabbit; + private ODataClient _client; + + public ContractsController(ILogger logger, IConfiguration config) + { + _config = config; + _logger = logger; + _rabbit = new RabbitConnection(_config); + var odata = new ODataAccess(_config); + _client = odata.Client; + } + + [HttpPost("SendContractFromQDoc")] + public async Task SendContractFromQDoc(long id) + { + var contract = await _client + .For("IContract") + .Key(id) + .Expand("BusinessUnit") + .Expand("Counterparty") + .Expand("Currency") + .FindEntryAsync(); + var sendContract = new Contract(contract); + await SendContractTo1C(sendContract); + } + + [HttpPost("SendContract")] + public async Task SendContractTo1C(Contract contract) + { + JsonSerializerOptions options = new() + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + WriteIndented = true + }; + var serialized = JsonSerializer.Serialize(contract, options); + await _rabbit.SendMessage(serialized); + } + + [HttpGet("GetContract")] + public async Task GetContract(long contractId) + { + var contract = await _client + .For("IContracts") + .Expand("Versions/Body") + .Key(contractId) + .FindEntryAsync(); + var versions = (IEnumerable)contract["Versions"]; + var lastVersion = versions.OrderByDescending(x => x["Number"]).FirstOrDefault(); + byte[] bytes = lastVersion["Body"]["Value"]; + var base64 = Convert.ToBase64String(bytes); + return base64; + } + } +} diff --git a/1CDataBus/Controllers/WeatherForecastController.cs b/1CDataBus/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000..938ce49 --- /dev/null +++ b/1CDataBus/Controllers/WeatherForecastController.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Mvc; + +namespace _1CDataBus.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} diff --git a/1CDataBus/Program.cs b/1CDataBus/Program.cs new file mode 100644 index 0000000..48863a6 --- /dev/null +++ b/1CDataBus/Program.cs @@ -0,0 +1,25 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/1CDataBus/Properties/launchSettings.json b/1CDataBus/Properties/launchSettings.json new file mode 100644 index 0000000..de90f50 --- /dev/null +++ b/1CDataBus/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:54967", + "sslPort": 44384 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5043", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7130;http://localhost:5043", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/1CDataBus/RabbitMQ/RabbitConnection.cs b/1CDataBus/RabbitMQ/RabbitConnection.cs new file mode 100644 index 0000000..5259f88 --- /dev/null +++ b/1CDataBus/RabbitMQ/RabbitConnection.cs @@ -0,0 +1,48 @@ +using RabbitMQ.Client; +using System.Text; + +namespace _1CDataBus.RabbitMQ +{ + public class RabbitConnection : IDisposable + { + private readonly IConnection _connection; + private readonly IChannel _channel; + private IConfiguration _configuration; + public RabbitConnection(IConfiguration configuration) + { + _configuration = configuration; + var factory = new ConnectionFactory + { + HostName = _configuration.GetValue("RabbitMqSettings:HostName"), + //HostName = "astsrvrabbit1", + UserName = _configuration.GetValue("RabbitMqSettings:UserName"), + //UserName = "erp", + Password = _configuration.GetValue("RabbitMqSettings:Password"), + //Password = @"Z!1;Q5#GE4v", + VirtualHost = _configuration.GetValue("RabbitMqSettings:VirtualHost") + //VirtualHost = "erp" + }; + + _connection = factory.CreateConnectionAsync().Result; + _channel = _connection.CreateChannelAsync().Result; + } + + public async Task SendMessage(string body) + { + await _channel.QueueDeclareAsync( + queue: "erp_test", + durable: false, + exclusive: false, + autoDelete: false, + arguments: null); + var encoded = Encoding.UTF8.GetBytes(body); + await _channel.BasicPublishAsync(string.Empty, "erp_test", encoded); + } + + public void Dispose() + { + _channel.Dispose(); + _connection.Dispose(); + } + } +} diff --git a/1CDataBus/Structure/Contract.cs b/1CDataBus/Structure/Contract.cs new file mode 100644 index 0000000..f81f181 --- /dev/null +++ b/1CDataBus/Structure/Contract.cs @@ -0,0 +1,39 @@ +namespace _1CDataBus.Structure +{ + public class Contract + { + private Dictionary _contractKinds = new Dictionary () + { + { 1, "Поставщик" }, + { 2, "Покупатель" }, + { 3, "Прочее" } + }; + + public Contract(dynamic contract) + { + Name = contract["Name"]; + BusinessUnit = contract["BusinessUnit"]["ExternalId"]; + BusinessUnitBIN = contract["BusinessUnit"]["BINArmadoc"]; + Counterparty = contract["Counterparty"]["ExternalId"]; + CounterpartyBIN = contract["Counterparty"]["BINArmadoc"]; + CreateDate = contract["Created"].Date; + Number = contract["RegistrationNumber"]; + Date = contract["ContractDateSungero"].Date; + Currency = contract["Currency"]["AlphaCode"]; + QDocID = contract["Id"]; + } + + public string Name { get; set; } + public string BusinessUnit { get; set; } + public string BusinessUnitBIN { get; set; } + public string Counterparty { get; set; } + public string CounterpartyBIN { get; set; } + public DateTimeOffset CreateDate { get; set; } = DateTimeOffset.Now; + public string Number { get; set; } + public DateTimeOffset Date { get; set; } + public string ContractKind { get; set; } = "Поставщик"; + public string Currency { get; set; } + public bool Orders { get; set; } = false; + public long QDocID { get; set; } + } +} diff --git a/1CDataBus/Structure/ODataAccess.cs b/1CDataBus/Structure/ODataAccess.cs new file mode 100644 index 0000000..79eee7d --- /dev/null +++ b/1CDataBus/Structure/ODataAccess.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.Configuration; +using Simple.OData.Client; +using System.Text; + +namespace _1CDataBus.Structure +{ + public class ODataAccess + { + private IConfiguration _configuration; + + public ODataAccess(IConfiguration configuration) + { + _configuration = configuration; + var section = _configuration.GetSection("QDocSettings"); + + var IntegrationServiceUrl = section.GetValue("Url"); + var Login = section.GetValue("Login"); + var Password = section.GetValue("Password"); + + // Настройки Simple OData Client: добавление ко всем запросам URL сервиса и + // заголовка с данными аутентификации. + var odataClientSettings = new ODataClientSettings(new Uri(IntegrationServiceUrl)); + odataClientSettings.BeforeRequest += (HttpRequestMessage message) => + { + var authenticationHeaderValue = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Login}:{Password}")); + message.Headers.Add("Authorization", "Basic " + authenticationHeaderValue); + }; + Client = new ODataClient(odataClientSettings); + } + + public ODataClient? Client { get; set; } + } +} diff --git a/1CDataBus/WeatherForecast.cs b/1CDataBus/WeatherForecast.cs new file mode 100644 index 0000000..bc308de --- /dev/null +++ b/1CDataBus/WeatherForecast.cs @@ -0,0 +1,13 @@ +namespace _1CDataBus +{ + public class WeatherForecast + { + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } + } +} \ No newline at end of file diff --git a/1CDataBus/appsettings.Development.json b/1CDataBus/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/1CDataBus/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/1CDataBus/appsettings.json b/1CDataBus/appsettings.json new file mode 100644 index 0000000..4097c27 --- /dev/null +++ b/1CDataBus/appsettings.json @@ -0,0 +1,25 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "RabbitMqSettings": { + "HostName": "astsrvrabbit1", + "UserName": "erp", + "Password": "Z!1;Q5#GE4v", + "VirtualHost": "erp" + }, + "QDocSettings": { + "Url": "https://qdoc.solidcore-resources.com/Integration/odata/", + "Login": "Administrator", + "Password": "MQVuEw9avO" + }, + "QDocTestSettings": { + "Url": "https://astsrvqtest.solidcore-resources.com/Integration/odata/", + "Login": "Administrator", + "Password": "D3cTXol8Se" + } +} diff --git a/1CDataBus/Справка по сервису 1CDataBus.docx b/1CDataBus/Справка по сервису 1CDataBus.docx new file mode 100644 index 0000000..adbc223 --- /dev/null +++ b/1CDataBus/Справка по сервису 1CDataBus.docx @@ -0,0 +1,25 @@ +Сервис предназначен для обмена договорами с 1С и QDoc. +POST-запросы: +https://astsrvqtest02.polymetal.ru:9812/Contracts/SendContractFromQDoc?id=2274 +Отправляет в очередь сериализованный договор из QDoc по идентификатору договора. +https://astsrvqtest02.polymetal.ru:9812/Contracts/SendContract +Отправляет в очередь JSON-структуру представляющую договор. +Образец: +{ + "name": "string", + "businessUnit": "string", + "businessUnitBIN": "string", + "counterparty": "string", + "counterpartyBIN": "string", + "createDate": "2025-02-24T11:27:09.476Z", + "number": "string", + "date": "2025-02-24T11:27:09.476Z", + "contractKind": "string", + "currency": "string", + "orders": true, + "qDocID": 0 +} + +GET-запросы: +https://astsrvqtest02.polymetal.ru:9812/Contracts/GetContract?contractId=2274 +Возвращает из QDoc последнюю версию файла договорного документа по идентификатору договора в виде строки Base64