Упрощён клиент

This commit is contained in:
2026-06-04 13:28:22 +03:00
parent 1b85ec5ce8
commit 6e6831c232
11 changed files with 273 additions and 306 deletions

View File

@@ -1,8 +1,13 @@
using ConsoleApp;
using System.Globalization;
using ApiClient;
using ApiClient.Grpc;
using ApiClient.Http;
using ConsoleApp.Data;
using ConsoleApp.Logging;
using ConsoleApp.Services;
using Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Npgsql;
var configuration = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
@@ -10,12 +15,222 @@ var configuration = new ConfigurationBuilder()
.AddEnvironmentVariables()
.Build();
using var _ = ConsoleLog.Configure();
var connectionString = configuration.GetConnectionString("Default")
?? throw new InvalidOperationException("Connection string 'Default' не задана.");
var services = new ServiceCollection();
services.AddSingleton<IConfiguration>(configuration);
services.AddConsoleApp(configuration);
var httpOptions = configuration.GetSection("Http").Get<HttpSmsClientOptions>() ?? new();
var grpcOptions = configuration.GetSection("Grpc").Get<GrpcSmsClientOptions>() ?? new();
await using var provider = services.BuildServiceProvider();
await using var scope = provider.CreateAsyncScope();
await scope.ServiceProvider.GetRequiredService<ConsoleAppRunner>().RunAsync(CancellationToken.None);
using var log = ConsoleLog.Open();
var smsClient = AskSmsClient(log, httpOptions, grpcOptions);
try
{
log.WriteLine("Инициализация базы данных...");
await using var db = CreateDbContext(connectionString);
await EnsureDatabaseAsync(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);
if (order is null)
{
return;
}
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<AppDbContext>()
.UseNpgsql(connectionString)
.UseSnakeCaseNamingConvention()
.Options;
return new AppDbContext(options);
}
static async Task EnsureDatabaseAsync(string connectionString)
{
var builder = new NpgsqlConnectionStringBuilder(connectionString);
var databaseName = builder.Database
?? throw new InvalidOperationException("Имя базы данных не указано в строке подключения.");
builder.Database = "postgres";
await using var connection = new NpgsqlConnection(builder.ConnectionString);
await connection.OpenAsync();
await using var checkCommand = connection.CreateCommand();
checkCommand.CommandText = "SELECT 1 FROM pg_database WHERE datname = @name";
checkCommand.Parameters.AddWithValue("name", databaseName);
if (await checkCommand.ExecuteScalarAsync() is not null)
{
return;
}
await using var createCommand = connection.CreateCommand();
createCommand.CommandText = $"CREATE DATABASE \"{databaseName.Replace("\"", "\"\"")}\"";
await createCommand.ExecuteNonQueryAsync();
}
static async Task SaveDishesAsync(AppDbContext db, IReadOnlyList<Dish> 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<Dish> 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;
}