using System; using System.Data.SQLite; using System.Net.Http; using System.Net.Http.Headers; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; using Электронная_Фармация.Properties; namespace Электронная_Фармация.Classes { /// /// Глобальные номера заказов вида EX-0000001. /// Источник истины — сервер (никогда не повторяются между аптеками). /// Offline fallback — локальный счётчик EX- (потом сервер может заменить при отправке). /// public static class OrderNumberHelper { private static readonly Regex ExPattern = new Regex(@"^EX-(\d{1,7})$", RegexOptions.IgnoreCase | RegexOptions.Compiled); /// /// Выделяет номер для нового локального заказа. /// Сначала пробует сервер /api/buyer/global-sign/next, иначе локальный EX-#######. /// public static string AllocateForNewOrder() { try { var fromServer = AllocateFromServerAsync().GetAwaiter().GetResult(); if (!string.IsNullOrWhiteSpace(fromServer)) { return fromServer; } } catch (Exception ex) { AppDebugLog.Info("OrderNumber", $"Сервер недоступен для GlobalSign: {ex.Message}"); } return AllocateLocalFallback(); } public static async Task AllocateFromServerAsync() { var token = Settings.Default.stringToken; var baseUrl = AppConfig.ApiBaseUrl; if (string.IsNullOrWhiteSpace(token) || string.IsNullOrWhiteSpace(baseUrl)) { return null; } using (var client = new HttpClient { Timeout = TimeSpan.FromSeconds(8) }) { client.BaseAddress = new Uri(baseUrl.TrimEnd('/') + "/"); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Trim()); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); using (var response = await client.PostAsync("api/buyer/global-sign/next", new StringContent(string.Empty)).ConfigureAwait(false)) { var body = await response.Content.ReadAsStringAsync().ConfigureAwait(false); if (!response.IsSuccessStatusCode) { AppDebugLog.Info("OrderNumber", $"global-sign/next HTTP {(int)response.StatusCode}: {body}"); return null; } using (var doc = JsonDocument.Parse(body)) { if (doc.RootElement.TryGetProperty("global_sign", out var el)) { var sign = el.GetString(); if (IsValidEx(sign)) { return Normalize(sign); } } } } } return null; } public static string AllocateLocalFallback() { long max = 0; try { using (var con = new SQLiteConnection(AppConfig.SqliteConnectionString)) { con.Open(); using (var cmd = new SQLiteCommand( "SELECT OrderNumber FROM Orders WHERE OrderNumber LIKE 'EX-%'", con)) using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { var n = ParseExNumber(reader[0]?.ToString()); if (n > max) { max = n; } } } } } catch (Exception ex) { AppDebugLog.Info("OrderNumber", $"Локальный max EX-: {ex.Message}"); } return FormatEx(max + 1); } public static bool IsValidEx(string value) { return ParseExNumber(value) > 0; } public static string Normalize(string value) { var n = ParseExNumber(value); return n > 0 ? FormatEx(n) : value?.Trim(); } public static string FormatEx(long n) { if (n < 1) { n = 1; } if (n > 9999999) { return "EX-" + n.ToString(); } return "EX-" + n.ToString("D7"); } public static long ParseExNumber(string value) { if (string.IsNullOrWhiteSpace(value)) { return 0; } var m = ExPattern.Match(value.Trim()); if (!m.Success) { return 0; } return long.TryParse(m.Groups[1].Value, out var n) ? n : 0; } } }