Add invoice request from orders and fix overlapping context menus.

Enable Получить накладную for sent orders, build local invoices without refusals, and stop the tab close menu from covering dedicated grid menus. Also fix price-list Backspace search and refresh the Release dist binaries.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Magomed 2026-07-16 14:00:19 +03:00
parent 1edb2387eb
commit c447048b66
23 changed files with 1318 additions and 389 deletions

View File

@ -1,38 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Электроннаяармация.Classes
{
public class ApiClient
{
private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = null
};
private readonly HttpClient _httpClient;
private readonly string _baseUrl;
private string _token;
public ApiClient(string baseUrl = null)
{
_baseUrl = (baseUrl ?? AppConfig.ApiBaseUrl).TrimEnd('/');
_baseUrl = AppConfig.NormalizeApiBaseUrl(baseUrl ?? AppConfig.ApiBaseUrl);
_httpClient = new HttpClient
{
BaseAddress = new Uri(_baseUrl),
Timeout = TimeSpan.FromSeconds(30)
Timeout = TimeSpan.FromSeconds(60)
};
AppDebugLog.Info("ApiClient", $"Создан клиент. BaseUrl={_baseUrl}, Timeout={_httpClient.Timeout.TotalSeconds}s");
}
/// <summary>
/// Выполняет авторизацию и сохраняет токен
/// </summary>
/// <param name="username">Имя пользователя</param>
/// <param name="password">Пароль</param>
/// <returns>JWT токен</returns>
/// <exception cref="HttpRequestException">При ошибке авторизации</exception>
public string BaseUrl => _baseUrl;
public async Task<string> LoginAsync(string username, string password)
{
var request = new LoginRequest
@ -41,54 +41,65 @@ namespace Электронная_Фармация.Classes
Password = password
};
var path = "/auth/login";
var fullUrl = BuildFullUrl(path);
var requestJson = JsonSerializer.Serialize(request, JsonOptions);
var stopwatch = Stopwatch.StartNew();
AppDebugLog.ApiRequest("POST", fullUrl, requestJson, $"username={username}");
try
{
var response = await _httpClient.PostAsJsonAsync("/auth/login", request);
using (var httpRequest = new HttpRequestMessage(HttpMethod.Post, fullUrl))
{
httpRequest.Content = new StringContent(requestJson, Encoding.UTF8, "application/json");
var response = await _httpClient.SendAsync(httpRequest);
var responseBody = await response.Content.ReadAsStringAsync();
stopwatch.Stop();
if (response.IsSuccessStatusCode)
{
var loginResponse = await response.Content.ReadFromJsonAsync<LoginResponse>();
_token = loginResponse.Token;
return _token;
}
else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
var error = await response.Content.ReadFromJsonAsync<ErrorResponse>();
throw new UnauthorizedAccessException($"Ошибка авторизации: {error?.Error ?? "Неверные учетные данные"}");
}
else
{
var error = await response.Content.ReadFromJsonAsync<ErrorResponse>();
throw new HttpRequestException($"Ошибка HTTP {response.StatusCode}: {error?.Error ?? "Неизвестная ошибка"}");
AppDebugLog.ApiResponse("POST", fullUrl, (int)response.StatusCode, stopwatch.ElapsedMilliseconds, responseBody);
return ParseLoginResponse(response, responseBody);
}
}
catch (TaskCanceledException)
catch (HttpRequestException ex) when (!ex.Message.StartsWith("Ошибка HTTP"))
{
throw new HttpRequestException("Превышено время ожидания ответа сервера");
stopwatch.Stop();
AppDebugLog.ApiFailure("POST", fullUrl, stopwatch.ElapsedMilliseconds, ex);
throw WrapNetworkError(ex, "авторизацию");
}
catch (TaskCanceledException ex)
{
stopwatch.Stop();
AppDebugLog.ApiFailure("POST", fullUrl, stopwatch.ElapsedMilliseconds, ex);
throw WrapNetworkError(ex, "авторизацию");
}
catch (Exception ex)
{
stopwatch.Stop();
if (ex is UnauthorizedAccessException || ex is HttpRequestException)
{
AppDebugLog.ApiFailure("POST", fullUrl, stopwatch.ElapsedMilliseconds, ex);
throw;
}
AppDebugLog.ApiFailure("POST", fullUrl, stopwatch.ElapsedMilliseconds, ex);
throw new HttpRequestException($"Ошибка при авторизации ({_baseUrl}): {ex.Message}", ex);
}
}
/// <summary>
/// Устанавливает токен для последующих запросов
/// </summary>
public void SetToken(string token)
{
_token = token;
_token = string.IsNullOrWhiteSpace(token) ? null : token;
AppDebugLog.Info("ApiClient", $"Токен установлен: {AppDebugLog.MaskToken(_token)}");
}
public string Token => _token;
/// <summary>
/// Проверяет, авторизован ли клиент
/// </summary>
public bool IsAuthenticated => !string.IsNullOrEmpty(_token);
public async Task<PriceSummaryResponse> GetPriceSummaryAsync(string supplierId = null, string regionId = null)
{
if (!IsAuthenticated)
{
throw new InvalidOperationException("Необходимо выполнить авторизацию перед запросом данных");
}
EnsureAuthenticated();
var queryParams = new List<string>();
if (!string.IsNullOrEmpty(supplierId))
@ -100,64 +111,147 @@ namespace Электронная_Фармация.Classes
queryParams.Add($"region_id={Uri.EscapeDataString(regionId)}");
}
var url = "/api/supplier-prices/summary";
var path = "/api/supplier-prices/summary";
if (queryParams.Count > 0)
{
url += "?" + string.Join("&", queryParams);
path += "?" + string.Join("&", queryParams);
}
_httpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _token);
try
{
var response = await _httpClient.GetAsync(url);
if (response.IsSuccessStatusCode)
{
var summary = await response.Content.ReadFromJsonAsync<PriceSummaryResponse>();
return summary;
}
else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
_token = null; // Токен истек или невалиден
var error = await response.Content.ReadFromJsonAsync<ErrorResponse>();
throw new UnauthorizedAccessException($"Сессия истекла: {error?.Error ?? "Необходимо войти заново"}");
}
else
{
var error = await response.Content.ReadFromJsonAsync<ErrorResponse>();
throw new HttpRequestException($"Ошибка HTTP {response.StatusCode}: {error?.Error ?? "Неизвестная ошибка"}");
}
}
catch (TaskCanceledException)
{
throw new HttpRequestException("Превышено время ожидания ответа сервера (таймаут 30 секунд)");
}
var responseBody = await SendAuthorizedGetAsync(path, "загрузку прайса");
var summary = JsonSerializer.Deserialize<PriceSummaryResponse>(responseBody, JsonOptions);
var count = summary?.Summary?.Count ?? 0;
AppDebugLog.Info("ApiClient", $"Прайс получен: {count} позиций");
return summary;
}
public async Task<List<InvoiceApiItem>> GetInvoicesAsync()
{
EnsureAuthenticated();
var responseBody = await SendAuthorizedGetAsync("/api/buyer/invoices", "загрузку накладных");
var items = JsonSerializer.Deserialize<List<InvoiceApiItem>>(responseBody, JsonOptions) ?? new List<InvoiceApiItem>();
AppDebugLog.Info("ApiClient", $"Накладные получены: {items.Count} шт.");
return items;
}
private void EnsureAuthenticated()
{
if (!IsAuthenticated)
{
throw new InvalidOperationException("Необходимо выполнить авторизацию перед запросом накладных");
throw new InvalidOperationException("Необходимо выполнить авторизацию перед запросом данных");
}
}
_httpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _token);
private async Task<string> SendAuthorizedGetAsync(string path, string operationName)
{
var fullUrl = BuildFullUrl(path);
var stopwatch = Stopwatch.StartNew();
var response = await _httpClient.GetAsync("/api/buyer/invoices");
var body = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
AppDebugLog.ApiRequest("GET", fullUrl, extra: $"token={AppDebugLog.MaskToken(_token)}");
try
{
throw new HttpRequestException(
$"Ошибка загрузки накладных: {(int)response.StatusCode} {response.ReasonPhrase}\n{body}");
using (var httpRequest = new HttpRequestMessage(HttpMethod.Get, fullUrl))
{
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token);
var response = await _httpClient.SendAsync(httpRequest);
var responseBody = await response.Content.ReadAsStringAsync();
stopwatch.Stop();
AppDebugLog.ApiResponse("GET", 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);
throw new UnauthorizedAccessException($"Сессия истекла: {error?.Error ?? "Необходимо войти заново"}");
}
var httpError = TryDeserializeError(responseBody);
throw new HttpRequestException($"Ошибка HTTP {response.StatusCode}: {httpError?.Error ?? responseBody}");
}
}
catch (HttpRequestException ex) when (!ex.Message.StartsWith("Ошибка HTTP"))
{
stopwatch.Stop();
AppDebugLog.ApiFailure("GET", fullUrl, stopwatch.ElapsedMilliseconds, ex);
throw WrapNetworkError(ex, operationName);
}
catch (TaskCanceledException ex)
{
stopwatch.Stop();
AppDebugLog.ApiFailure("GET", fullUrl, stopwatch.ElapsedMilliseconds, ex);
throw WrapNetworkError(ex, operationName);
}
catch (Exception ex) when (!(ex is UnauthorizedAccessException) && !(ex is HttpRequestException))
{
stopwatch.Stop();
AppDebugLog.ApiFailure("GET", fullUrl, stopwatch.ElapsedMilliseconds, ex);
throw;
}
}
private string ParseLoginResponse(HttpResponseMessage response, string responseBody)
{
if (response.IsSuccessStatusCode)
{
var loginResponse = JsonSerializer.Deserialize<LoginResponse>(responseBody, JsonOptions);
if (loginResponse == null || string.IsNullOrWhiteSpace(loginResponse.Token))
{
throw new HttpRequestException($"Сервер {_baseUrl} вернул пустой токен авторизации");
}
_token = loginResponse.Token;
AppDebugLog.Info("ApiClient", $"Авторизация успешна. token={AppDebugLog.MaskToken(_token)}");
return _token;
}
return JsonSerializer.Deserialize<List<InvoiceApiItem>>(body) ?? new List<InvoiceApiItem>();
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
var error = TryDeserializeError(responseBody);
throw new UnauthorizedAccessException($"Ошибка авторизации: {error?.Error ?? "Неверные учетные данные"}");
}
var httpError = TryDeserializeError(responseBody);
throw new HttpRequestException($"Ошибка HTTP {response.StatusCode}: {httpError?.Error ?? responseBody}");
}
private string BuildFullUrl(string path)
{
return new Uri(new Uri(_baseUrl + "/"), path.TrimStart('/')).ToString();
}
private static ErrorResponse TryDeserializeError(string body)
{
if (string.IsNullOrWhiteSpace(body))
{
return null;
}
try
{
return JsonSerializer.Deserialize<ErrorResponse>(body, JsonOptions);
}
catch
{
return null;
}
}
private HttpRequestException WrapNetworkError(Exception ex, string operation)
{
var details = ex.InnerException != null && ex.InnerException.Message != ex.Message
? $" ({ex.InnerException.Message})"
: string.Empty;
return new HttpRequestException(
$"Не удалось выполнить {operation}. Сервер: {_baseUrl}{details}. {ex.Message}",
ex);
}
}

View File

@ -9,9 +9,32 @@ namespace Электронная_Фармация.Classes
/// </summary>
public static class AppConfig
{
public const string DefaultApiBaseUrl = "http://195.34.241.84:9988";
public const string DefaultApiBaseUrl = "https://24pharmdata.ru";
private const string LegacyApiBaseUrl = "http://195.34.241.84:9988";
private const string ApiBaseUrlEnvVar = "ELF_API_BASE_URL";
/// <summary>
/// Replaces known dead API URLs in saved user settings.
/// </summary>
public static void MigrateLegacySettings()
{
var current = Settings.Default.ApiBaseUrl;
if (string.IsNullOrWhiteSpace(current))
{
return;
}
var normalized = NormalizeApiBaseUrl(current);
if (string.Equals(normalized, LegacyApiBaseUrl, StringComparison.OrdinalIgnoreCase) ||
string.Equals(normalized, "http://195.34.241.84:9988", StringComparison.OrdinalIgnoreCase))
{
Settings.Default.ApiBaseUrl = DefaultApiBaseUrl;
Settings.Default.stringToken = string.Empty;
Settings.Default.Save();
AppDebugLog.Info("AppConfig", $"ApiBaseUrl мигрирован: {LegacyApiBaseUrl} -> {DefaultApiBaseUrl}");
}
}
public static string ApiBaseUrl
{
get
@ -19,19 +42,36 @@ namespace Электронная_Фармация.Classes
var fromSettings = Settings.Default.ApiBaseUrl;
if (!string.IsNullOrWhiteSpace(fromSettings))
{
return fromSettings.Trim().TrimEnd('/');
return NormalizeApiBaseUrl(fromSettings);
}
var fromEnv = Environment.GetEnvironmentVariable(ApiBaseUrlEnvVar);
if (!string.IsNullOrWhiteSpace(fromEnv))
{
return fromEnv.Trim().TrimEnd('/');
return NormalizeApiBaseUrl(fromEnv);
}
return DefaultApiBaseUrl;
}
}
public static string NormalizeApiBaseUrl(string url)
{
if (string.IsNullOrWhiteSpace(url))
{
return DefaultApiBaseUrl;
}
url = url.Trim().TrimEnd('/');
if (!url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
!url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
{
url = "http://" + url;
}
return url;
}
public static string SqliteDbPath
{
get

View File

@ -0,0 +1,202 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace Электроннаяармация.Classes
{
/// <summary>
/// File logger for API/auth/network debugging.
/// Logs are written to Logs/api-debug-YYYY-MM-DD.log next to the executable.
/// </summary>
public static class AppDebugLog
{
private static readonly object Sync = new object();
private const int MaxBodyLength = 4000;
private const string DisableEnvVar = "ELF_API_DEBUG";
private const string DisableMarkerFile = "api-debug.off";
public static bool IsEnabled
{
get
{
var env = Environment.GetEnvironmentVariable(DisableEnvVar);
if (string.Equals(env, "0", StringComparison.OrdinalIgnoreCase) ||
string.Equals(env, "false", StringComparison.OrdinalIgnoreCase))
{
return false;
}
var markerPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DisableMarkerFile);
return !File.Exists(markerPath);
}
}
public static string LogDirectory =>
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
public static string CurrentLogFilePath =>
Path.Combine(LogDirectory, $"api-debug-{DateTime.Now:yyyy-MM-dd}.log");
public static void Info(string category, string message)
{
Write("INFO", category, message, null);
}
public static void Warning(string category, string message)
{
Write("WARN", category, message, null);
}
public static void Error(string category, string message, Exception exception = null)
{
Write("ERROR", category, message, exception);
}
public static void ApiRequest(string method, string url, string requestBody = null, string extra = null)
{
var message = new StringBuilder();
message.Append(method).Append(" ").Append(url);
if (!string.IsNullOrWhiteSpace(extra))
{
message.Append(" | ").Append(extra);
}
if (!string.IsNullOrWhiteSpace(requestBody))
{
message.Append(" | body=").Append(Truncate(SanitizeJson(requestBody)));
}
Write("REQ", "Api", message.ToString(), null);
}
public static void ApiResponse(string method, string url, int statusCode, long elapsedMs, string responseBody = null)
{
var message = new StringBuilder();
message.Append(method).Append(" ").Append(url);
message.Append(" -> HTTP ").Append(statusCode);
message.Append(" (").Append(elapsedMs).Append(" ms)");
if (!string.IsNullOrWhiteSpace(responseBody))
{
message.Append(" | body=").Append(Truncate(SanitizeJson(responseBody)));
}
Write(statusCode >= 400 ? "RESP-ERR" : "RESP", "Api", message.ToString(), null);
}
public static void ApiFailure(string method, string url, long elapsedMs, Exception exception)
{
var message = $"{method} {url} failed after {elapsedMs} ms: {exception.Message}";
Write("FAIL", "Api", message, exception);
}
public static string MaskToken(string token)
{
if (string.IsNullOrWhiteSpace(token))
{
return "(empty)";
}
if (token.Length <= 8)
{
return "***";
}
return token.Substring(0, 4) + "..." + token.Substring(token.Length - 4);
}
public static string SanitizeJson(string json)
{
if (string.IsNullOrWhiteSpace(json))
{
return string.Empty;
}
var sanitized = Regex.Replace(
json,
@"""password""\s*:\s*""[^""]*""",
@"""password"":""***""",
RegexOptions.IgnoreCase);
sanitized = Regex.Replace(
sanitized,
@"""token""\s*:\s*""([^""]*)""",
m => @"""token"":""" + MaskToken(m.Groups[1].Value) + @"""",
RegexOptions.IgnoreCase);
return sanitized;
}
public static string FormatException(Exception exception)
{
if (exception == null)
{
return string.Empty;
}
var sb = new StringBuilder();
var current = exception;
var depth = 0;
while (current != null && depth < 8)
{
if (depth > 0)
{
sb.AppendLine();
sb.Append(" -> ");
}
sb.Append(current.GetType().Name).Append(": ").Append(current.Message);
current = current.InnerException;
depth++;
}
return sb.ToString();
}
private static void Write(string level, string category, string message, Exception exception)
{
if (!IsEnabled)
{
return;
}
try
{
var line = new StringBuilder();
line.Append(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));
line.Append(" [").Append(level).Append("] ");
line.Append("[").Append(category).Append("] ");
line.Append(message);
if (exception != null)
{
line.Append(" | ").Append(FormatException(exception));
}
lock (Sync)
{
Directory.CreateDirectory(LogDirectory);
File.AppendAllText(CurrentLogFilePath, line + Environment.NewLine, Encoding.UTF8);
}
Debug.WriteLine(line.ToString());
}
catch
{
// Logging must never break the app.
}
}
private static string Truncate(string text)
{
if (string.IsNullOrEmpty(text) || text.Length <= MaxBodyLength)
{
return text ?? string.Empty;
}
return text.Substring(0, MaxBodyLength) + "...[truncated]";
}
}
}

View File

@ -197,6 +197,10 @@ WHERE Orders.OrderState = 'НОВЫЙ'
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())
{
@ -204,18 +208,33 @@ WHERE Orders.OrderState = 'НОВЫЙ'
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token);
var content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.PostAsync(BuyerOrdersEndpoint, content);
string responseString = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
try
{
ToastNotification.ShowError(
$"Ошибка отправки заказа {order.LocalOrderId}: {(int)response.StatusCode} {response.ReasonPhrase}\n{responseString}");
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.Error("DataSender", $"Ошибка отправки заказа {order.LocalOrderId}");
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;
}
MarkOrderSent(order.LocalOrderId);
return true;
}
}

View File

@ -0,0 +1,340 @@
using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Globalization;
namespace Электроннаяармация.Classes
{
public sealed class InvoiceRequestService
{
public string CreateOrUpdateFromOrder(string orderId)
{
if (string.IsNullOrWhiteSpace(orderId))
{
throw new ArgumentException("Не указан заказ для формирования накладной.", nameof(orderId));
}
using (var con = new SQLiteConnection(AppConfig.SqliteConnectionString))
{
con.Open();
string orderNumber;
string supplierName;
string consigneeName;
string orderState;
using (var cmd = new SQLiteCommand(@"
select
o.[OrderNumber],
o.[SupplierName],
ifnull(o.[ConsigneeName], '') as [ConsigneeName],
ifnull(o.[OrderState], '') as [OrderState]
from [Orders] o
where o.[Id_Order] = @orderId;", con))
{
cmd.Parameters.AddWithValue("@orderId", orderId);
using (var reader = cmd.ExecuteReader())
{
if (!reader.Read())
{
throw new InvalidOperationException("Выбранный заказ не найден.");
}
orderNumber = reader["OrderNumber"]?.ToString() ?? string.Empty;
supplierName = reader["SupplierName"]?.ToString() ?? string.Empty;
consigneeName = reader["ConsigneeName"]?.ToString() ?? string.Empty;
orderState = reader["OrderState"]?.ToString() ?? string.Empty;
}
}
if (!string.Equals(orderState, "ОТПРАВЛЕН", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("Получить накладную можно только для заказа со статусом ОТПРАВЛЕН.");
}
var items = new List<InvoiceOrderItem>();
using (var cmd = new SQLiteCommand(@"
select
ifnull([es_code], 0) as [GoodCode],
[DrugName],
[Price],
ifnull([Zakaz], 0) as [Zakaz],
ifnull([RefuseItemsCount], 0) as [RefuseItemsCount],
ifnull([ExpiryPeriod], '') as [ExpiryPeriod]
from [OrderItems]
where [Id_Order] = @orderId
order by [DrugName];", con))
{
cmd.Parameters.AddWithValue("@orderId", orderId);
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
int orderedQty = SafeInt(reader["Zakaz"]);
int refusedQty = SafeInt(reader["RefuseItemsCount"]);
int arrivedQty = Math.Max(orderedQty - refusedQty, 0);
if (arrivedQty <= 0)
{
continue;
}
var price = SafeDecimal(reader["Price"]);
items.Add(new InvoiceOrderItem
{
GoodCode = SafeInt(reader["GoodCode"]),
GoodName = reader["DrugName"]?.ToString() ?? string.Empty,
Price = price,
ArrivedQty = arrivedQty,
ExpiryPeriod = reader["ExpiryPeriod"]?.ToString() ?? string.Empty
});
}
}
}
if (items.Count == 0)
{
throw new InvalidOperationException("Для этого заказа нет фактически пришедших позиций.");
}
int invoiceId;
string invoiceNumber;
using (var findCmd = new SQLiteCommand(@"
select [idInvoice], [InvoiceNumber]
from [Invoice]
where [SourceOrderId] = @orderId
limit 1;", con))
{
findCmd.Parameters.AddWithValue("@orderId", orderId);
using (var reader = findCmd.ExecuteReader())
{
if (reader.Read())
{
invoiceId = Convert.ToInt32(reader["idInvoice"]);
invoiceNumber = reader["InvoiceNumber"]?.ToString() ?? string.Empty;
}
else
{
invoiceId = 0;
invoiceNumber = $"НКЛ-{orderNumber}";
}
}
}
var invoiceDate = DateTime.Now.ToString("yyyy-MM-dd");
var invoiceSum = 0m;
foreach (var item in items)
{
invoiceSum += item.Price * item.ArrivedQty;
}
if (invoiceId == 0)
{
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,
@invoiceSum,
0,
@sourceOrderId,
@sourceOrderNumber,
1
);
select last_insert_rowid();", con))
{
insertCmd.Parameters.AddWithValue("@number", invoiceNumber);
insertCmd.Parameters.AddWithValue("@date", invoiceDate);
insertCmd.Parameters.AddWithValue("@sum", invoiceSum);
insertCmd.Parameters.AddWithValue("@supplier", supplierName);
insertCmd.Parameters.AddWithValue("@consignee", consigneeName);
insertCmd.Parameters.AddWithValue("@invoiceSum", invoiceSum);
insertCmd.Parameters.AddWithValue("@sourceOrderId", orderId);
insertCmd.Parameters.AddWithValue("@sourceOrderNumber", orderNumber);
invoiceId = Convert.ToInt32(insertCmd.ExecuteScalar());
}
}
else
{
using (var updateCmd = new SQLiteCommand(@"
update [Invoice]
set
[InvoiceDate] = @date,
[SumWithoutNDS] = @sum,
[SumWithNDS] = @sum,
[SumNDS] = 0,
[SupplierName] = @supplier,
[ConsigneesName] = @consignee,
[InvoiceSum] = @invoiceSum,
[RefuseSum] = 0,
[SourceOrderNumber] = @sourceOrderNumber,
[IsRequestedInvoice] = 1
where [idInvoice] = @invoiceId;", con))
{
updateCmd.Parameters.AddWithValue("@date", invoiceDate);
updateCmd.Parameters.AddWithValue("@sum", invoiceSum);
updateCmd.Parameters.AddWithValue("@supplier", supplierName);
updateCmd.Parameters.AddWithValue("@consignee", consigneeName);
updateCmd.Parameters.AddWithValue("@invoiceSum", invoiceSum);
updateCmd.Parameters.AddWithValue("@sourceOrderNumber", orderNumber);
updateCmd.Parameters.AddWithValue("@invoiceId", invoiceId);
updateCmd.ExecuteNonQuery();
}
using (var deleteItemsCmd = new SQLiteCommand("delete from [InvoiceItem] where [idInvoice] = @invoiceId;", con))
{
deleteItemsCmd.Parameters.AddWithValue("@invoiceId", invoiceId);
deleteItemsCmd.ExecuteNonQuery();
}
}
foreach (var item in items)
{
decimal lineSum = item.Price * item.ArrivedQty;
using (var itemCmd = 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,
'',
@bestBefore,
'',
0,
''
);", con))
{
itemCmd.Parameters.AddWithValue("@goodCode", item.GoodCode);
itemCmd.Parameters.AddWithValue("@good", item.GoodName);
itemCmd.Parameters.AddWithValue("@invoiceId", invoiceId);
itemCmd.Parameters.AddWithValue("@qty", item.ArrivedQty);
itemCmd.Parameters.AddWithValue("@price", item.Price);
itemCmd.Parameters.AddWithValue("@sum", lineSum);
itemCmd.Parameters.AddWithValue("@bestBefore", item.ExpiryPeriod);
itemCmd.ExecuteNonQuery();
}
}
return orderId;
}
}
private static int SafeInt(object value)
{
if (value == null || value == DBNull.Value)
{
return 0;
}
int result;
return int.TryParse(value.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result)
|| int.TryParse(value.ToString(), NumberStyles.Integer, CultureInfo.CurrentCulture, out result)
? result
: 0;
}
private static decimal SafeDecimal(object value)
{
if (value == null || value == DBNull.Value)
{
return 0m;
}
var text = value.ToString()?.Trim();
if (string.IsNullOrWhiteSpace(text))
{
return 0m;
}
decimal result;
if (decimal.TryParse(text, NumberStyles.Number, CultureInfo.InvariantCulture, out result))
{
return result;
}
text = text.Replace(',', '.');
if (decimal.TryParse(text, NumberStyles.Number, CultureInfo.InvariantCulture, out result))
{
return result;
}
if (decimal.TryParse(text, NumberStyles.Number, CultureInfo.CurrentCulture, out result))
{
return result;
}
return 0m;
}
private sealed class InvoiceOrderItem
{
public int GoodCode { get; set; }
public string GoodName { get; set; }
public decimal Price { get; set; }
public int ArrivedQty { get; set; }
public string ExpiryPeriod { get; set; }
}
}
}

View File

@ -75,6 +75,14 @@ namespace Электронная_Фармация.Classes
textBox.BorderStyle = BorderStyle.FixedSingle;
textBox.Font = theme.FontRegular13;
}
else if (control is RichTextBox richTextBox)
{
richTextBox.BackColor = theme.BackgroundPrimary;
richTextBox.ForeColor = theme.TextPrimary;
richTextBox.BorderStyle = BorderStyle.None;
richTextBox.Font = theme.FontRegular13;
richTextBox.ReadOnly = true;
}
else if (control is NumericUpDown numeric)
{
numeric.BackColor = theme.InputBackground;

View File

@ -103,8 +103,10 @@
<ItemGroup>
<Compile Include="Classes\ApiClient.cs" />
<Compile Include="Classes\AppConfig.cs" />
<Compile Include="Classes\AppDebugLog.cs" />
<Compile Include="Classes\DataSender.cs" />
<Compile Include="Classes\ErrorResponse.cs" />
<Compile Include="Classes\InvoiceRequestService.cs" />
<Compile Include="Classes\InvoiceSyncService.cs" />
<Compile Include="Classes\LoginRequest.cs" />
<Compile Include="Classes\LoginResponse.cs" />

View File

@ -230,7 +230,7 @@
this.tsItemInvoices.Name = "tsItemInvoices";
this.tsItemInvoices.Size = new System.Drawing.Size(224, 32);
this.tsItemInvoices.Text = "Накладные";
this.tsItemInvoices.Visible = false;
this.tsItemInvoices.Visible = true;
this.tsItemInvoices.Click += new System.EventHandler(this.tsItemInvoices_Click);
//
// toolStripSeparator1

View File

@ -51,6 +51,7 @@ namespace Электронная_Фармация
{
new SidebarItem { Key = "price", Text = "ПРАЙС-ЛИСТ", IconGlyph = "\uE8FD" },
new SidebarItem { Key = "orders", Text = "ЗАКАЗЫ", IconGlyph = "\uE719" },
new SidebarItem { Key = "invoices", Text = "НАКЛАДНЫЕ", IconGlyph = "\uE8A5" },
new SidebarItem { Key = "upload", Text = "ЗАГРУЗИТЬ", IconGlyph = "\uE896" },
new SidebarItem { Key = "send", Text = "ОТПРАВИТЬ", IconGlyph = "\uE725" },
new SidebarItem { Key = "refusals", Text = "ОТКАЗЫ", IconGlyph = "\uE711" },
@ -207,6 +208,9 @@ namespace Электронная_Фармация
case "orders":
OpenOrdersTab();
break;
case "invoices":
OpenInvoicesTab();
break;
case "upload":
tsBtnDownloadData_Click(this, EventArgs.Empty);
_appHeader.SetActive("upload");
@ -300,6 +304,11 @@ namespace Электронная_Фармация
return;
}
if (sender is Control clicked && HasDedicatedContextMenu(clicked))
{
return;
}
_documentCloseTabMenuItem.Enabled = !string.IsNullOrEmpty(_documentTabStrip.ActiveTabId);
_documentCloseAllTabsMenuItem.Enabled = _documentTabStrip.Tabs.Count > 0;
@ -307,6 +316,19 @@ namespace Электронная_Фармация
_documentTabMenu.Show(screenPoint);
}
private static bool HasDedicatedContextMenu(Control control)
{
for (var current = control; current != null; current = current.Parent)
{
if (current.ContextMenuStrip != null)
{
return true;
}
}
return false;
}
private void CloseDocumentTab(string tabId)
{
if (_documentContents.TryGetValue(tabId, out var control))
@ -343,6 +365,24 @@ namespace Электронная_Фармация
orders.Show();
}
public void OpenInvoicesTab(string sourceOrderId = null)
{
var existing = _documentTabStrip.FindByNavKey("invoices");
if (existing != null &&
_documentContents.TryGetValue(existing.Id, out var existingControl) &&
existingControl is UCInvoices existingInvoices)
{
existingInvoices.SetSourceOrderFilter(sourceOrderId);
_documentTabStrip.SelectTab(existing.Id);
ShowDocumentTab(existing.Id);
_appHeader.SetActive("invoices");
return;
}
var invoices = new UCInvoices(sourceOrderId);
OpenDocumentTab("Накладные", "invoices", invoices, allowDuplicate: false);
}
private void OpenRefusalsTab()
{
var refusals = new UCRefusals();

View File

@ -146,8 +146,7 @@ namespace Электронная_Фармация
private void tsItemInvoices_Click(object sender, EventArgs e)
{
var ucInvoices = new UCInvoices();
OpenDocumentTab("Накладные", "invoices", ucInvoices, allowDuplicate: true);
OpenInvoicesTab();
}
private void tsItemPriceList_Click(object sender, EventArgs e)

View File

@ -393,6 +393,13 @@ create table if not exists [OrderItems] (
cmdCreateNotExistingsTables.ExecuteNonQuery();
TryAddColumn(conForCheckTables, "Orders", "ConsigneeName", "nvarchar(128)");
TryAddColumn(conForCheckTables, "Invoice", "SupplierName", "nvarchar(256)");
TryAddColumn(conForCheckTables, "Invoice", "ConsigneesName", "nvarchar(128)");
TryAddColumn(conForCheckTables, "Invoice", "InvoiceSum", "nvarchar(64)");
TryAddColumn(conForCheckTables, "Invoice", "RefuseSum", "nvarchar(64)");
TryAddColumn(conForCheckTables, "Invoice", "SourceOrderId", "integer");
TryAddColumn(conForCheckTables, "Invoice", "SourceOrderNumber", "nvarchar(36)");
TryAddColumn(conForCheckTables, "Invoice", "IsRequestedInvoice", "int(1)");
}
catch (Exception ex)
{

View File

@ -1,16 +1,8 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Data.SQLite;
using System.Drawing;
using System.Linq;
using System.Net.Http;
using System.Text;
//using System.Net.Http.json;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using System.Windows.Forms;
using Электроннаяармация.Classes;
@ -25,198 +17,165 @@ namespace Электронная_Фармация.HelpForms
InitializeComponent();
}
string login = Settings.Default.stringLogin.ToString();
string password = Settings.Default.stringPassword.ToString();
string connectionStringToLocalDB = AppConfig.SqliteConnectionString;
private readonly string _login = Settings.Default.stringLogin ?? string.Empty;
private readonly string _password = Settings.Default.stringPassword ?? string.Empty;
private readonly string _connectionStringToLocalDb = AppConfig.SqliteConnectionString;
private async void HF_DownloadDataFromServer_Load(object sender, EventArgs e)
{
UiThemeHelper.ApplyToControlTree(this);
btnCloseThis.Enabled = false;
rtxtDebug.Visible = false;
dataGridView1.Visible = false;
rtxtDebug.Visible = true;
rtxtDebug.ReadOnly = true;
var client = new ApiClient();
AppendStatus($"Сервер: {client.BaseUrl}");
AppendStatus($"Лог: {AppDebugLog.CurrentLogFilePath}");
AppDebugLog.Info("Download", $"Старт загрузки прайса. Сервер={client.BaseUrl}");
try
{
if (!string.IsNullOrWhiteSpace(Settings.Default.stringToken))
{
client.SetToken(Settings.Default.stringToken);
rtxtDebug.Text += "Используется сохранённый токен...\n";
}
else
{
rtxtDebug.Text += "Выполняю авторизацию...\n";
var token = await client.LoginAsync(login, password);
Settings.Default.stringToken = token;
Settings.Default.Save();
rtxtDebug.Text += "Авторизация успешно пройдена\n";
}
await EnsureAuthenticatedAsync(client);
rtxtDebug.Text += "\nЗагружаю сводный прайс для всех поставщиков...";
var allSuppliersSummary = await client.GetPriceSummaryAsync(
supplierId: null,
regionId: string.IsNullOrWhiteSpace(Settings.Default.RegionId) ? null : Settings.Default.RegionId);
rtxtDebug.Text += ($"✓ Получено {allSuppliersSummary.Summary.Count} позиций\n");
AppendStatus("Загружаю сводный прайс для всех поставщиков...");
var allSuppliersSummary = await GetPriceSummaryWithRetryAsync(client);
AppendStatus($"Получено {allSuppliersSummary.Summary?.Count ?? 0} позиций");
DataTable tablePrice = new DataTable();
var tablePrice = BuildPriceTable(allSuppliersSummary);
SavePriceTable(tablePrice);
tablePrice.Columns.Add("supplier_price_id");
tablePrice.Columns.Add("guid_es");
tablePrice.Columns.Add("es_code");
tablePrice.Columns.Add("supplier_id");
tablePrice.Columns.Add("DrugName");
tablePrice.Columns.Add("SupplierName");
tablePrice.Columns.Add("Price");
tablePrice.Columns.Add("Quantity");
DialogResult = DialogResult.OK;
AppendStatus($"Готово. Сохранено {tablePrice.Rows.Count} позиций в локальную базу.");
ToastNotification.ShowSuccess($"Прайс обновлён: {tablePrice.Rows.Count} позиций");
}
catch (UnauthorizedAccessException ex)
{
AppendStatus($"Ошибка авторизации: {ex.Message}");
AppDebugLog.Error("Download", "Ошибка авторизации при загрузке прайса", ex);
ToastNotification.ShowError($"Ошибка авторизации: {ex.Message}");
}
catch (HttpRequestException ex)
{
AppendStatus($"Ошибка сети: {ex.Message}");
AppDebugLog.Error("Download", "Ошибка сети при загрузке прайса", ex);
ToastNotification.ShowError($"Ошибка сети: {ex.Message}");
}
catch (Exception ex)
{
AppendStatus($"Ошибка загрузки прайса: {ex.Message}");
AppDebugLog.Error("Download", "Неожиданная ошибка при загрузке прайса", ex);
ToastNotification.ShowError($"Ошибка загрузки прайса: {ex.Message}");
}
tablePrice.Columns.Add(new DataColumn
{
ColumnName = "ExpiryPeriod",
DataType = typeof(string),
AllowDBNull = true
});
btnCloseThis.Enabled = true;
}
tablePrice.Columns.Add(new DataColumn
{
ColumnName = "Description",
DataType = typeof(string),
AllowDBNull = true
});
private async Task EnsureAuthenticatedAsync(ApiClient client)
{
if (!string.IsNullOrWhiteSpace(Settings.Default.stringToken))
{
client.SetToken(Settings.Default.stringToken);
AppendStatus("Используется сохранённый токен...");
return;
}
//DataColumn colExpiryPeriod = new DataColumn("ExpiryPeriod", typeof(DateTime));
//DataColumn colDescription = new DataColumn("Description", typeof(string));
await LoginAndSaveTokenAsync(client);
}
//colExpiryPeriod.AllowDBNull = true;
//colDescription.AllowDBNull = true;
private async Task LoginAndSaveTokenAsync(ApiClient client)
{
if (string.IsNullOrWhiteSpace(_login) || string.IsNullOrWhiteSpace(_password))
{
throw new UnauthorizedAccessException(
"Не заданы логин и пароль. Выполните авторизацию в меню «Регистрация».");
}
AppendStatus("Выполняю авторизацию...");
var token = await client.LoginAsync(_login, _password);
Settings.Default.stringToken = token;
Settings.Default.Save();
AppendStatus("Авторизация успешно пройдена");
}
////tablePrice.Columns.Add(colExpiryPeriod);
//tablePrice.Columns.Add(colDescription);
private async Task<PriceSummaryResponse> GetPriceSummaryWithRetryAsync(ApiClient client)
{
var regionId = string.IsNullOrWhiteSpace(Settings.Default.RegionId)
? null
: Settings.Default.RegionId;
try
{
return await client.GetPriceSummaryAsync(supplierId: null, regionId: regionId);
}
catch (UnauthorizedAccessException)
{
AppendStatus("Токен недействителен, повторная авторизация...");
Settings.Default.stringToken = string.Empty;
Settings.Default.Save();
client.SetToken(null);
await LoginAndSaveTokenAsync(client);
return await client.GetPriceSummaryAsync(supplierId: null, regionId: regionId);
}
}
tablePrice.Columns.Add("Zakaz");
tablePrice.Columns.Add("SummaZakaza");
private static DataTable BuildPriceTable(PriceSummaryResponse allSuppliersSummary)
{
var tablePrice = new DataTable();
tablePrice.Columns.Add("supplier_price_id");
tablePrice.Columns.Add("guid_es");
tablePrice.Columns.Add("es_code");
tablePrice.Columns.Add("supplier_id");
tablePrice.Columns.Add("DrugName");
tablePrice.Columns.Add("SupplierName");
tablePrice.Columns.Add("Price");
tablePrice.Columns.Add("Quantity");
tablePrice.Columns.Add(new DataColumn
{
ColumnName = "ExpiryPeriod",
DataType = typeof(string),
AllowDBNull = true
});
tablePrice.Columns.Add(new DataColumn
{
ColumnName = "Description",
DataType = typeof(string),
AllowDBNull = true
});
tablePrice.Columns.Add("Zakaz");
tablePrice.Columns.Add("SummaZakaza");
if (allSuppliersSummary?.Summary == null)
{
return tablePrice;
}
#region полная таблица
//tablePrice.Columns.Add("GuidEs");
//tablePrice.Columns.Add("SupplierId");
//tablePrice.Columns.Add("SupplierName");
//tablePrice.Columns.Add("DrugName");
//tablePrice.Columns.Add("Inn");
//tablePrice.Columns.Add("CureForm");
//tablePrice.Columns.Add("Barcode");
//tablePrice.Columns.Add("Price");
//tablePrice.Columns.Add("Quantity");
//tablePrice.Columns.Add("RegionId");
//tablePrice.Columns.Add("RegionName");
//tablePrice.Columns.Add("LastPriceDate");
//tablePrice.Columns.Add("MatchMethod");
//tablePrice.Columns.Add("MatchConfidence");
//tablePrice.Columns.Add("TradeName");
//tablePrice.Columns.Add("Dosage");
//tablePrice.Columns.Add("RegistryPrice");
//tablePrice.Columns.Add("InstructionGuid");
//tablePrice.Columns.Add("Description");
//tablePrice.Columns.Add("StoringCondition");
//tablePrice.Columns.Add("ExpiryPeriod");
//tablePrice.Columns.Add("ProducerName");
//tablePrice.Columns.Add("RegistryDate");
//tablePrice.Columns.Add("EsCode");
//tablePrice.Columns.Add("RegistryStatus");
#endregion
foreach (var item in allSuppliersSummary.Summary)
{
var row = tablePrice.NewRow();
row["supplier_price_id"] = item.SupplierPriceID.ToString();
row["guid_es"] = item.GuidEs.ToString();
row["es_code"] = item.EsCode.ToString();
row["supplier_id"] = item.SupplierId.ToString();
row["DrugName"] = item.DrugName.ToString();
row["SupplierName"] = item.SupplierName.ToString();
row["Price"] = item.Price.ToString();
row["Quantity"] = item.Quantity.ToString();
row["ExpiryPeriod"] = item.ExpiryPeriod == null ? (object)DBNull.Value : item.ExpiryPeriod.ToString();
row["Description"] = item.Description == null ? (object)DBNull.Value : item.Description.ToString();
row["Zakaz"] = string.Empty;
row["SummaZakaza"] = string.Empty;
tablePrice.Rows.Add(row);
}
if (allSuppliersSummary.Summary != null)
{
return tablePrice;
}
foreach (var item in allSuppliersSummary.Summary)
{
DataRow row = tablePrice.NewRow();
row["supplier_price_id"] = item.SupplierPriceID.ToString();
row["guid_es"] = item.GuidEs.ToString();
row["es_code"] = item.EsCode.ToString();
row["supplier_id"] = item.SupplierId.ToString();
row["DrugName"] = item.DrugName.ToString();
row["SupplierName"] = item.SupplierName.ToString();
row["Price"] = item.Price.ToString();
row["Quantity"] = item.Quantity.ToString();
if (item.ExpiryPeriod == null)
{ row["ExpiryPeriod"] = DBNull.Value; }
else { row["ExpiryPeriod"] = item.ExpiryPeriod.ToString(); }
;
if (item.Description == null)
{ row["Description"] = DBNull.Value; }
else { row["Description"] = item.Description.ToString(); }
;
row["Zakaz"] = string.Empty;
row["SummaZakaza"] = string.Empty;
tablePrice.Rows.Add(row);
// DataSetPriceList.Tables.Add(tablePrice);
// DataSetPriceList.WriteXml("PriceList.xml", XmlWriteMode.WriteSchema);
//SQLiteDataAdapter sqlite_data = new SQLiteDataAdapter();
//new SQLiteCommandBuilder(sqlite_data);
////sqlite_data.Fill(tablePrice);
//sqlite_data.Update(tablePrice);
//dataGridView1.DataSource = tablePrice;
#region сводный прайс и статистика
//// Шаг 3: Получение сводного прайса для конкретного поставщика
//if (allSuppliersSummary.Summary.Any())
//{
// var firstSupplierId = allSuppliersSummary.Summary
// .FirstOrDefault(s => !string.IsNullOrEmpty(s.SupplierId))?.SupplierId;
// if (!string.IsNullOrEmpty(firstSupplierId))
// {
// Console.WriteLine($"\nЗагружаю прайс для поставщика {firstSupplierId}...");
// var supplierSummary = await client.GetPriceSummaryAsync(supplierId: firstSupplierId);
// Console.WriteLine($"✓ Получено {supplierSummary.Summary.Count} позиций для поставщика\n");
// }
//}
//// Шаг 4: Статистика
//Console.WriteLine("\n=== Статистика ===");
//var withPrice = allSuppliersSummary.Summary.Where(s => s.Price.HasValue).ToList();
//var withQuantity = allSuppliersSummary.Summary.Where(s => s.Quantity.HasValue).ToList();
//Console.WriteLine($"Всего позиций: {allSuppliersSummary.Summary.Count}");
//Console.WriteLine($"С ценой: {withPrice.Count}");
//Console.WriteLine($"С количеством: {withQuantity.Count}");
//if (withPrice.Any())
//{
// var avgPrice = withPrice.Average(s => s.Price.Value);
// var minPrice = withPrice.Min(s => s.Price.Value);
// var maxPrice = withPrice.Max(s => s.Price.Value);
// Console.WriteLine($"Средняя цена: {avgPrice:F2} ₽");
// Console.WriteLine($"Минимальная цена: {minPrice:F2} ₽");
// Console.WriteLine($"Максимальная цена: {maxPrice:F2} ₽");
//}
#endregion
}
}
string commandToDelete = "delete from [PriceList]";
string commandTODeleteTempOrder = "delete from [TempOrderItems]";
string commandToInsert = @"INSERT INTO [PriceList] (
private void SavePriceTable(DataTable tablePrice)
{
const string commandToDelete = "delete from [PriceList]";
const string commandToDeleteTempOrder = "delete from [TempOrderItems]";
const string commandToInsert = @"INSERT INTO [PriceList] (
[guid_es],
[es_code],
[supplier_id],
@ -244,73 +203,70 @@ VALUES
@Zakaz,
@SummaZakaza,
@supplier_price_id
)
";
)";
using (SQLiteConnection sqliteCon = new SQLiteConnection(connectionStringToLocalDB))
using (var sqliteCon = new SQLiteConnection(_connectionStringToLocalDb))
{
sqliteCon.Open();
using (var cmdDeleteBeforeInsert = new SQLiteCommand(commandToDelete, sqliteCon))
using (var cmdDeleteTempOrder = new SQLiteCommand(commandToDeleteTempOrder, sqliteCon))
{
cmdDeleteBeforeInsert.ExecuteNonQuery();
cmdDeleteTempOrder.ExecuteNonQuery();
}
using (var transaction = sqliteCon.BeginTransaction())
{
foreach (DataRow row in tablePrice.Rows)
{
using (var cmd = new SQLiteCommand(commandToInsert, sqliteCon, transaction))
{
sqliteCon.Open();
SQLiteCommand cmdDeleteBeforeInsert = new SQLiteCommand(commandToDelete, sqliteCon);
SQLiteCommand cmdDeleteTempOrder = new SQLiteCommand(commandTODeleteTempOrder, sqliteCon);
cmdDeleteBeforeInsert.ExecuteNonQuery();
cmdDeleteTempOrder.ExecuteNonQuery();
using (var transaction = sqliteCon.BeginTransaction())
{
foreach (DataRow row in tablePrice.Rows)
{
using (SQLiteCommand cmd = new SQLiteCommand(commandToInsert, sqliteCon, transaction))
{
cmd.Parameters.AddWithValue("@guid_es", row["guid_es"]);
cmd.Parameters.AddWithValue("@es_code", row["es_code"]);
cmd.Parameters.AddWithValue("@supplier_id", row["supplier_id"]);
cmd.Parameters.AddWithValue("@DrugName", row["DrugName"]);
cmd.Parameters.AddWithValue("@SupplierName", row["SupplierName"]);
cmd.Parameters.AddWithValue("@Price", row["Price"]);
cmd.Parameters.AddWithValue("@Quantity", row["Quantity"]);
cmd.Parameters.AddWithValue("@ExpiryPeriod", row["ExpiryPeriod"]);
cmd.Parameters.AddWithValue("@Description", row["Description"]);
cmd.Parameters.AddWithValue("@Zakaz", row["Zakaz"]);
cmd.Parameters.AddWithValue("@SummaZakaza", row["SummaZakaza"]);
cmd.Parameters.AddWithValue("@supplier_price_id", row["supplier_price_id"]);
cmd.ExecuteNonQuery();
}
}
transaction.Commit();
}
sqliteCon.Close();
cmd.Parameters.AddWithValue("@guid_es", row["guid_es"]);
cmd.Parameters.AddWithValue("@es_code", row["es_code"]);
cmd.Parameters.AddWithValue("@supplier_id", row["supplier_id"]);
cmd.Parameters.AddWithValue("@DrugName", row["DrugName"]);
cmd.Parameters.AddWithValue("@SupplierName", row["SupplierName"]);
cmd.Parameters.AddWithValue("@Price", row["Price"]);
cmd.Parameters.AddWithValue("@Quantity", row["Quantity"]);
cmd.Parameters.AddWithValue("@ExpiryPeriod", row["ExpiryPeriod"]);
cmd.Parameters.AddWithValue("@Description", row["Description"]);
cmd.Parameters.AddWithValue("@Zakaz", row["Zakaz"]);
cmd.Parameters.AddWithValue("@SummaZakaza", row["SummaZakaza"]);
cmd.Parameters.AddWithValue("@supplier_price_id", row["supplier_price_id"]);
cmd.ExecuteNonQuery();
}
}
DialogResult = DialogResult.OK;
ToastNotification.ShowSuccess($"Прайс обновлён: {tablePrice.Rows.Count} позиций");
transaction.Commit();
}
}
catch (UnauthorizedAccessException ex)
}
private void AppendStatus(string message)
{
if (InvokeRequired)
{
ToastNotification.ShowError($"Ошибка авторизации: {ex.Message}");
}
catch (HttpRequestException ex)
{
ToastNotification.ShowError($"Ошибка сети: {ex.Message}");
}
catch (Exception ex)
{
ToastNotification.ShowError($"Ошибка загрузки прайса: {ex.Message}");
BeginInvoke(new Action<string>(AppendStatus), message);
return;
}
btnCloseThis.Enabled = true;
rtxtDebug.AppendText(message + Environment.NewLine);
rtxtDebug.SelectionStart = rtxtDebug.TextLength;
rtxtDebug.ScrollToCaret();
rtxtDebug.Refresh();
AppDebugLog.Info("Download", message);
}
private void HF_DownloadDataFromServer_FormClosing(object sender, FormClosingEventArgs e)
{
this.Dispose();
this.Close();
Dispose();
Close();
}
private void btnCloseThis_Click(object sender, EventArgs e)
{
this.Close();
Close();
}
}
}

View File

@ -56,7 +56,8 @@ namespace Электронная_Фармация.HelpForms
loginRequest.Username = strLogin.Trim();
loginRequest.Password = strPassword.Trim();
var client = new ApiClient(apiUrl);
var client = new ApiClient(AppConfig.NormalizeApiBaseUrl(apiUrl));
AppDebugLog.Info("Auth", $"Попытка авторизации. Сервер={client.BaseUrl}, login={loginRequest.Username}");
try
{
@ -66,9 +67,10 @@ namespace Электронная_Фармация.HelpForms
Settings.Default.stringPassword = strPassword;
Settings.Default.stringToken = token;
Settings.Default.LocationId = txtLocationId.Text.Trim();
Settings.Default.ApiBaseUrl = txtApiUrl.Text.Trim();
Settings.Default.ApiBaseUrl = client.BaseUrl;
Settings.Default.Save();
AppDebugLog.Info("Auth", $"Авторизация успешна. token={AppDebugLog.MaskToken(token)}");
ToastNotification.ShowSuccess("Авторизация успешно выполнена");
//MessageBox.Show("Данные были сохранены", "Регистрация программы успешно выполнена", MessageBoxButtons.OK, MessageBoxIcon.Information);
@ -79,13 +81,13 @@ namespace Электронная_Фармация.HelpForms
}
catch (UnauthorizedAccessException ex)
{
AppDebugLog.Error("Auth", "Ошибка авторизации", ex);
ToastNotification.ShowError($"Ошибка авторизации: {ex.Message}");
//MessageBox.Show($"Ошибка авторизации: {ex.Message}");
}
catch (HttpRequestException ex)
{
AppDebugLog.Error("Auth", "Ошибка сети при авторизации", ex);
ToastNotification.ShowError($"Ошибка сети: {ex.Message}");
//MessageBox.Show($"Ошибка сети: {ex.Message}");
}
}
else

View File

@ -1,5 +1,7 @@
using System;
using System.Windows.Forms;
using System.Net;
using Электроннаяармация.Classes;
namespace Электроннаяармация
{
@ -8,6 +10,9 @@ namespace Электронная_Фармация
[STAThread]
static void Main()
{
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
AppConfig.MigrateLegacySettings();
AppDebugLog.Info("App", $"Запуск. ApiBaseUrl={AppConfig.ApiBaseUrl}, Log={AppDebugLog.CurrentLogFilePath}");
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new ElectroPharmacy());

View File

@ -85,7 +85,7 @@ namespace Электронная_Фармация.Properties {
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("http://195.34.241.84:9988")]
[global::System.Configuration.DefaultSettingValueAttribute("https://24pharmdata.ru")]
public string ApiBaseUrl {
get {
return ((string)(this["ApiBaseUrl"]));

View File

@ -18,7 +18,7 @@
<Value Profile="(Default)" />
</Setting>
<Setting Name="ApiBaseUrl" Type="System.String" Scope="User">
<Value Profile="(Default)">http://195.34.241.84:9988</Value>
<Value Profile="(Default)">https://24pharmdata.ru</Value>
</Setting>
<Setting Name="RegionId" Type="System.String" Scope="User">
<Value Profile="(Default)" />

View File

@ -46,13 +46,54 @@ namespace Электронная_Фармация.UserControls
ApplyToolbarLayout();
ModernButtonStyles.ApplyClear(btnClearGoodSearch);
ModernButtonStyles.ApplyClear(btnClearOrderNumberFilter);
InitializeOrderContextMenu();
label3.BringToFront();
panelToolbar.Resize += (_, __) => ApplyToolbarLayout();
}
private void InitializeOrderContextMenu()
{
if (_ordersMenu != null)
{
return;
}
_getInvoiceMenuItem = new ToolStripMenuItem("Получить накладную");
_getInvoiceMenuItem.Click += (_, __) => RequestInvoiceForSelectedOrder();
_ordersMenu = new ContextMenuStrip();
_ordersMenu.Items.Add(_getInvoiceMenuItem);
_ordersMenu.Opening += OrdersMenu_Opening;
DGVOrders.ContextMenuStrip = _ordersMenu;
}
private void OrdersMenu_Opening(object sender, CancelEventArgs e)
{
var pt = DGVOrders.PointToClient(Cursor.Position);
var hit = DGVOrders.HitTest(pt.X, pt.Y);
if (hit.RowIndex < 0 || hit.RowIndex >= DGVOrders.Rows.Count)
{
e.Cancel = true;
return;
}
DGVOrders.ClearSelection();
DGVOrders.Rows[hit.RowIndex].Selected = true;
if (hit.ColumnIndex >= 0)
{
DGVOrders.CurrentCell = DGVOrders.Rows[hit.RowIndex].Cells[hit.ColumnIndex];
}
var status = DGVOrders.Rows[hit.RowIndex].Cells["Статус"]?.Value?.ToString();
_getInvoiceMenuItem.Enabled = string.Equals(status, "ОТПРАВЛЕН", StringComparison.OrdinalIgnoreCase);
}
private ModernComboBox _comboOrderStatus;
private ModernComboBox _comboConsignees;
private ModernButton _btnSendOrder;
private ContextMenuStrip _ordersMenu;
private ToolStripMenuItem _getInvoiceMenuItem;
private void InitOrderToolbar()
{
@ -319,6 +360,44 @@ namespace Электронная_Фармация.UserControls
ApplyOrderFilters();
}
private void RequestInvoiceForSelectedOrder()
{
if (DGVOrders.SelectedRows.Count == 0)
{
ToastNotification.ShowCustom("Выберите отправленный заказ.", Color.DarkOrange, Color.White);
return;
}
var selectedRow = DGVOrders.SelectedRows[0];
var status = selectedRow.Cells["Статус"]?.Value?.ToString();
if (!string.Equals(status, "ОТПРАВЛЕН", StringComparison.OrdinalIgnoreCase))
{
ToastNotification.ShowCustom("Накладную можно получить только для отправленного заказа.", Color.DarkOrange, Color.White);
return;
}
var orderId = selectedRow.Cells["ИД заказа"]?.Value?.ToString();
if (string.IsNullOrWhiteSpace(orderId))
{
ToastNotification.ShowError("Не удалось определить идентификатор заказа.");
return;
}
try
{
var service = new InvoiceRequestService();
service.CreateOrUpdateFromOrder(orderId);
ToastNotification.ShowSuccess("Накладная добавлена в раздел \"Накладные\".");
var shell = FindForm() as ElectroPharmacy;
shell?.OpenInvoicesTab(orderId);
}
catch (Exception ex)
{
ToastNotification.ShowError($"Не удалось получить накладную: {ex.Message}");
}
}
void LoadOrders(string dateFrom, string dateTo, string filters)
{
string connectionStringToLocalDB = AppConfig.SqliteConnectionString;

View File

@ -15,9 +15,28 @@ namespace Электронная_Фармация.UserControls
{
public partial class UCInvoices : UserControl
{
private string _initialSourceOrderId;
private ContextMenuStrip _invoiceMenu;
private ToolStripMenuItem _exportInvoiceMenuItem;
public UCInvoices()
: this(null)
{
}
public UCInvoices(string sourceOrderId)
{
InitializeComponent();
_initialSourceOrderId = sourceOrderId;
}
public void SetSourceOrderFilter(string sourceOrderId)
{
_initialSourceOrderId = sourceOrderId;
if (IsHandleCreated)
{
getDatesFromCalendar();
}
}
void getDatesFromCalendar()
@ -94,52 +113,80 @@ namespace Электронная_Фармация.UserControls
void loadInvoicesList(string InvoicesDateFrom, string InvoicesDateTo, string dateFromYearFormat)
{
string connectionStringToLocalDB = AppConfig.SqliteConnectionString;
string filterAddons = $"where i.InvoiceDate between date('{InvoicesDateFrom}') and date('{InvoicesDateTo}')";
if (txtInvoiceNumber.Text != "" & txtInvoiceNumber.Text != "Номер накладной")
{
filterAddons += $" and i.InvoiceNumber like '%{txtInvoiceNumber.Text}%'";
}
if (comboSuppliers.Text != "" & comboSuppliers.Text != "Поставщик")
{
filterAddons += $" and s.SuppliersName like '%{comboSuppliers.Text}%'";
}
if (comboConsignees.Text != "" & comboConsignees.Text != "Грузополучатель")
{
filterAddons += $" and c.ConsigneesName like '%{comboConsignees.Text}%'";
}
string qShowMeListOfInvoices = $@"
select i.InvoiceNumber as [Номер],
i.invoiceDate as [Дата],
s.SuppliersName as [Поставщик],
c.ConsigneesName as [Грузополучатель],
c.ConsigneesAddress as [Адрес ГП],
i.SumWithoutNDS as [Сумма без НДС],
i.SumWithNDS as [Сумма с НДС],
i.SumNDS as [Сумма НДС]
from [Invoice] i
inner join [Suppliers] s on s.codeSuppliers = i.CodeSupplier
inner join [Consignees] c on c.codeConsignees = i.CodeConsignees
{filterAddons}
order by i.InvoiceDate asc
";
using (SQLiteConnection conInvoices = new SQLiteConnection(connectionStringToLocalDB))
{
try
{
conInvoices.Open();
SQLiteCommand cmdShowInvoicesList = new SQLiteCommand(qShowMeListOfInvoices, conInvoices);
var sql = @"
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 [Грузополучатель],
i.[SourceOrderNumber] as [Номер заказа],
coalesce(i.[InvoiceSum], i.[SumWithNDS], i.[SumWithoutNDS], 0) as [Сумма],
coalesce(i.[RefuseSum], 0) as [Сумма отказа]
from [Invoice] i
where date(i.[InvoiceDate]) between date(@dateFrom) and date(@dateTo)";
if (txtInvoiceNumber.Text != "" && txtInvoiceNumber.Text != "Номер накладной")
{
sql += " and i.[InvoiceNumber] like @invoiceNumber";
}
if (comboSuppliers.Text != "" && comboSuppliers.Text != "Поставщик")
{
sql += " and coalesce(nullif(i.[SupplierName], ''), (select s.[SuppliersName] from [Suppliers] s where s.[codeSuppliers] = i.[CodeSupplier])) 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";
}
if (!string.IsNullOrWhiteSpace(_initialSourceOrderId))
{
sql += " and cast(i.[SourceOrderId] as text) = @sourceOrderId";
}
sql += " order by date(i.[InvoiceDate]) desc, i.[idInvoice] desc;";
SQLiteCommand cmdShowInvoicesList = new SQLiteCommand(sql, conInvoices);
cmdShowInvoicesList.Parameters.AddWithValue("@dateFrom", InvoicesDateFrom);
cmdShowInvoicesList.Parameters.AddWithValue("@dateTo", InvoicesDateTo);
if (txtInvoiceNumber.Text != "" && txtInvoiceNumber.Text != "Номер накладной")
{
cmdShowInvoicesList.Parameters.AddWithValue("@invoiceNumber", $"%{txtInvoiceNumber.Text}%");
}
if (comboSuppliers.Text != "" && comboSuppliers.Text != "Поставщик")
{
cmdShowInvoicesList.Parameters.AddWithValue("@supplierName", $"%{comboSuppliers.Text}%");
}
if (comboConsignees.Text != "" && comboConsignees.Text != "Грузополучатель")
{
cmdShowInvoicesList.Parameters.AddWithValue("@consigneeName", $"%{comboConsignees.Text}%");
}
if (!string.IsNullOrWhiteSpace(_initialSourceOrderId))
{
cmdShowInvoicesList.Parameters.AddWithValue("@sourceOrderId", _initialSourceOrderId);
}
DataTable tableInvoices = new DataTable();
SQLiteDataAdapter daInvoicesList = new SQLiteDataAdapter(cmdShowInvoicesList);
daInvoicesList.Fill(tableInvoices);
dgvInvoice.DataSource = tableInvoices;
if (dgvInvoice.Columns.Count > 0)
{
dgvInvoice.Columns["ИД накладной"].Visible = false;
}
}
catch (Exception ex)
{
@ -162,6 +209,7 @@ order by i.InvoiceDate asc
{
UiThemeHelper.ApplyToControlTree(this);
Font = new Font("Segoe UI", 9.75f, FontStyle.Regular);
InitializeInvoiceContextMenu();
button1.Visible = true;
button1.Text = "Обновить с сервера";
@ -196,6 +244,43 @@ order by i.InvoiceDate asc
}
}
private void InitializeInvoiceContextMenu()
{
if (_invoiceMenu != null)
{
return;
}
_exportInvoiceMenuItem = new ToolStripMenuItem("Экспорт");
_exportInvoiceMenuItem.Click += (_, __) => ExportSelectedInvoice();
_invoiceMenu = new ContextMenuStrip();
_invoiceMenu.Items.Add(_exportInvoiceMenuItem);
_invoiceMenu.Opening += InvoiceMenu_Opening;
dgvInvoice.ContextMenuStrip = _invoiceMenu;
}
private void InvoiceMenu_Opening(object sender, CancelEventArgs e)
{
var pt = dgvInvoice.PointToClient(Cursor.Position);
var hit = dgvInvoice.HitTest(pt.X, pt.Y);
if (hit.RowIndex < 0 || hit.RowIndex >= dgvInvoice.Rows.Count)
{
e.Cancel = true;
return;
}
dgvInvoice.ClearSelection();
dgvInvoice.Rows[hit.RowIndex].Selected = true;
if (hit.ColumnIndex >= 0)
{
dgvInvoice.CurrentCell = dgvInvoice.Rows[hit.RowIndex].Cells[hit.ColumnIndex];
}
_exportInvoiceMenuItem.Enabled = true;
}
private void dtpDateTo_Leave(object sender, EventArgs e)
{
@ -226,28 +311,22 @@ order by i.InvoiceDate asc
void selectInvoice()
{
if (dgvInvoice.Rows.Count > 0)
if (dgvInvoice.Rows.Count > 0 && dgvInvoice.CurrentCell != null)
{
int SelectedInvoiceIndex = dgvInvoice.CurrentCell.RowIndex;
string SelectedInvoiceNumber = dgvInvoice.Rows[SelectedInvoiceIndex].Cells[0].Value.ToString();
loadInvoiceItems(SelectedInvoiceNumber);
string SelectedInvoiceId = dgvInvoice.Rows[SelectedInvoiceIndex].Cells["ИД накладной"]?.Value?.ToString() ?? string.Empty;
loadInvoiceItems(SelectedInvoiceId);
}
}
void loadInvoiceItems(string InvoiceNumber)
void loadInvoiceItems(string invoiceId)
{
string filterAddons = string.Empty;
string InvNum = InvoiceNumber;
if (dgvInvoice.Rows.Count > 0)
{
if (InvNum != "" | InvoiceNumber != string.Empty)
if (!string.IsNullOrWhiteSpace(invoiceId))
{
filterAddons = $"where i.InvoiceNumber = '{InvNum}'";
string connectionStringToLocalDB = AppConfig.SqliteConnectionString;
string qShowMeInvoiceItems = $@"
string qShowMeInvoiceItems = @"
select
ii.GoodCode as [Код товара],
ii.Good as [Товар],
@ -270,20 +349,22 @@ ii.Sertificate as [Сертификат],
ii.Marked as [Признак маркировки],
ii.GTIN as [GTIN]
from InvoiceItem ii
inner join Invoice i on i.idInvoice = ii.idInvoice
{filterAddons}
";
where ii.idInvoice = @invoiceId;";
using (SQLiteConnection conInvoiceItems = new SQLiteConnection(connectionStringToLocalDB))
{
try
{
conInvoiceItems.Open();
SQLiteCommand cmdShowMeInvoiceItems = new SQLiteCommand(qShowMeInvoiceItems, conInvoiceItems);
cmdShowMeInvoiceItems.Parameters.AddWithValue("@invoiceId", invoiceId);
DataTable tableInvoiceItems = new DataTable();
SQLiteDataAdapter daInvoiceItems = new SQLiteDataAdapter(cmdShowMeInvoiceItems);
daInvoiceItems.Fill(tableInvoiceItems);
dgvInvoiceItems.DataSource = tableInvoiceItems;
dgvInvoiceItems.Columns[10].ValueType = typeof(bool);
if (dgvInvoiceItems.Columns.Count > 10)
{
dgvInvoiceItems.Columns[10].ValueType = typeof(bool);
}
}
catch (Exception ex)
{
@ -303,6 +384,21 @@ inner join Invoice i on i.idInvoice = ii.idInvoice
}
}
private void ExportSelectedInvoice()
{
if (dgvInvoice.SelectedRows.Count == 0)
{
ToastNotification.ShowCustom("Выберите накладную для экспорта.", Color.DarkOrange, Color.White);
return;
}
var invoiceNumber = dgvInvoice.SelectedRows[0].Cells["Номер"]?.Value?.ToString() ?? string.Empty;
ToastNotification.ShowCustom(
$"Экспорт накладной {invoiceNumber} будет добавлен после согласования формата.",
Color.SteelBlue,
Color.White);
}
private void txtInvoiceNumber_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == (char)Keys.Enter)

View File

@ -824,15 +824,46 @@ and tempOrder.GoodName = PriceList.GoodName
private void dgvPriceList_KeyPress(object sender, KeyPressEventArgs e)
{
int RowIndex = dgvPriceList.SelectedRows[0].Index;
int RowIndex = -1;
string idPriceListItem = string.Empty;
string idPriceListItem = dgvPriceList.Rows[RowIndex].Cells[11].Value.ToString();
if (dgvPriceList.SelectedRows.Count > 0)
{
RowIndex = dgvPriceList.SelectedRows[0].Index;
if (RowIndex >= 0 && RowIndex < dgvPriceList.Rows.Count)
{
idPriceListItem = dgvPriceList.Rows[RowIndex].Cells[11].Value?.ToString() ?? string.Empty;
}
}
if (e.KeyChar == (Char)Keys.Back && GoodsFilter.Length > 0)
{
GoodsFilter = GoodsFilter.Substring(0, GoodsFilter.Length - 1);
txtSearchGoodNameInPriceList.Text = GoodsFilter;
ApplyPriceListFilters();
if (GoodsFilter.Length == 0)
{
timerForResetSearch.Enabled = false;
timerForResetSearch.Stop();
}
else
{
timerForResetSearch.Enabled = true;
timerForResetSearch.Start();
}
return;
}
if (timerForResetSearch.Enabled == false)
{
deleteGood(RowIndex, idPriceListItem);
timerForResetSearch.Enabled = true;
timerForResetSearch.Start();
if (RowIndex >= 0 && !string.IsNullOrEmpty(idPriceListItem))
{
deleteGood(RowIndex, idPriceListItem);
timerForResetSearch.Enabled = true;
timerForResetSearch.Start();
}
}
if ((e.KeyChar >= 'A' && e.KeyChar <= 'Z') || (e.KeyChar >= 'a' && e.KeyChar <= 'z') || (e.KeyChar >= 'А' && e.KeyChar <= 'Я') || (e.KeyChar >= 'а' && e.KeyChar <= 'я') || (e.KeyChar == (Char)Keys.Subtract))
@ -874,6 +905,7 @@ and tempOrder.GoodName = PriceList.GoodName
}
#endregion
}
return;
}
else if ((e.KeyChar >= (Char)Keys.D1 && e.KeyChar <= (Char)Keys.D9) ||
(e.KeyChar >= (Char)Keys.NumPad1 && e.KeyChar <= (Char)Keys.NumPad9) || (e.KeyChar == (Char)Keys.D0 && dgvPriceList.Rows[RowIndex].Cells[10].Value.ToString().Length > 1) || (e.KeyChar == (Char)Keys.NumPad0 && dgvPriceList.Rows[RowIndex].Cells[10].Value.ToString().Length > 1))
@ -923,9 +955,11 @@ and tempOrder.GoodName = PriceList.GoodName
txtSearchGoodNameInPriceList.Text = string.Empty;
GoodsFilter = string.Empty;
ApplyPriceListFilters();
return;
}
else if (e.KeyChar == (Char)Keys.Back)
{
if (RowIndex < 0) return;
string countToBuy = dgvPriceList.Rows[RowIndex].Cells[9].Value.ToString();
if (countToBuy.Length > 1)
@ -945,14 +979,16 @@ and tempOrder.GoodName = PriceList.GoodName
}
else if (e.KeyChar == (Char)Keys.Delete)
{
if (RowIndex < 0 || string.IsNullOrEmpty(idPriceListItem)) return;
deleteGood(RowIndex, idPriceListItem);
}
else if (dgvPriceList.Rows[RowIndex].Cells[9].Value.ToString() == string.Empty && e.KeyChar == (Char)Keys.D0)
else if (RowIndex >= 0 && dgvPriceList.Rows[RowIndex].Cells[9].Value?.ToString() == string.Empty && e.KeyChar == (Char)Keys.D0)
{
}
else if (e.KeyChar == (Char)Keys.Down || e.KeyChar == (Char)Keys.Up)
{
if (RowIndex < 0 || string.IsNullOrEmpty(idPriceListItem)) return;
timerForResetSearch.Enabled = false;
timerForResetSearch.Stop();
@ -966,7 +1002,9 @@ and tempOrder.GoodName = PriceList.GoodName
}
}
string summaZakaza = dgvPriceList.Rows[RowIndex].Cells[10].Value.ToString();
if (RowIndex < 0 || RowIndex >= dgvPriceList.Rows.Count) return;
string summaZakaza = dgvPriceList.Rows[RowIndex].Cells[10].Value?.ToString() ?? string.Empty;
if (summaZakaza.Trim().Length > 0)
@ -1662,6 +1700,8 @@ and SumOrderedItems = '{SumOrder}'";
private void timerForResetSearch_Tick(object sender, EventArgs e)
{
txtSearchGoodNameInPriceList.Text = string.Empty;
GoodsFilter = string.Empty;
ApplyPriceListFilters();
timerForResetSearch.Enabled = false;
timerForResetSearch.Stop();
}