elfisa-pharmacy/src/ElectronicPharmacy/Classes/OrderNumberHelper.cs
Exest 63abfd4e31 Release v1.0.23: global order numbers EX-####### (GLOBAL_SIGN).
Desktop reserves/sends EX numbers from server; supplier DBF gets GLOBAL_SIGN column.
2026-08-05 18:59:43 +03:00

158 lines
5.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
{
/// <summary>
/// Глобальные номера заказов вида EX-0000001.
/// Источник истины — сервер (никогда не повторяются между аптеками).
/// Offline fallback — локальный счётчик EX- (потом сервер может заменить при отправке).
/// </summary>
public static class OrderNumberHelper
{
private static readonly Regex ExPattern = new Regex(@"^EX-(\d{1,7})$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
/// <summary>
/// Выделяет номер для нового локального заказа.
/// Сначала пробует сервер /api/buyer/global-sign/next, иначе локальный EX-#######.
/// </summary>
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<string> 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;
}
}
}