Prompt for Location ID after login, move cart delete there, refine price-list Backspace navigation, and log every API failure with stack and response body. Co-authored-by: Cursor <cursoragent@cursor.com>
349 lines
13 KiB
C#
349 lines
13 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Data.SQLite;
|
||
using System.Drawing;
|
||
using System.Globalization;
|
||
using System.Linq;
|
||
using System.Net.Http;
|
||
using System.Net.Http.Headers;
|
||
using System.Text;
|
||
using System.Text.Json;
|
||
using System.Text.Json.Serialization;
|
||
using System.Threading.Tasks;
|
||
using System.Windows.Forms;
|
||
using Электронная_Фармация.HelpForms;
|
||
using Электронная_Фармация.Properties;
|
||
|
||
namespace Электронная_Фармация.Classes
|
||
{
|
||
public class DataSender
|
||
{
|
||
private const string BuyerOrdersEndpoint = "/api/buyer/orders";
|
||
|
||
private string _baseUrl;
|
||
private string _token;
|
||
|
||
public async Task Main()
|
||
{
|
||
_token = Settings.Default.stringToken;
|
||
_baseUrl = AppConfig.ApiBaseUrl;
|
||
|
||
if (string.IsNullOrWhiteSpace(_token))
|
||
{
|
||
AppDebugLog.Error("DataSender", "Отправка заказов: токен отсутствует");
|
||
ToastNotification.ShowError("Не найден токен авторизации.");
|
||
return;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(AppConfig.LocationId))
|
||
{
|
||
if (!HF_LocationId.PromptAndSave(Form.ActiveForm)
|
||
|| string.IsNullOrWhiteSpace(AppConfig.LocationId))
|
||
{
|
||
AppDebugLog.Error("DataSender", "Отправка заказов: location_id не указан");
|
||
ToastNotification.ShowCustom(
|
||
"Не указан location_id. Заполните его после авторизации.",
|
||
Color.DarkOrange,
|
||
Color.White);
|
||
return;
|
||
}
|
||
}
|
||
|
||
List<BuyerOrderRequest> orders;
|
||
try
|
||
{
|
||
orders = FetchDataFromSql(null);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
AppDebugLog.Error("DataSender", "Ошибка подготовки заказов к отправке", ex);
|
||
ToastNotification.ShowError($"Ошибка подготовки заказов: {ex.Message}");
|
||
return;
|
||
}
|
||
if (orders.Count == 0)
|
||
{
|
||
ToastNotification.ShowCustom("Нет новых заказов для отправки.", Color.DarkOrange, Color.White);
|
||
return;
|
||
}
|
||
|
||
int successCount = 0;
|
||
int failCount = 0;
|
||
|
||
foreach (var order in orders)
|
||
{
|
||
bool ok = await SendOrderAsync(order);
|
||
if (ok)
|
||
{
|
||
successCount++;
|
||
}
|
||
else
|
||
{
|
||
failCount++;
|
||
}
|
||
}
|
||
|
||
if (successCount > 0 && failCount == 0)
|
||
{
|
||
ToastNotification.ShowSuccess($"Отправлено заказов: {successCount}");
|
||
}
|
||
else if (successCount > 0)
|
||
{
|
||
ToastNotification.ShowCustom(
|
||
$"Отправлено: {successCount}, с ошибкой: {failCount}",
|
||
Color.DarkOrange,
|
||
Color.White);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Sends a single local order by Id_Order (must be in state НОВЫЙ).
|
||
/// </summary>
|
||
public async Task<bool> SendSingleOrderAsync(string localOrderId)
|
||
{
|
||
_token = Settings.Default.stringToken;
|
||
_baseUrl = AppConfig.ApiBaseUrl;
|
||
|
||
if (string.IsNullOrWhiteSpace(_token))
|
||
{
|
||
AppDebugLog.Error("DataSender", $"Отправка заказа {localOrderId}: токен отсутствует");
|
||
ToastNotification.ShowError("Не найден токен авторизации.");
|
||
return false;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(AppConfig.LocationId))
|
||
{
|
||
if (!HF_LocationId.PromptAndSave(Form.ActiveForm)
|
||
|| string.IsNullOrWhiteSpace(AppConfig.LocationId))
|
||
{
|
||
AppDebugLog.Error("DataSender", $"Отправка заказа {localOrderId}: location_id не указан");
|
||
ToastNotification.ShowCustom(
|
||
"Не указан location_id. Заполните его после авторизации.",
|
||
Color.DarkOrange,
|
||
Color.White);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
List<BuyerOrderRequest> orders;
|
||
try
|
||
{
|
||
orders = FetchDataFromSql(localOrderId);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
AppDebugLog.Error("DataSender", $"Ошибка подготовки заказа {localOrderId}", ex);
|
||
ToastNotification.ShowError($"Ошибка подготовки заказа: {ex.Message}");
|
||
return false;
|
||
}
|
||
if (orders.Count == 0)
|
||
{
|
||
ToastNotification.ShowCustom("Заказ не найден или уже отправлен.", Color.DarkOrange, Color.White);
|
||
return false;
|
||
}
|
||
|
||
bool ok = await SendOrderAsync(orders[0]);
|
||
if (ok)
|
||
{
|
||
ToastNotification.ShowSuccess("Заказ отправлен");
|
||
}
|
||
|
||
return ok;
|
||
}
|
||
|
||
public List<BuyerOrderRequest> FetchDataFromSql(string onlyOrderId)
|
||
{
|
||
var orders = new Dictionary<string, BuyerOrderRequest>();
|
||
string locationId = AppConfig.LocationId;
|
||
|
||
using (var connection = new SQLiteConnection(AppConfig.SqliteConnectionString))
|
||
{
|
||
connection.Open();
|
||
string query = @"
|
||
SELECT
|
||
Orders.Id_Order,
|
||
OrderItems.supplier_price_id,
|
||
OrderItems.Zakaz,
|
||
Orders.Comment
|
||
FROM Orders
|
||
INNER JOIN OrderItems ON OrderItems.Id_Order = Orders.Id_Order
|
||
WHERE Orders.OrderState = 'НОВЫЙ'
|
||
";
|
||
if (!string.IsNullOrWhiteSpace(onlyOrderId))
|
||
{
|
||
query += " AND Orders.Id_Order = @orderId";
|
||
}
|
||
|
||
query += " ORDER BY Orders.Id_Order";
|
||
|
||
using (var command = new SQLiteCommand(query, connection))
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(onlyOrderId))
|
||
{
|
||
command.Parameters.AddWithValue("@orderId", onlyOrderId);
|
||
}
|
||
|
||
using (var reader = command.ExecuteReader())
|
||
{
|
||
while (reader.Read())
|
||
{
|
||
string orderId = reader["Id_Order"].ToString().Trim();
|
||
string supplierPriceId = reader["supplier_price_id"].ToString().Trim();
|
||
decimal qty = ReadDecimal(reader["Zakaz"]);
|
||
|
||
if (string.IsNullOrWhiteSpace(supplierPriceId))
|
||
{
|
||
throw new InvalidOperationException($"В заказе {orderId} есть позиция без supplier_price_id.");
|
||
}
|
||
|
||
if (qty <= 0)
|
||
{
|
||
throw new InvalidOperationException($"В заказе {orderId} количество должно быть больше нуля.");
|
||
}
|
||
|
||
if (!orders.TryGetValue(orderId, out BuyerOrderRequest order))
|
||
{
|
||
order = new BuyerOrderRequest
|
||
{
|
||
LocalOrderId = orderId,
|
||
LocationId = locationId,
|
||
Comment = reader["Comment"] == DBNull.Value ? null : reader["Comment"].ToString()
|
||
};
|
||
orders.Add(orderId, order);
|
||
}
|
||
|
||
order.Items.Add(new BuyerOrderItem
|
||
{
|
||
SupplierPriceId = supplierPriceId,
|
||
Qty = qty
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return orders.Values.ToList();
|
||
}
|
||
|
||
private async Task<bool> SendOrderAsync(BuyerOrderRequest order)
|
||
{
|
||
var options = new JsonSerializerOptions
|
||
{
|
||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||
};
|
||
string json = JsonSerializer.Serialize(order, options);
|
||
var fullUrl = new Uri(new Uri(_baseUrl.TrimEnd('/') + "/"), BuyerOrdersEndpoint.TrimStart('/')).ToString();
|
||
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||
|
||
AppDebugLog.ApiRequest("POST", fullUrl, json, $"order={order.LocalOrderId}");
|
||
|
||
using (var client = new HttpClient())
|
||
{
|
||
client.BaseAddress = new Uri(_baseUrl);
|
||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token);
|
||
|
||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||
try
|
||
{
|
||
HttpResponseMessage response = await client.PostAsync(BuyerOrdersEndpoint, content);
|
||
string responseString = await response.Content.ReadAsStringAsync();
|
||
stopwatch.Stop();
|
||
|
||
AppDebugLog.ApiResponse("POST", fullUrl, (int)response.StatusCode, stopwatch.ElapsedMilliseconds, responseString);
|
||
|
||
if (!response.IsSuccessStatusCode)
|
||
{
|
||
AppDebugLog.ApiHttpError(
|
||
"POST",
|
||
fullUrl,
|
||
(int)response.StatusCode,
|
||
responseString,
|
||
$"order={order.LocalOrderId}");
|
||
AppDebugLog.Error(
|
||
"DataSender",
|
||
$"Ошибка отправки заказа {order.LocalOrderId}: HTTP {(int)response.StatusCode} {response.ReasonPhrase}");
|
||
ToastNotification.ShowError(
|
||
$"Ошибка отправки заказа {order.LocalOrderId}: {(int)response.StatusCode} {response.ReasonPhrase}\n{responseString}");
|
||
return false;
|
||
}
|
||
|
||
MarkOrderSent(order.LocalOrderId);
|
||
AppDebugLog.Info("DataSender", $"Заказ {order.LocalOrderId} отправлен");
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
stopwatch.Stop();
|
||
AppDebugLog.ApiFailure("POST", fullUrl, stopwatch.ElapsedMilliseconds, ex);
|
||
ToastNotification.ShowError($"Ошибка сети при отправке заказа {order.LocalOrderId}: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
private static void MarkOrderSent(string localOrderId)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(localOrderId))
|
||
{
|
||
return;
|
||
}
|
||
|
||
using (var con = new SQLiteConnection(AppConfig.SqliteConnectionString))
|
||
{
|
||
con.Open();
|
||
using (var cmd = new SQLiteCommand(
|
||
"UPDATE [Orders] SET [OrderState] = 'ОТПРАВЛЕН' WHERE [Id_Order] = @id AND [OrderState] = 'НОВЫЙ'",
|
||
con))
|
||
{
|
||
cmd.Parameters.AddWithValue("@id", localOrderId);
|
||
cmd.ExecuteNonQuery();
|
||
}
|
||
}
|
||
}
|
||
|
||
private static decimal ReadDecimal(object value)
|
||
{
|
||
if (value == null || value == DBNull.Value)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
string text = value.ToString();
|
||
if (decimal.TryParse(text, NumberStyles.Number, CultureInfo.InvariantCulture, out decimal invariantValue))
|
||
{
|
||
return invariantValue;
|
||
}
|
||
|
||
if (decimal.TryParse(text, NumberStyles.Number, CultureInfo.CurrentCulture, out decimal currentCultureValue))
|
||
{
|
||
return currentCultureValue;
|
||
}
|
||
|
||
throw new InvalidOperationException($"Некорректное количество: {text}");
|
||
}
|
||
}
|
||
|
||
public class BuyerOrderRequest
|
||
{
|
||
[JsonIgnore]
|
||
public string LocalOrderId { get; set; }
|
||
|
||
[JsonPropertyName("location_id")]
|
||
public string LocationId { get; set; }
|
||
|
||
[JsonPropertyName("comment")]
|
||
public string Comment { get; set; }
|
||
|
||
[JsonPropertyName("items")]
|
||
public List<BuyerOrderItem> Items { get; set; } = new List<BuyerOrderItem>();
|
||
}
|
||
|
||
public class BuyerOrderItem
|
||
{
|
||
[JsonPropertyName("supplier_price_id")]
|
||
public string SupplierPriceId { get; set; }
|
||
|
||
[JsonPropertyName("qty")]
|
||
public decimal Qty { get; set; }
|
||
}
|
||
}
|