using System.Globalization; using ApiClient; using ApiClient.Grpc; using ApiClient.Http; using ConsoleApp.Data; using ConsoleApp.Logging; using Domain.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; var configuration = new ConfigurationBuilder() .SetBasePath(AppContext.BaseDirectory) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: false) .AddEnvironmentVariables() .Build(); var connectionString = configuration.GetConnectionString("Default") ?? throw new InvalidOperationException("Connection string 'Default' не задана."); var httpOptions = configuration.GetSection("Http").Get() ?? new(); var grpcOptions = configuration.GetSection("Grpc").Get() ?? new(); using var log = ConsoleLog.Open(); var smsClient = AskSmsClient(log, httpOptions, grpcOptions); try { log.WriteLine("Инициализация базы данных..."); await using var db = CreateDbContext(connectionString); await db.Database.MigrateAsync(); log.WriteLine("База данных готова."); log.WriteLine("Запрос меню с сервера..."); var menuResponse = await smsClient.GetMenuAsync(withPrice: true); if (!menuResponse.Success) { log.WriteLine(menuResponse.ToString()); return; } var dishes = menuResponse.Data?.MenuItems ?? []; await SaveDishesAsync(db, dishes); log.WriteLine("Меню:"); foreach (var dish in dishes) { log.WriteLine(dish.ToString()); } var order = ReadOrder(log, dishes); log.WriteLine(order.ToString()); log.WriteLine("Отправка заказа на сервер..."); var sendResponse = await smsClient.SendOrderAsync(order); if (sendResponse.Success) { log.WriteLine("УСПЕХ"); return; } log.WriteLine(sendResponse.ToString()); } finally { (smsClient as IDisposable)?.Dispose(); log.WriteLine($"Файл лога (запуск {log.StartedAt:yyyy-MM-dd HH:mm:ss}): {log.FilePath}"); } static ISmsClient AskSmsClient(ConsoleLog log, HttpSmsClientOptions http, GrpcSmsClientOptions grpc) { log.WriteLine("Выберите протокол: 1 — HTTP, 2 — gRPC"); while (true) { var input = log.ReadLine("> ")?.Trim(); if (input is "1" or "http" or "Http" or "HTTP") { return SmsClientFactory.CreateHttp(http); } if (input is "2" or "grpc" or "Grpc" or "gRPC") { return SmsClientFactory.CreateGrpc(grpc); } log.WriteLine("Введите 1 (HTTP) или 2 (gRPC)."); } } static AppDbContext CreateDbContext(string connectionString) { var options = new DbContextOptionsBuilder() .UseNpgsql(connectionString) .UseSnakeCaseNamingConvention() .Options; return new AppDbContext(options); } static async Task SaveDishesAsync(AppDbContext db, IReadOnlyList dishes) { await db.Dishes.ExecuteDeleteAsync(); db.Dishes.AddRange(dishes.Select(d => new MenuDish { Id = d.Id, Article = d.Article, Name = d.Name, Price = d.Price, IsWeighted = d.IsWeighted, FullPath = d.FullPath, Barcodes = d.Barcodes.ToList(), })); await db.SaveChangesAsync(); } static Order ReadOrder(ConsoleLog log, IReadOnlyList dishes) { var dishesByArticle = dishes.ToDictionary(d => d.Article, StringComparer.OrdinalIgnoreCase); log.WriteLine(); log.WriteLine("Введите позиции заказа в формате Код:Количество;Код:Количество;..."); while (true) { var input = log.ReadLine("> "); if (!TryParseOrderInput(input, out var lines, out var parseError)) { log.WriteLine(parseError); continue; } var unknownArticles = lines .Select(line => line.Article) .Where(article => !dishesByArticle.ContainsKey(article)) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); if (unknownArticles.Count > 0) { log.WriteLine($"Неизвестные коды: {string.Join(", ", unknownArticles)}"); continue; } Order order = new(); foreach (var (article, quantity) in lines) { order = order.AddItem(dishesByArticle[article].Id, quantity); } return order; } } static bool TryParseOrderInput( string? input, out List<(string Article, decimal Quantity)> items, out string errorMessage) { items = []; if (string.IsNullOrWhiteSpace(input)) { errorMessage = "Строка заказа не может быть пустой."; return false; } var parts = input.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); foreach (var part in parts) { var pair = part.Split(':', 2); if (pair.Length != 2) { errorMessage = $"Некорректный формат позиции: '{part}'. Ожидается Код:Количество."; return false; } var article = pair[0].Trim(); if (string.IsNullOrWhiteSpace(article)) { errorMessage = "Артикул не может быть пустым."; return false; } if (!decimal.TryParse(pair[1].Trim(), NumberStyles.Number, CultureInfo.InvariantCulture, out var quantity) && !decimal.TryParse(pair[1].Trim(), NumberStyles.Number, CultureInfo.CurrentCulture, out quantity)) { errorMessage = $"Некорректное количество для артикула '{article}'."; return false; } if (quantity <= 0) { errorMessage = $"Количество для артикула '{article}' должно быть больше нуля."; return false; } items.Add((article, quantity)); } errorMessage = ""; return true; }