v1.0.4: адаптация desktop к текущему buyer API
Перевести desktop-клиент на работу с текущими buyer orders и локальным SQLite-кешем, чтобы отправка заказов и синхронизация документов работали с уже развернутым сервером без старых MSSQL-зависимостей. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
4b3b2c8ca8
commit
e35e67849f
@ -1,11 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Электронная_Фармация.Classes
|
||||
{
|
||||
@ -155,20 +158,101 @@ namespace Электронная_Фармация.Classes
|
||||
}
|
||||
|
||||
public async Task<List<InvoiceApiItem>> GetInvoicesAsync()
|
||||
{
|
||||
var orders = await GetBuyerOrdersAsync(status: "Placed", limit: 200);
|
||||
var result = new List<InvoiceApiItem>();
|
||||
|
||||
foreach (var order in orders)
|
||||
{
|
||||
var detail = await GetBuyerOrderByIdAsync(order.OrderID);
|
||||
if (detail == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result.Add(InvoiceApiItem.FromBuyerOrder(detail));
|
||||
}
|
||||
|
||||
AppDebugLog.Info("ApiClient", $"Накладные (через buyer/orders) получены: {result.Count} шт.");
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<List<BuyerOrderListItemDto>> GetBuyerOrdersAsync(string status = null, int limit = 200, int offset = 0)
|
||||
{
|
||||
EnsureAuthenticated();
|
||||
|
||||
var responseBody = await SendAuthorizedGetAsync("/api/buyer/invoices", "загрузку накладных");
|
||||
var query = new List<string>();
|
||||
if (!string.IsNullOrWhiteSpace(status))
|
||||
{
|
||||
query.Add($"status={Uri.EscapeDataString(status)}");
|
||||
}
|
||||
|
||||
if (limit > 0)
|
||||
{
|
||||
query.Add($"limit={limit}");
|
||||
}
|
||||
|
||||
if (offset > 0)
|
||||
{
|
||||
query.Add($"offset={offset}");
|
||||
}
|
||||
|
||||
var path = "/api/buyer/orders";
|
||||
if (query.Count > 0)
|
||||
{
|
||||
path += "?" + string.Join("&", query);
|
||||
}
|
||||
|
||||
var responseBody = await SendAuthorizedGetAsync(path, "загрузку заказов");
|
||||
try
|
||||
{
|
||||
var items = JsonSerializer.Deserialize<List<InvoiceApiItem>>(responseBody, JsonOptions) ?? new List<InvoiceApiItem>();
|
||||
AppDebugLog.Info("ApiClient", $"Накладные получены: {items.Count} шт.");
|
||||
return items;
|
||||
var response = JsonSerializer.Deserialize<BuyerOrdersResponse>(responseBody, JsonOptions);
|
||||
return response?.Orders ?? new List<BuyerOrderListItemDto>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppDebugLog.Error("ApiClient", "Не удалось разобрать ответ накладных", ex);
|
||||
throw new HttpRequestException($"Некорректный ответ сервера при загрузке накладных: {ex.Message}", ex);
|
||||
AppDebugLog.Error("ApiClient", "Не удалось разобрать список заказов", ex);
|
||||
throw new HttpRequestException($"Некорректный ответ сервера при загрузке заказов: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<BuyerOrderDetailDto> GetBuyerOrderByIdAsync(string orderId)
|
||||
{
|
||||
EnsureAuthenticated();
|
||||
if (string.IsNullOrWhiteSpace(orderId))
|
||||
{
|
||||
throw new ArgumentException("Не указан ID заказа.", nameof(orderId));
|
||||
}
|
||||
|
||||
var responseBody = await SendAuthorizedGetAsync($"/api/buyer/orders/{Uri.EscapeDataString(orderId)}", "загрузку заказа");
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Deserialize<BuyerOrderDetailDto>(responseBody, JsonOptions);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppDebugLog.Error("ApiClient", $"Не удалось разобрать заказ {orderId}", ex);
|
||||
throw new HttpRequestException($"Некорректный ответ сервера при загрузке заказа: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<BuyerOrderDetailDto> PlaceBuyerOrderAsync(string orderId)
|
||||
{
|
||||
EnsureAuthenticated();
|
||||
if (string.IsNullOrWhiteSpace(orderId))
|
||||
{
|
||||
throw new ArgumentException("Не указан ID заказа.", nameof(orderId));
|
||||
}
|
||||
|
||||
var responseBody = await SendAuthorizedPostAsync($"/api/buyer/orders/{Uri.EscapeDataString(orderId)}/place", string.Empty, "оформление заказа");
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Deserialize<BuyerOrderDetailDto>(responseBody, JsonOptions);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppDebugLog.Error("ApiClient", $"Не удалось разобрать ответ оформления заказа {orderId}", ex);
|
||||
throw new HttpRequestException($"Некорректный ответ сервера при оформлении заказа: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,6 +271,63 @@ namespace Электронная_Фармация.Classes
|
||||
return await SendAuthorizedGetWithProgressAsync(path, operationName, null);
|
||||
}
|
||||
|
||||
private async Task<string> SendAuthorizedPostAsync(string path, string requestBody, string operationName)
|
||||
{
|
||||
var fullUrl = BuildFullUrl(path);
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
||||
AppDebugLog.ApiRequest("POST", fullUrl, requestBody, $"token={AppDebugLog.MaskToken(_token)}");
|
||||
|
||||
try
|
||||
{
|
||||
using (var httpRequest = new HttpRequestMessage(HttpMethod.Post, fullUrl))
|
||||
{
|
||||
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token);
|
||||
httpRequest.Content = new StringContent(requestBody ?? string.Empty, Encoding.UTF8, "application/json");
|
||||
var response = await _httpClient.SendAsync(httpRequest);
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
stopwatch.Stop();
|
||||
|
||||
AppDebugLog.ApiResponse("POST", fullUrl, (int)response.StatusCode, stopwatch.ElapsedMilliseconds, responseBody);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return responseBody;
|
||||
}
|
||||
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||
{
|
||||
_token = null;
|
||||
var error = TryDeserializeError(responseBody);
|
||||
var message = $"Сессия истекла: {error?.Error ?? "Необходимо войти заново"}";
|
||||
AppDebugLog.ApiHttpError("POST", fullUrl, (int)response.StatusCode, responseBody, operationName);
|
||||
throw new UnauthorizedAccessException(message);
|
||||
}
|
||||
|
||||
var httpError = TryDeserializeError(responseBody);
|
||||
var httpMessage = $"Ошибка HTTP {response.StatusCode}: {httpError?.Error ?? responseBody}";
|
||||
AppDebugLog.ApiHttpError("POST", fullUrl, (int)response.StatusCode, responseBody, operationName);
|
||||
throw new HttpRequestException(httpMessage);
|
||||
}
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (HttpRequestException ex) when (!ex.Message.StartsWith("Ошибка HTTP"))
|
||||
{
|
||||
stopwatch.Stop();
|
||||
AppDebugLog.ApiFailure("POST", fullUrl, stopwatch.ElapsedMilliseconds, ex);
|
||||
throw WrapNetworkError(ex, operationName);
|
||||
}
|
||||
catch (TaskCanceledException ex)
|
||||
{
|
||||
stopwatch.Stop();
|
||||
AppDebugLog.ApiFailure("POST", fullUrl, stopwatch.ElapsedMilliseconds, ex);
|
||||
throw WrapNetworkError(ex, operationName);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> SendAuthorizedGetWithProgressAsync(
|
||||
string path,
|
||||
string operationName,
|
||||
@ -372,6 +513,127 @@ namespace Электронная_Фармация.Classes
|
||||
public string ConsigneeName { get; set; }
|
||||
public string InvoiceSum { get; set; }
|
||||
public string RefuseSum { get; set; }
|
||||
|
||||
public string SourceOrderId { get; set; }
|
||||
public List<InvoiceApiOrderItem> Items { get; set; } = new List<InvoiceApiOrderItem>();
|
||||
|
||||
public static InvoiceApiItem FromBuyerOrder(BuyerOrderDetailDto detail)
|
||||
{
|
||||
var supplierNames = detail.Items == null
|
||||
? string.Empty
|
||||
: string.Join(", ", detail.Items
|
||||
.Select(i => i.Supplier)
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||||
.Distinct());
|
||||
|
||||
var invoiceDate = detail.PlacedAt ?? detail.CreatedAt;
|
||||
var invoice = new InvoiceApiItem
|
||||
{
|
||||
InvoiceNumber = string.IsNullOrWhiteSpace(detail.LocalOrderNumber)
|
||||
? detail.OrderID
|
||||
: detail.LocalOrderNumber,
|
||||
InvoiceDate = invoiceDate?.ToString("yyyy-MM-dd") ?? string.Empty,
|
||||
SupplierName = supplierNames,
|
||||
ConsigneeName = detail.LocationAddress ?? string.Empty,
|
||||
InvoiceSum = (detail.TotalAmount ?? 0m).ToString(System.Globalization.CultureInfo.InvariantCulture),
|
||||
RefuseSum = "0",
|
||||
SourceOrderId = detail.OrderID
|
||||
};
|
||||
|
||||
if (detail.Items != null)
|
||||
{
|
||||
foreach (var item in detail.Items)
|
||||
{
|
||||
invoice.Items.Add(new InvoiceApiOrderItem
|
||||
{
|
||||
GoodCode = item.ItemCode ?? string.Empty,
|
||||
GoodName = item.Name ?? string.Empty,
|
||||
SupplierName = item.Supplier ?? string.Empty,
|
||||
Qty = item.Qty,
|
||||
Price = item.UnitPrice,
|
||||
Total = item.Total
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return invoice;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class InvoiceApiOrderItem
|
||||
{
|
||||
public string GoodCode { get; set; }
|
||||
public string GoodName { get; set; }
|
||||
public string SupplierName { get; set; }
|
||||
public decimal Qty { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
public decimal Total { get; set; }
|
||||
}
|
||||
|
||||
public sealed class BuyerOrdersResponse
|
||||
{
|
||||
[JsonPropertyName("orders")]
|
||||
public List<BuyerOrderListItemDto> Orders { get; set; }
|
||||
}
|
||||
|
||||
public sealed class BuyerOrderListItemDto
|
||||
{
|
||||
[JsonPropertyName("order_id")]
|
||||
public string OrderID { get; set; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; set; }
|
||||
}
|
||||
|
||||
public sealed class BuyerOrderDetailDto
|
||||
{
|
||||
[JsonPropertyName("order_id")]
|
||||
public string OrderID { get; set; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; set; }
|
||||
|
||||
[JsonPropertyName("total_amount")]
|
||||
public decimal? TotalAmount { get; set; }
|
||||
|
||||
[JsonPropertyName("created_at")]
|
||||
public DateTime? CreatedAt { get; set; }
|
||||
|
||||
[JsonPropertyName("placed_at")]
|
||||
public DateTime? PlacedAt { get; set; }
|
||||
|
||||
[JsonPropertyName("comment")]
|
||||
public string Comment { get; set; }
|
||||
|
||||
[JsonPropertyName("location_address")]
|
||||
public string LocationAddress { get; set; }
|
||||
|
||||
[JsonPropertyName("items")]
|
||||
public List<BuyerOrderItemDetailDto> Items { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string LocalOrderNumber { get; set; }
|
||||
}
|
||||
|
||||
public sealed class BuyerOrderItemDetailDto
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonPropertyName("supplier")]
|
||||
public string Supplier { get; set; }
|
||||
|
||||
[JsonPropertyName("qty")]
|
||||
public decimal Qty { get; set; }
|
||||
|
||||
[JsonPropertyName("unit_price")]
|
||||
public decimal UnitPrice { get; set; }
|
||||
|
||||
[JsonPropertyName("total")]
|
||||
public decimal Total { get; set; }
|
||||
|
||||
[JsonPropertyName("item_code")]
|
||||
public string ItemCode { get; set; }
|
||||
}
|
||||
|
||||
public sealed class HttpTransferProgress
|
||||
|
||||
@ -272,7 +272,13 @@ WHERE Orders.OrderState = 'НОВЫЙ'
|
||||
return false;
|
||||
}
|
||||
|
||||
MarkOrderSent(order.LocalOrderId);
|
||||
var createdOrder = JsonSerializer.Deserialize<BuyerOrderCreateResult>(responseString, options);
|
||||
if (createdOrder == null || string.IsNullOrWhiteSpace(createdOrder.OrderID))
|
||||
{
|
||||
throw new InvalidOperationException("Сервер не вернул order_id созданного заказа.");
|
||||
}
|
||||
|
||||
MarkOrderSent(order.LocalOrderId, createdOrder.OrderID, createdOrder.Status);
|
||||
AppDebugLog.Info("DataSender", $"Заказ {order.LocalOrderId} отправлен");
|
||||
return true;
|
||||
}
|
||||
@ -286,7 +292,7 @@ WHERE Orders.OrderState = 'НОВЫЙ'
|
||||
}
|
||||
}
|
||||
|
||||
private static void MarkOrderSent(string localOrderId)
|
||||
private static void MarkOrderSent(string localOrderId, string remoteOrderId, string remoteStatus)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(localOrderId))
|
||||
{
|
||||
@ -297,9 +303,14 @@ WHERE Orders.OrderState = 'НОВЫЙ'
|
||||
{
|
||||
con.Open();
|
||||
using (var cmd = new SQLiteCommand(
|
||||
"UPDATE [Orders] SET [OrderState] = 'ОТПРАВЛЕН' WHERE [Id_Order] = @id AND [OrderState] = 'НОВЫЙ'",
|
||||
@"UPDATE [Orders]
|
||||
SET [OrderState] = @state,
|
||||
[RemoteOrderId] = @remoteOrderId
|
||||
WHERE [Id_Order] = @id AND [OrderState] = 'НОВЫЙ'",
|
||||
con))
|
||||
{
|
||||
cmd.Parameters.AddWithValue("@state", "ОТПРАВЛЕН");
|
||||
cmd.Parameters.AddWithValue("@remoteOrderId", remoteOrderId ?? string.Empty);
|
||||
cmd.Parameters.AddWithValue("@id", localOrderId);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
@ -357,4 +368,13 @@ WHERE Orders.OrderState = 'НОВЫЙ'
|
||||
[JsonPropertyName("item_code")]
|
||||
public string ItemCode { get; set; }
|
||||
}
|
||||
|
||||
public sealed class BuyerOrderCreateResult
|
||||
{
|
||||
[JsonPropertyName("order_id")]
|
||||
public string OrderID { get; set; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SQLite;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Электронная_Фармация.Properties;
|
||||
|
||||
@ -26,7 +28,29 @@ namespace Электронная_Фармация.Classes
|
||||
var client = new ApiClient();
|
||||
client.SetToken(token);
|
||||
AppDebugLog.Info("InvoiceSync", $"Старт синхронизации накладных. Сервер={client.BaseUrl}");
|
||||
var items = await client.GetInvoicesAsync();
|
||||
var orders = await client.GetBuyerOrdersAsync(limit: 200);
|
||||
var items = new List<InvoiceApiItem>();
|
||||
using (var con = new SQLiteConnection(AppConfig.SqliteConnectionString))
|
||||
{
|
||||
con.Open();
|
||||
foreach (var order in orders)
|
||||
{
|
||||
if (string.Equals(order.Status, "Cancelled", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var detail = await client.GetBuyerOrderByIdAsync(order.OrderID);
|
||||
if (detail == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
detail.LocalOrderNumber = TryGetLocalOrderNumber(con, detail.OrderID);
|
||||
items.Add(InvoiceApiItem.FromBuyerOrder(detail));
|
||||
}
|
||||
}
|
||||
|
||||
var saved = SaveInvoices(items);
|
||||
AppDebugLog.Info("InvoiceSync", $"Накладные сохранены локально: {saved} шт.");
|
||||
return saved;
|
||||
@ -40,30 +64,32 @@ namespace Электронная_Фармация.Classes
|
||||
|
||||
private static int SaveInvoices(IReadOnlyList<InvoiceApiItem> items)
|
||||
{
|
||||
if (items.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
using (var con = new SQLiteConnection(AppConfig.SqliteConnectionString))
|
||||
{
|
||||
con.Open();
|
||||
using (var tx = con.BeginTransaction())
|
||||
{
|
||||
using (var cleanupItems = new SQLiteCommand(@"
|
||||
DELETE FROM InvoiceItem
|
||||
WHERE idInvoice IN (
|
||||
SELECT idInvoice FROM Invoice WHERE ifnull(IsRequestedInvoice, 0) = 0
|
||||
);", con, tx))
|
||||
{
|
||||
cleanupItems.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
using (var cleanupInvoices = new SQLiteCommand(
|
||||
"DELETE FROM Invoice WHERE ifnull(IsRequestedInvoice, 0) = 0;",
|
||||
con,
|
||||
tx))
|
||||
{
|
||||
cleanupInvoices.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
using (var cmd = new SQLiteCommand(@"
|
||||
INSERT OR REPLACE INTO Invoice (InvoiceNumber, invoiceDate, SupplierName, ConsigneesName, InvoiceSum, RefuseSum)
|
||||
VALUES (@number, @date, @supplier, @consignee, @sum, @refuseSum)", con, tx))
|
||||
{
|
||||
cmd.Parameters.AddWithValue("@number", item.InvoiceNumber ?? string.Empty);
|
||||
cmd.Parameters.AddWithValue("@date", item.InvoiceDate ?? string.Empty);
|
||||
cmd.Parameters.AddWithValue("@supplier", item.SupplierName ?? string.Empty);
|
||||
cmd.Parameters.AddWithValue("@consignee", item.ConsigneeName ?? string.Empty);
|
||||
cmd.Parameters.AddWithValue("@sum", item.InvoiceSum ?? string.Empty);
|
||||
cmd.Parameters.AddWithValue("@refuseSum", item.RefuseSum ?? string.Empty);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
var invoiceId = UpsertInvoice(con, tx, item);
|
||||
SaveInvoiceItems(con, tx, invoiceId, item);
|
||||
}
|
||||
|
||||
tx.Commit();
|
||||
@ -72,5 +98,192 @@ VALUES (@number, @date, @supplier, @consignee, @sum, @refuseSum)", con, tx))
|
||||
|
||||
return items.Count;
|
||||
}
|
||||
|
||||
private static int UpsertInvoice(SQLiteConnection con, SQLiteTransaction tx, InvoiceApiItem item)
|
||||
{
|
||||
using (var findCmd = new SQLiteCommand(@"
|
||||
SELECT [idInvoice]
|
||||
FROM [Invoice]
|
||||
WHERE cast([SourceOrderId] as text) = @sourceOrderId
|
||||
LIMIT 1;", con, tx))
|
||||
{
|
||||
findCmd.Parameters.AddWithValue("@sourceOrderId", item.SourceOrderId ?? string.Empty);
|
||||
var existing = findCmd.ExecuteScalar();
|
||||
if (existing != null && existing != DBNull.Value)
|
||||
{
|
||||
var invoiceId = Convert.ToInt32(existing, CultureInfo.InvariantCulture);
|
||||
using (var updateCmd = new SQLiteCommand(@"
|
||||
UPDATE [Invoice]
|
||||
SET [InvoiceNumber] = @number,
|
||||
[InvoiceDate] = @date,
|
||||
[SupplierName] = @supplier,
|
||||
[ConsigneesName] = @consignee,
|
||||
[InvoiceSum] = @sum,
|
||||
[RefuseSum] = @refuseSum,
|
||||
[SourceOrderNumber] = @sourceOrderNumber,
|
||||
[IsRequestedInvoice] = 0
|
||||
WHERE [idInvoice] = @invoiceId;", con, tx))
|
||||
{
|
||||
updateCmd.Parameters.AddWithValue("@number", item.InvoiceNumber ?? string.Empty);
|
||||
updateCmd.Parameters.AddWithValue("@date", item.InvoiceDate ?? string.Empty);
|
||||
updateCmd.Parameters.AddWithValue("@supplier", item.SupplierName ?? string.Empty);
|
||||
updateCmd.Parameters.AddWithValue("@consignee", item.ConsigneeName ?? string.Empty);
|
||||
updateCmd.Parameters.AddWithValue("@sum", item.InvoiceSum ?? "0");
|
||||
updateCmd.Parameters.AddWithValue("@refuseSum", item.RefuseSum ?? "0");
|
||||
updateCmd.Parameters.AddWithValue("@sourceOrderNumber", item.InvoiceNumber ?? string.Empty);
|
||||
updateCmd.Parameters.AddWithValue("@invoiceId", invoiceId);
|
||||
updateCmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
using (var deleteItems = new SQLiteCommand("DELETE FROM [InvoiceItem] WHERE [idInvoice] = @invoiceId;", con, tx))
|
||||
{
|
||||
deleteItems.Parameters.AddWithValue("@invoiceId", invoiceId);
|
||||
deleteItems.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
return invoiceId;
|
||||
}
|
||||
}
|
||||
|
||||
using (var insertCmd = new SQLiteCommand(@"
|
||||
INSERT INTO [Invoice]
|
||||
(
|
||||
[InvoiceNumber],
|
||||
[InvoiceDate],
|
||||
[CodeSupplier],
|
||||
[CodeConsignees],
|
||||
[SumWithoutNDS],
|
||||
[SumWithNDS],
|
||||
[SumNDS],
|
||||
[SupplierName],
|
||||
[ConsigneesName],
|
||||
[InvoiceSum],
|
||||
[RefuseSum],
|
||||
[SourceOrderId],
|
||||
[SourceOrderNumber],
|
||||
[IsRequestedInvoice]
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
@number,
|
||||
@date,
|
||||
0,
|
||||
0,
|
||||
@sum,
|
||||
@sum,
|
||||
0,
|
||||
@supplier,
|
||||
@consignee,
|
||||
@sum,
|
||||
@refuseSum,
|
||||
@sourceOrderId,
|
||||
@sourceOrderNumber,
|
||||
0
|
||||
);
|
||||
SELECT last_insert_rowid();", con, tx))
|
||||
{
|
||||
insertCmd.Parameters.AddWithValue("@number", item.InvoiceNumber ?? string.Empty);
|
||||
insertCmd.Parameters.AddWithValue("@date", item.InvoiceDate ?? string.Empty);
|
||||
insertCmd.Parameters.AddWithValue("@sum", item.InvoiceSum ?? "0");
|
||||
insertCmd.Parameters.AddWithValue("@supplier", item.SupplierName ?? string.Empty);
|
||||
insertCmd.Parameters.AddWithValue("@consignee", item.ConsigneeName ?? string.Empty);
|
||||
insertCmd.Parameters.AddWithValue("@refuseSum", item.RefuseSum ?? "0");
|
||||
insertCmd.Parameters.AddWithValue("@sourceOrderId", item.SourceOrderId ?? string.Empty);
|
||||
insertCmd.Parameters.AddWithValue("@sourceOrderNumber", item.InvoiceNumber ?? string.Empty);
|
||||
return Convert.ToInt32(insertCmd.ExecuteScalar(), CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SaveInvoiceItems(SQLiteConnection con, SQLiteTransaction tx, int invoiceId, InvoiceApiItem item)
|
||||
{
|
||||
foreach (var invoiceItem in item.Items ?? Enumerable.Empty<InvoiceApiOrderItem>())
|
||||
{
|
||||
using (var cmd = new SQLiteCommand(@"
|
||||
INSERT INTO [InvoiceItem]
|
||||
(
|
||||
[idGood],
|
||||
[GoodCode],
|
||||
[Good],
|
||||
[ProducerName],
|
||||
[CountryName],
|
||||
[idInvoice],
|
||||
[GoodsGount],
|
||||
[PriceSupplierWithoutNDS],
|
||||
[PriceSupplierWithNDS],
|
||||
[PriceProducerWithoutNDS],
|
||||
[PriceProducerWithNDS],
|
||||
[NDS],
|
||||
[JNVLS],
|
||||
[PriceReestr],
|
||||
[SumWithoutNDS],
|
||||
[SumWithNDS],
|
||||
[SumNDS],
|
||||
[Serial],
|
||||
[BestBefore],
|
||||
[Sertificate],
|
||||
[Marked],
|
||||
[GTIN]
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
null,
|
||||
@goodCode,
|
||||
@good,
|
||||
'',
|
||||
'',
|
||||
@invoiceId,
|
||||
@qty,
|
||||
@price,
|
||||
@price,
|
||||
null,
|
||||
null,
|
||||
0,
|
||||
0,
|
||||
null,
|
||||
@sum,
|
||||
@sum,
|
||||
0,
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
0,
|
||||
''
|
||||
)", con, tx))
|
||||
{
|
||||
cmd.Parameters.AddWithValue("@goodCode", ParseGoodCode(invoiceItem.GoodCode));
|
||||
cmd.Parameters.AddWithValue("@good", invoiceItem.GoodName ?? string.Empty);
|
||||
cmd.Parameters.AddWithValue("@invoiceId", invoiceId);
|
||||
cmd.Parameters.AddWithValue("@qty", invoiceItem.Qty);
|
||||
cmd.Parameters.AddWithValue("@price", invoiceItem.Price);
|
||||
cmd.Parameters.AddWithValue("@sum", invoiceItem.Total);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int ParseGoodCode(string value)
|
||||
{
|
||||
int parsed;
|
||||
return int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out parsed) ? parsed : 0;
|
||||
}
|
||||
|
||||
private static string TryGetLocalOrderNumber(SQLiteConnection con, string remoteOrderId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(remoteOrderId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
using (var cmd = new SQLiteCommand(@"
|
||||
SELECT [OrderNumber]
|
||||
FROM [Orders]
|
||||
WHERE [RemoteOrderId] = @remoteOrderId
|
||||
LIMIT 1;", con))
|
||||
{
|
||||
cmd.Parameters.AddWithValue("@remoteOrderId", remoteOrderId);
|
||||
var value = cmd.ExecuteScalar();
|
||||
return value == null || value == DBNull.Value ? null : value.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -301,9 +301,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
<Content Include="Data\efClient.db">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Resources\JVMarkOfGood.jpg" />
|
||||
|
||||
@ -250,7 +250,8 @@ create table if not exists [Orders] (
|
||||
[SummaZakaza] nvarchar(32),
|
||||
[OrderState] nvarchar(32),
|
||||
[Comment] nvarchar(1024),
|
||||
[ConsigneeName] nvarchar(128)
|
||||
[ConsigneeName] nvarchar(128),
|
||||
[RemoteOrderId] nvarchar(36)
|
||||
)
|
||||
";
|
||||
|
||||
@ -400,6 +401,7 @@ create table if not exists [OrderItems] (
|
||||
TryAddColumn(conForCheckTables, "Invoice", "SourceOrderId", "integer");
|
||||
TryAddColumn(conForCheckTables, "Invoice", "SourceOrderNumber", "nvarchar(36)");
|
||||
TryAddColumn(conForCheckTables, "Invoice", "IsRequestedInvoice", "int(1)");
|
||||
TryAddColumn(conForCheckTables, "Orders", "RemoteOrderId", "nvarchar(36)");
|
||||
TryAddColumn(conForCheckTables, "PriceList", "TradeName", "nvarchar(256)");
|
||||
TryAddColumn(conForCheckTables, "PriceList", "Dosage", "nvarchar(128)");
|
||||
TryAddColumn(conForCheckTables, "PriceList", "MarkupPercent", "real");
|
||||
|
||||
@ -43,22 +43,25 @@ using Электронная_Фармация.Classes;
|
||||
|
||||
using (SQLiteConnection conAddComment = new SQLiteConnection(connectionStringToLocalDB))
|
||||
{
|
||||
|
||||
string qShowMeStatusOrder = $"select OrderState from Orders where Id_Order = '{_idOrder}'";
|
||||
|
||||
string qAddComment = $"update Orders set Comment = '{rtxtComment.Text}' where Id_Order = '{_idOrder}'";
|
||||
try
|
||||
{
|
||||
conAddComment.Open();
|
||||
|
||||
SQLiteCommand cmdShowMeOrderStatus = new SQLiteCommand(qShowMeStatusOrder, conAddComment);
|
||||
|
||||
string OrderState = cmdShowMeOrderStatus.ExecuteScalar().ToString();
|
||||
|
||||
if (OrderState == "НОВЫЙ")
|
||||
string orderState;
|
||||
using (var cmdShowMeOrderStatus = new SQLiteCommand("select OrderState from Orders where Id_Order = @id", conAddComment))
|
||||
{
|
||||
SQLiteCommand cmdAddComment = new SQLiteCommand(qAddComment, conAddComment);
|
||||
cmdAddComment.ExecuteNonQuery();
|
||||
cmdShowMeOrderStatus.Parameters.AddWithValue("@id", _idOrder);
|
||||
orderState = Convert.ToString(cmdShowMeOrderStatus.ExecuteScalar()) ?? string.Empty;
|
||||
}
|
||||
|
||||
if (orderState == "НОВЫЙ")
|
||||
{
|
||||
using (var cmdAddComment = new SQLiteCommand("update Orders set Comment = @comment where Id_Order = @id", conAddComment))
|
||||
{
|
||||
cmdAddComment.Parameters.AddWithValue("@comment", rtxtComment.Text);
|
||||
cmdAddComment.Parameters.AddWithValue("@id", _idOrder);
|
||||
cmdAddComment.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@ -394,9 +394,9 @@ namespace Электронная_Фармация.UserControls
|
||||
|
||||
try
|
||||
{
|
||||
var service = new InvoiceRequestService();
|
||||
service.CreateOrUpdateFromOrder(orderId);
|
||||
ToastNotification.ShowSuccess("Накладная добавлена в раздел \"Накладные\".");
|
||||
var service = new InvoiceSyncService();
|
||||
service.SyncFromServerAsync().GetAwaiter().GetResult();
|
||||
ToastNotification.ShowSuccess("Накладные обновлены с сервера.");
|
||||
|
||||
var shell = FindForm() as ElectroPharmacy;
|
||||
shell?.OpenInvoicesTab(orderId);
|
||||
@ -508,7 +508,11 @@ and OrderDate <= '{dateTo}'
|
||||
void loadConsignees()
|
||||
{
|
||||
string connectionStringToLocalDB = AppConfig.SqliteConnectionString;
|
||||
string qShowMeConsigneesList = "select ConsigneesName from Consignees order by [ConsigneesUseAsDefault] desc";
|
||||
string qShowMeConsigneesList = @"
|
||||
select distinct ConsigneeName
|
||||
from Orders
|
||||
where trim(ifnull(ConsigneeName, '')) <> ''
|
||||
order by ConsigneeName";
|
||||
|
||||
using (SQLiteConnection conConsignees = new SQLiteConnection(connectionStringToLocalDB))
|
||||
{
|
||||
@ -527,7 +531,7 @@ and OrderDate <= '{dateTo}'
|
||||
|
||||
var items = new List<string> { "Грузополучатель" };
|
||||
items.AddRange(tableConsignees.Rows.Cast<DataRow>()
|
||||
.Select(r => r["ConsigneesName"]?.ToString())
|
||||
.Select(r => r["ConsigneeName"]?.ToString())
|
||||
.Where(n => !string.IsNullOrWhiteSpace(n)));
|
||||
_comboConsignees.DataSource = items;
|
||||
_comboConsignees.SelectedIndex = 0;
|
||||
@ -547,7 +551,11 @@ and OrderDate <= '{dateTo}'
|
||||
{
|
||||
string connectionStringToLocalDB = AppConfig.SqliteConnectionString;
|
||||
|
||||
string qShowMeSuppliersList = "select SuppliersName from suppliers order by [SuppliersName] asc";
|
||||
string qShowMeSuppliersList = @"
|
||||
select distinct SupplierName
|
||||
from Orders
|
||||
where trim(ifnull(SupplierName, '')) <> ''
|
||||
order by SupplierName asc";
|
||||
|
||||
using (SQLiteConnection conSuppliers = new SQLiteConnection(connectionStringToLocalDB))
|
||||
{
|
||||
@ -559,7 +567,7 @@ and OrderDate <= '{dateTo}'
|
||||
DataTable tableSuppliers = new DataTable();
|
||||
daSuppliersList.Fill(tableSuppliers);
|
||||
comboSuppliers.DataSource = tableSuppliers;
|
||||
comboSuppliers.DisplayMember = "SuppliersName";
|
||||
comboSuppliers.DisplayMember = "SupplierName";
|
||||
comboSuppliers.Text = "Поставщик";
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@ -57,7 +57,11 @@ namespace Электронная_Фармация.UserControls
|
||||
{
|
||||
string connectionStringToLocalDB = AppConfig.SqliteConnectionString;
|
||||
|
||||
string qShowMeSuppliersList = "select SuppliersName from suppliers order by [SuppliersName] asc";
|
||||
string qShowMeSuppliersList = @"
|
||||
select distinct SupplierName
|
||||
from Invoice
|
||||
where trim(ifnull(SupplierName, '')) <> ''
|
||||
order by SupplierName asc";
|
||||
|
||||
using (SQLiteConnection conSuppliers = new SQLiteConnection(connectionStringToLocalDB))
|
||||
{
|
||||
@ -69,7 +73,7 @@ namespace Электронная_Фармация.UserControls
|
||||
DataTable tableSuppliers = new DataTable();
|
||||
daSuppliersList.Fill(tableSuppliers);
|
||||
comboSuppliers.DataSource = tableSuppliers;
|
||||
comboSuppliers.DisplayMember = "SuppliersName";
|
||||
comboSuppliers.DisplayMember = "SupplierName";
|
||||
comboSuppliers.Text = "Поставщик";
|
||||
}
|
||||
catch(Exception ex)
|
||||
@ -87,7 +91,11 @@ namespace Электронная_Фармация.UserControls
|
||||
{
|
||||
string connectionStringToLocalDB = AppConfig.SqliteConnectionString;
|
||||
|
||||
string qShowMeConsigneesList = "select ConsigneesName from Consignees order by [ConsigneesUseAsDefault] desc";
|
||||
string qShowMeConsigneesList = @"
|
||||
select distinct ConsigneesName
|
||||
from Invoice
|
||||
where trim(ifnull(ConsigneesName, '')) <> ''
|
||||
order by ConsigneesName asc";
|
||||
|
||||
using (SQLiteConnection conConsignees = new SQLiteConnection(connectionStringToLocalDB))
|
||||
{
|
||||
@ -127,8 +135,8 @@ select
|
||||
i.[idInvoice] as [ИД накладной],
|
||||
i.[InvoiceNumber] as [Номер],
|
||||
i.[InvoiceDate] as [Дата],
|
||||
coalesce(nullif(i.[SupplierName], ''), (select s.[SuppliersName] from [Suppliers] s where s.[codeSuppliers] = i.[CodeSupplier])) as [Поставщик],
|
||||
coalesce(nullif(i.[ConsigneesName], ''), (select c.[ConsigneesName] from [Consignees] c where c.[codeConsignees] = i.[CodeConsignees])) as [Грузополучатель],
|
||||
ifnull(i.[SupplierName], '') as [Поставщик],
|
||||
ifnull(i.[ConsigneesName], '') as [Грузополучатель],
|
||||
i.[SourceOrderNumber] as [Номер заказа],
|
||||
coalesce(i.[InvoiceSum], i.[SumWithNDS], i.[SumWithoutNDS], 0) as [Сумма],
|
||||
coalesce(i.[RefuseSum], 0) as [Сумма отказа]
|
||||
@ -142,12 +150,12 @@ where date(i.[InvoiceDate]) between date(@dateFrom) and date(@dateTo)";
|
||||
|
||||
if (comboSuppliers.Text != "" && comboSuppliers.Text != "Поставщик")
|
||||
{
|
||||
sql += " and coalesce(nullif(i.[SupplierName], ''), (select s.[SuppliersName] from [Suppliers] s where s.[codeSuppliers] = i.[CodeSupplier])) like @supplierName";
|
||||
sql += " and ifnull(i.[SupplierName], '') like @supplierName";
|
||||
}
|
||||
|
||||
if (comboConsignees.Text != "" && comboConsignees.Text != "Грузополучатель")
|
||||
{
|
||||
sql += " and coalesce(nullif(i.[ConsigneesName], ''), (select c.[ConsigneesName] from [Consignees] c where c.[codeConsignees] = i.[CodeConsignees])) like @consigneeName";
|
||||
sql += " and ifnull(i.[ConsigneesName], '') like @consigneeName";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_initialSourceOrderId))
|
||||
|
||||
@ -124,12 +124,7 @@ namespace Электронная_Фармация.UserControls
|
||||
using (var con = new SQLiteConnection(AppConfig.SqliteConnectionString))
|
||||
{
|
||||
con.Open();
|
||||
|
||||
var orderColumns = GetColumns(con, "Orders");
|
||||
var usesNewSchema = orderColumns.Contains("Id_Order") && orderColumns.Contains("OrderedCountItems");
|
||||
|
||||
var q = usesNewSchema ? GetQueryForNewSchema() : GetQueryForOldSchema();
|
||||
using (var cmd = new SQLiteCommand(q, con))
|
||||
using (var cmd = new SQLiteCommand(GetQueryForNewSchema(), con))
|
||||
using (var da = new SQLiteDataAdapter(cmd))
|
||||
{
|
||||
var table = new DataTable();
|
||||
@ -186,26 +181,6 @@ order by oi.[DrugName]", con))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<string> GetColumns(SQLiteConnection con, string tableName)
|
||||
{
|
||||
var columns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
using (var cmd = new SQLiteCommand($"pragma table_info([{tableName}]);", con))
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
var name = reader["name"] as string;
|
||||
if (!string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
columns.Add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
private static string GetQueryForNewSchema()
|
||||
{
|
||||
return @"
|
||||
@ -237,26 +212,5 @@ group by
|
||||
having sum(ifnull(oi.[RefuseItemsCount], 0)) > 0
|
||||
order by o.[OrderDate] desc;";
|
||||
}
|
||||
|
||||
private static string GetQueryForOldSchema()
|
||||
{
|
||||
return @"
|
||||
select
|
||||
o.[OrderNumber] as [Номер заказа],
|
||||
o.[OrderDate] as [Дата],
|
||||
supp.[SuppliersName] as [Поставщик],
|
||||
consign.[ConsigneesName] as [Грузополучатель],
|
||||
o.[OrderedItemsCount] as [К-во товара в заказе],
|
||||
o.[OrderSum] as [Сумма заказа],
|
||||
o.[RefuseItemsCount] as [К-во отказанного товара],
|
||||
o.[RefuseSum] as [Сумма отказа],
|
||||
o.[Comment] as [Комментарий],
|
||||
o.[OrderState] as [Статус]
|
||||
from Orders as o
|
||||
inner join Consignees consign on consign.codeConsignees = o.codeConsignees
|
||||
inner join Suppliers supp on supp.codeSuppliers = o.codeSuppliers
|
||||
where ifnull(o.[RefuseItemsCount], 0) > 0 or ifnull(o.[RefuseSum], 0) > 0
|
||||
order by [OrderDate] desc;";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user