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:
parent
1edb2387eb
commit
c447048b66
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,38 +1,38 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Diagnostics;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Net.Http.Json;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Электронная_Фармация.Classes
|
namespace Электронная_Фармация.Classes
|
||||||
{
|
{
|
||||||
public class ApiClient
|
public class ApiClient
|
||||||
{
|
{
|
||||||
|
private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = null
|
||||||
|
};
|
||||||
|
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
private readonly string _baseUrl;
|
private readonly string _baseUrl;
|
||||||
private string _token;
|
private string _token;
|
||||||
|
|
||||||
public ApiClient(string baseUrl = null)
|
public ApiClient(string baseUrl = null)
|
||||||
{
|
{
|
||||||
_baseUrl = (baseUrl ?? AppConfig.ApiBaseUrl).TrimEnd('/');
|
_baseUrl = AppConfig.NormalizeApiBaseUrl(baseUrl ?? AppConfig.ApiBaseUrl);
|
||||||
_httpClient = new HttpClient
|
_httpClient = new HttpClient
|
||||||
{
|
{
|
||||||
BaseAddress = new Uri(_baseUrl),
|
Timeout = TimeSpan.FromSeconds(60)
|
||||||
Timeout = TimeSpan.FromSeconds(30)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AppDebugLog.Info("ApiClient", $"Создан клиент. BaseUrl={_baseUrl}, Timeout={_httpClient.Timeout.TotalSeconds}s");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public string BaseUrl => _baseUrl;
|
||||||
/// Выполняет авторизацию и сохраняет токен
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="username">Имя пользователя</param>
|
|
||||||
/// <param name="password">Пароль</param>
|
|
||||||
/// <returns>JWT токен</returns>
|
|
||||||
/// <exception cref="HttpRequestException">При ошибке авторизации</exception>
|
|
||||||
public async Task<string> LoginAsync(string username, string password)
|
public async Task<string> LoginAsync(string username, string password)
|
||||||
{
|
{
|
||||||
var request = new LoginRequest
|
var request = new LoginRequest
|
||||||
@ -41,54 +41,65 @@ namespace Электронная_Фармация.Classes
|
|||||||
Password = password
|
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
|
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)
|
AppDebugLog.ApiResponse("POST", fullUrl, (int)response.StatusCode, stopwatch.ElapsedMilliseconds, responseBody);
|
||||||
{
|
return ParseLoginResponse(response, responseBody);
|
||||||
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 ?? "Неизвестная ошибка"}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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)
|
public void SetToken(string token)
|
||||||
{
|
{
|
||||||
_token = token;
|
_token = string.IsNullOrWhiteSpace(token) ? null : token;
|
||||||
|
AppDebugLog.Info("ApiClient", $"Токен установлен: {AppDebugLog.MaskToken(_token)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Token => _token;
|
public string Token => _token;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Проверяет, авторизован ли клиент
|
|
||||||
/// </summary>
|
|
||||||
public bool IsAuthenticated => !string.IsNullOrEmpty(_token);
|
public bool IsAuthenticated => !string.IsNullOrEmpty(_token);
|
||||||
|
|
||||||
public async Task<PriceSummaryResponse> GetPriceSummaryAsync(string supplierId = null, string regionId = null)
|
public async Task<PriceSummaryResponse> GetPriceSummaryAsync(string supplierId = null, string regionId = null)
|
||||||
{
|
{
|
||||||
if (!IsAuthenticated)
|
EnsureAuthenticated();
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Необходимо выполнить авторизацию перед запросом данных");
|
|
||||||
}
|
|
||||||
|
|
||||||
var queryParams = new List<string>();
|
var queryParams = new List<string>();
|
||||||
if (!string.IsNullOrEmpty(supplierId))
|
if (!string.IsNullOrEmpty(supplierId))
|
||||||
@ -100,64 +111,147 @@ namespace Электронная_Фармация.Classes
|
|||||||
queryParams.Add($"region_id={Uri.EscapeDataString(regionId)}");
|
queryParams.Add($"region_id={Uri.EscapeDataString(regionId)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = "/api/supplier-prices/summary";
|
var path = "/api/supplier-prices/summary";
|
||||||
if (queryParams.Count > 0)
|
if (queryParams.Count > 0)
|
||||||
{
|
{
|
||||||
url += "?" + string.Join("&", queryParams);
|
path += "?" + string.Join("&", queryParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
_httpClient.DefaultRequestHeaders.Authorization =
|
var responseBody = await SendAuthorizedGetAsync(path, "загрузку прайса");
|
||||||
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _token);
|
var summary = JsonSerializer.Deserialize<PriceSummaryResponse>(responseBody, JsonOptions);
|
||||||
|
var count = summary?.Summary?.Count ?? 0;
|
||||||
try
|
AppDebugLog.Info("ApiClient", $"Прайс получен: {count} позиций");
|
||||||
{
|
|
||||||
var response = await _httpClient.GetAsync(url);
|
|
||||||
|
|
||||||
if (response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
var summary = await response.Content.ReadFromJsonAsync<PriceSummaryResponse>();
|
|
||||||
return summary;
|
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 секунд)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<InvoiceApiItem>> GetInvoicesAsync()
|
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)
|
if (!IsAuthenticated)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Необходимо выполнить авторизацию перед запросом накладных");
|
throw new InvalidOperationException("Необходимо выполнить авторизацию перед запросом данных");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_httpClient.DefaultRequestHeaders.Authorization =
|
private async Task<string> SendAuthorizedGetAsync(string path, string operationName)
|
||||||
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _token);
|
|
||||||
|
|
||||||
var response = await _httpClient.GetAsync("/api/buyer/invoices");
|
|
||||||
var body = await response.Content.ReadAsStringAsync();
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
{
|
||||||
throw new HttpRequestException(
|
var fullUrl = BuildFullUrl(path);
|
||||||
$"Ошибка загрузки накладных: {(int)response.StatusCode} {response.ReasonPhrase}\n{body}");
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
AppDebugLog.ApiRequest("GET", fullUrl, extra: $"token={AppDebugLog.MaskToken(_token)}");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
return JsonSerializer.Deserialize<List<InvoiceApiItem>>(body) ?? new List<InvoiceApiItem>();
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,9 +9,32 @@ namespace Электронная_Фармация.Classes
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class AppConfig
|
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";
|
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
|
public static string ApiBaseUrl
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -19,19 +42,36 @@ namespace Электронная_Фармация.Classes
|
|||||||
var fromSettings = Settings.Default.ApiBaseUrl;
|
var fromSettings = Settings.Default.ApiBaseUrl;
|
||||||
if (!string.IsNullOrWhiteSpace(fromSettings))
|
if (!string.IsNullOrWhiteSpace(fromSettings))
|
||||||
{
|
{
|
||||||
return fromSettings.Trim().TrimEnd('/');
|
return NormalizeApiBaseUrl(fromSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
var fromEnv = Environment.GetEnvironmentVariable(ApiBaseUrlEnvVar);
|
var fromEnv = Environment.GetEnvironmentVariable(ApiBaseUrlEnvVar);
|
||||||
if (!string.IsNullOrWhiteSpace(fromEnv))
|
if (!string.IsNullOrWhiteSpace(fromEnv))
|
||||||
{
|
{
|
||||||
return fromEnv.Trim().TrimEnd('/');
|
return NormalizeApiBaseUrl(fromEnv);
|
||||||
}
|
}
|
||||||
|
|
||||||
return DefaultApiBaseUrl;
|
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
|
public static string SqliteDbPath
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|||||||
202
src/ElectronicPharmacy/Classes/AppDebugLog.cs
Normal file
202
src/ElectronicPharmacy/Classes/AppDebugLog.cs
Normal 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]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -197,6 +197,10 @@ WHERE Orders.OrderState = 'НОВЫЙ'
|
|||||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||||
};
|
};
|
||||||
string json = JsonSerializer.Serialize(order, options);
|
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())
|
using (var client = new HttpClient())
|
||||||
{
|
{
|
||||||
@ -204,19 +208,34 @@ WHERE Orders.OrderState = 'НОВЫЙ'
|
|||||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token);
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token);
|
||||||
|
|
||||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||||
|
try
|
||||||
|
{
|
||||||
HttpResponseMessage response = await client.PostAsync(BuyerOrdersEndpoint, content);
|
HttpResponseMessage response = await client.PostAsync(BuyerOrdersEndpoint, content);
|
||||||
string responseString = await response.Content.ReadAsStringAsync();
|
string responseString = await response.Content.ReadAsStringAsync();
|
||||||
|
stopwatch.Stop();
|
||||||
|
|
||||||
|
AppDebugLog.ApiResponse("POST", fullUrl, (int)response.StatusCode, stopwatch.ElapsedMilliseconds, responseString);
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
|
AppDebugLog.Error("DataSender", $"Ошибка отправки заказа {order.LocalOrderId}");
|
||||||
ToastNotification.ShowError(
|
ToastNotification.ShowError(
|
||||||
$"Ошибка отправки заказа {order.LocalOrderId}: {(int)response.StatusCode} {response.ReasonPhrase}\n{responseString}");
|
$"Ошибка отправки заказа {order.LocalOrderId}: {(int)response.StatusCode} {response.ReasonPhrase}\n{responseString}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
MarkOrderSent(order.LocalOrderId);
|
MarkOrderSent(order.LocalOrderId);
|
||||||
|
AppDebugLog.Info("DataSender", $"Заказ {order.LocalOrderId} отправлен");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
stopwatch.Stop();
|
||||||
|
AppDebugLog.ApiFailure("POST", fullUrl, stopwatch.ElapsedMilliseconds, ex);
|
||||||
|
ToastNotification.ShowError($"Ошибка сети при отправке заказа {order.LocalOrderId}: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void MarkOrderSent(string localOrderId)
|
private static void MarkOrderSent(string localOrderId)
|
||||||
|
|||||||
340
src/ElectronicPharmacy/Classes/InvoiceRequestService.cs
Normal file
340
src/ElectronicPharmacy/Classes/InvoiceRequestService.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -75,6 +75,14 @@ namespace Электронная_Фармация.Classes
|
|||||||
textBox.BorderStyle = BorderStyle.FixedSingle;
|
textBox.BorderStyle = BorderStyle.FixedSingle;
|
||||||
textBox.Font = theme.FontRegular13;
|
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)
|
else if (control is NumericUpDown numeric)
|
||||||
{
|
{
|
||||||
numeric.BackColor = theme.InputBackground;
|
numeric.BackColor = theme.InputBackground;
|
||||||
|
|||||||
@ -103,8 +103,10 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Classes\ApiClient.cs" />
|
<Compile Include="Classes\ApiClient.cs" />
|
||||||
<Compile Include="Classes\AppConfig.cs" />
|
<Compile Include="Classes\AppConfig.cs" />
|
||||||
|
<Compile Include="Classes\AppDebugLog.cs" />
|
||||||
<Compile Include="Classes\DataSender.cs" />
|
<Compile Include="Classes\DataSender.cs" />
|
||||||
<Compile Include="Classes\ErrorResponse.cs" />
|
<Compile Include="Classes\ErrorResponse.cs" />
|
||||||
|
<Compile Include="Classes\InvoiceRequestService.cs" />
|
||||||
<Compile Include="Classes\InvoiceSyncService.cs" />
|
<Compile Include="Classes\InvoiceSyncService.cs" />
|
||||||
<Compile Include="Classes\LoginRequest.cs" />
|
<Compile Include="Classes\LoginRequest.cs" />
|
||||||
<Compile Include="Classes\LoginResponse.cs" />
|
<Compile Include="Classes\LoginResponse.cs" />
|
||||||
|
|||||||
2
src/ElectronicPharmacy/Form1.Designer.cs
generated
2
src/ElectronicPharmacy/Form1.Designer.cs
generated
@ -230,7 +230,7 @@
|
|||||||
this.tsItemInvoices.Name = "tsItemInvoices";
|
this.tsItemInvoices.Name = "tsItemInvoices";
|
||||||
this.tsItemInvoices.Size = new System.Drawing.Size(224, 32);
|
this.tsItemInvoices.Size = new System.Drawing.Size(224, 32);
|
||||||
this.tsItemInvoices.Text = "Накладные";
|
this.tsItemInvoices.Text = "Накладные";
|
||||||
this.tsItemInvoices.Visible = false;
|
this.tsItemInvoices.Visible = true;
|
||||||
this.tsItemInvoices.Click += new System.EventHandler(this.tsItemInvoices_Click);
|
this.tsItemInvoices.Click += new System.EventHandler(this.tsItemInvoices_Click);
|
||||||
//
|
//
|
||||||
// toolStripSeparator1
|
// toolStripSeparator1
|
||||||
|
|||||||
@ -51,6 +51,7 @@ namespace Электронная_Фармация
|
|||||||
{
|
{
|
||||||
new SidebarItem { Key = "price", Text = "ПРАЙС-ЛИСТ", IconGlyph = "\uE8FD" },
|
new SidebarItem { Key = "price", Text = "ПРАЙС-ЛИСТ", IconGlyph = "\uE8FD" },
|
||||||
new SidebarItem { Key = "orders", Text = "ЗАКАЗЫ", IconGlyph = "\uE719" },
|
new SidebarItem { Key = "orders", Text = "ЗАКАЗЫ", IconGlyph = "\uE719" },
|
||||||
|
new SidebarItem { Key = "invoices", Text = "НАКЛАДНЫЕ", IconGlyph = "\uE8A5" },
|
||||||
new SidebarItem { Key = "upload", Text = "ЗАГРУЗИТЬ", IconGlyph = "\uE896" },
|
new SidebarItem { Key = "upload", Text = "ЗАГРУЗИТЬ", IconGlyph = "\uE896" },
|
||||||
new SidebarItem { Key = "send", Text = "ОТПРАВИТЬ", IconGlyph = "\uE725" },
|
new SidebarItem { Key = "send", Text = "ОТПРАВИТЬ", IconGlyph = "\uE725" },
|
||||||
new SidebarItem { Key = "refusals", Text = "ОТКАЗЫ", IconGlyph = "\uE711" },
|
new SidebarItem { Key = "refusals", Text = "ОТКАЗЫ", IconGlyph = "\uE711" },
|
||||||
@ -207,6 +208,9 @@ namespace Электронная_Фармация
|
|||||||
case "orders":
|
case "orders":
|
||||||
OpenOrdersTab();
|
OpenOrdersTab();
|
||||||
break;
|
break;
|
||||||
|
case "invoices":
|
||||||
|
OpenInvoicesTab();
|
||||||
|
break;
|
||||||
case "upload":
|
case "upload":
|
||||||
tsBtnDownloadData_Click(this, EventArgs.Empty);
|
tsBtnDownloadData_Click(this, EventArgs.Empty);
|
||||||
_appHeader.SetActive("upload");
|
_appHeader.SetActive("upload");
|
||||||
@ -300,6 +304,11 @@ namespace Электронная_Фармация
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sender is Control clicked && HasDedicatedContextMenu(clicked))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_documentCloseTabMenuItem.Enabled = !string.IsNullOrEmpty(_documentTabStrip.ActiveTabId);
|
_documentCloseTabMenuItem.Enabled = !string.IsNullOrEmpty(_documentTabStrip.ActiveTabId);
|
||||||
_documentCloseAllTabsMenuItem.Enabled = _documentTabStrip.Tabs.Count > 0;
|
_documentCloseAllTabsMenuItem.Enabled = _documentTabStrip.Tabs.Count > 0;
|
||||||
|
|
||||||
@ -307,6 +316,19 @@ namespace Электронная_Фармация
|
|||||||
_documentTabMenu.Show(screenPoint);
|
_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)
|
private void CloseDocumentTab(string tabId)
|
||||||
{
|
{
|
||||||
if (_documentContents.TryGetValue(tabId, out var control))
|
if (_documentContents.TryGetValue(tabId, out var control))
|
||||||
@ -343,6 +365,24 @@ namespace Электронная_Фармация
|
|||||||
orders.Show();
|
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()
|
private void OpenRefusalsTab()
|
||||||
{
|
{
|
||||||
var refusals = new UCRefusals();
|
var refusals = new UCRefusals();
|
||||||
|
|||||||
@ -146,8 +146,7 @@ namespace Электронная_Фармация
|
|||||||
|
|
||||||
private void tsItemInvoices_Click(object sender, EventArgs e)
|
private void tsItemInvoices_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var ucInvoices = new UCInvoices();
|
OpenInvoicesTab();
|
||||||
OpenDocumentTab("Накладные", "invoices", ucInvoices, allowDuplicate: true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tsItemPriceList_Click(object sender, EventArgs e)
|
private void tsItemPriceList_Click(object sender, EventArgs e)
|
||||||
|
|||||||
@ -393,6 +393,13 @@ create table if not exists [OrderItems] (
|
|||||||
cmdCreateNotExistingsTables.ExecuteNonQuery();
|
cmdCreateNotExistingsTables.ExecuteNonQuery();
|
||||||
|
|
||||||
TryAddColumn(conForCheckTables, "Orders", "ConsigneeName", "nvarchar(128)");
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,16 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Data.SQLite;
|
using System.Data.SQLite;
|
||||||
using System.Drawing;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
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.Threading.Tasks;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using Электронная_Фармация.Classes;
|
using Электронная_Фармация.Classes;
|
||||||
@ -25,43 +17,111 @@ namespace Электронная_Фармация.HelpForms
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
string login = Settings.Default.stringLogin.ToString();
|
private readonly string _login = Settings.Default.stringLogin ?? string.Empty;
|
||||||
string password = Settings.Default.stringPassword.ToString();
|
private readonly string _password = Settings.Default.stringPassword ?? string.Empty;
|
||||||
|
private readonly string _connectionStringToLocalDb = AppConfig.SqliteConnectionString;
|
||||||
string connectionStringToLocalDB = AppConfig.SqliteConnectionString;
|
|
||||||
|
|
||||||
private async void HF_DownloadDataFromServer_Load(object sender, EventArgs e)
|
private async void HF_DownloadDataFromServer_Load(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
UiThemeHelper.ApplyToControlTree(this);
|
UiThemeHelper.ApplyToControlTree(this);
|
||||||
btnCloseThis.Enabled = false;
|
btnCloseThis.Enabled = false;
|
||||||
rtxtDebug.Visible = false;
|
|
||||||
dataGridView1.Visible = false;
|
dataGridView1.Visible = false;
|
||||||
|
rtxtDebug.Visible = true;
|
||||||
|
rtxtDebug.ReadOnly = true;
|
||||||
|
|
||||||
var client = new ApiClient();
|
var client = new ApiClient();
|
||||||
|
AppendStatus($"Сервер: {client.BaseUrl}");
|
||||||
|
AppendStatus($"Лог: {AppDebugLog.CurrentLogFilePath}");
|
||||||
|
AppDebugLog.Info("Download", $"Старт загрузки прайса. Сервер={client.BaseUrl}");
|
||||||
|
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
await EnsureAuthenticatedAsync(client);
|
||||||
|
|
||||||
|
AppendStatus("Загружаю сводный прайс для всех поставщиков...");
|
||||||
|
var allSuppliersSummary = await GetPriceSummaryWithRetryAsync(client);
|
||||||
|
AppendStatus($"Получено {allSuppliersSummary.Summary?.Count ?? 0} позиций");
|
||||||
|
|
||||||
|
var tablePrice = BuildPriceTable(allSuppliersSummary);
|
||||||
|
SavePriceTable(tablePrice);
|
||||||
|
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
|
||||||
|
btnCloseThis.Enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EnsureAuthenticatedAsync(ApiClient client)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(Settings.Default.stringToken))
|
if (!string.IsNullOrWhiteSpace(Settings.Default.stringToken))
|
||||||
{
|
{
|
||||||
client.SetToken(Settings.Default.stringToken);
|
client.SetToken(Settings.Default.stringToken);
|
||||||
rtxtDebug.Text += "Используется сохранённый токен...\n";
|
AppendStatus("Используется сохранённый токен...");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
await LoginAndSaveTokenAsync(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoginAndSaveTokenAsync(ApiClient client)
|
||||||
{
|
{
|
||||||
rtxtDebug.Text += "Выполняю авторизацию...\n";
|
if (string.IsNullOrWhiteSpace(_login) || string.IsNullOrWhiteSpace(_password))
|
||||||
var token = await client.LoginAsync(login, password);
|
{
|
||||||
|
throw new UnauthorizedAccessException(
|
||||||
|
"Не заданы логин и пароль. Выполните авторизацию в меню «Регистрация».");
|
||||||
|
}
|
||||||
|
|
||||||
|
AppendStatus("Выполняю авторизацию...");
|
||||||
|
var token = await client.LoginAsync(_login, _password);
|
||||||
Settings.Default.stringToken = token;
|
Settings.Default.stringToken = token;
|
||||||
Settings.Default.Save();
|
Settings.Default.Save();
|
||||||
rtxtDebug.Text += "Авторизация успешно пройдена\n";
|
AppendStatus("Авторизация успешно пройдена");
|
||||||
}
|
}
|
||||||
|
|
||||||
rtxtDebug.Text += "\nЗагружаю сводный прайс для всех поставщиков...";
|
private async Task<PriceSummaryResponse> GetPriceSummaryWithRetryAsync(ApiClient client)
|
||||||
var allSuppliersSummary = await client.GetPriceSummaryAsync(
|
{
|
||||||
supplierId: null,
|
var regionId = string.IsNullOrWhiteSpace(Settings.Default.RegionId)
|
||||||
regionId: string.IsNullOrWhiteSpace(Settings.Default.RegionId) ? null : Settings.Default.RegionId);
|
? null
|
||||||
rtxtDebug.Text += ($"✓ Получено {allSuppliersSummary.Summary.Count} позиций\n");
|
: Settings.Default.RegionId;
|
||||||
|
|
||||||
DataTable tablePrice = new DataTable();
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DataTable BuildPriceTable(PriceSummaryResponse allSuppliersSummary)
|
||||||
|
{
|
||||||
|
var tablePrice = new DataTable();
|
||||||
tablePrice.Columns.Add("supplier_price_id");
|
tablePrice.Columns.Add("supplier_price_id");
|
||||||
tablePrice.Columns.Add("guid_es");
|
tablePrice.Columns.Add("guid_es");
|
||||||
tablePrice.Columns.Add("es_code");
|
tablePrice.Columns.Add("es_code");
|
||||||
@ -70,72 +130,29 @@ namespace Электронная_Фармация.HelpForms
|
|||||||
tablePrice.Columns.Add("SupplierName");
|
tablePrice.Columns.Add("SupplierName");
|
||||||
tablePrice.Columns.Add("Price");
|
tablePrice.Columns.Add("Price");
|
||||||
tablePrice.Columns.Add("Quantity");
|
tablePrice.Columns.Add("Quantity");
|
||||||
|
|
||||||
tablePrice.Columns.Add(new DataColumn
|
tablePrice.Columns.Add(new DataColumn
|
||||||
{
|
{
|
||||||
ColumnName = "ExpiryPeriod",
|
ColumnName = "ExpiryPeriod",
|
||||||
DataType = typeof(string),
|
DataType = typeof(string),
|
||||||
AllowDBNull = true
|
AllowDBNull = true
|
||||||
});
|
});
|
||||||
|
|
||||||
tablePrice.Columns.Add(new DataColumn
|
tablePrice.Columns.Add(new DataColumn
|
||||||
{
|
{
|
||||||
ColumnName = "Description",
|
ColumnName = "Description",
|
||||||
DataType = typeof(string),
|
DataType = typeof(string),
|
||||||
AllowDBNull = true
|
AllowDBNull = true
|
||||||
});
|
});
|
||||||
|
|
||||||
//DataColumn colExpiryPeriod = new DataColumn("ExpiryPeriod", typeof(DateTime));
|
|
||||||
//DataColumn colDescription = new DataColumn("Description", typeof(string));
|
|
||||||
|
|
||||||
//colExpiryPeriod.AllowDBNull = true;
|
|
||||||
//colDescription.AllowDBNull = true;
|
|
||||||
|
|
||||||
|
|
||||||
////tablePrice.Columns.Add(colExpiryPeriod);
|
|
||||||
//tablePrice.Columns.Add(colDescription);
|
|
||||||
|
|
||||||
|
|
||||||
tablePrice.Columns.Add("Zakaz");
|
tablePrice.Columns.Add("Zakaz");
|
||||||
tablePrice.Columns.Add("SummaZakaza");
|
tablePrice.Columns.Add("SummaZakaza");
|
||||||
|
|
||||||
|
if (allSuppliersSummary?.Summary == null)
|
||||||
#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
|
|
||||||
|
|
||||||
if (allSuppliersSummary.Summary != null)
|
|
||||||
{
|
{
|
||||||
|
return tablePrice;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var item in allSuppliersSummary.Summary)
|
foreach (var item in allSuppliersSummary.Summary)
|
||||||
{
|
{
|
||||||
|
var row = tablePrice.NewRow();
|
||||||
DataRow row = tablePrice.NewRow();
|
|
||||||
|
|
||||||
row["supplier_price_id"] = item.SupplierPriceID.ToString();
|
row["supplier_price_id"] = item.SupplierPriceID.ToString();
|
||||||
row["guid_es"] = item.GuidEs.ToString();
|
row["guid_es"] = item.GuidEs.ToString();
|
||||||
row["es_code"] = item.EsCode.ToString();
|
row["es_code"] = item.EsCode.ToString();
|
||||||
@ -144,79 +161,21 @@ namespace Электронная_Фармация.HelpForms
|
|||||||
row["SupplierName"] = item.SupplierName.ToString();
|
row["SupplierName"] = item.SupplierName.ToString();
|
||||||
row["Price"] = item.Price.ToString();
|
row["Price"] = item.Price.ToString();
|
||||||
row["Quantity"] = item.Quantity.ToString();
|
row["Quantity"] = item.Quantity.ToString();
|
||||||
|
row["ExpiryPeriod"] = item.ExpiryPeriod == null ? (object)DBNull.Value : item.ExpiryPeriod.ToString();
|
||||||
if (item.ExpiryPeriod == null)
|
row["Description"] = item.Description == null ? (object)DBNull.Value : item.Description.ToString();
|
||||||
{ 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["Zakaz"] = string.Empty;
|
||||||
row["SummaZakaza"] = string.Empty;
|
row["SummaZakaza"] = string.Empty;
|
||||||
|
|
||||||
tablePrice.Rows.Add(row);
|
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]";
|
return tablePrice;
|
||||||
string commandTODeleteTempOrder = "delete from [TempOrderItems]";
|
}
|
||||||
|
|
||||||
|
private void SavePriceTable(DataTable tablePrice)
|
||||||
string commandToInsert = @"INSERT INTO [PriceList] (
|
{
|
||||||
|
const string commandToDelete = "delete from [PriceList]";
|
||||||
|
const string commandToDeleteTempOrder = "delete from [TempOrderItems]";
|
||||||
|
const string commandToInsert = @"INSERT INTO [PriceList] (
|
||||||
[guid_es],
|
[guid_es],
|
||||||
[es_code],
|
[es_code],
|
||||||
[supplier_id],
|
[supplier_id],
|
||||||
@ -244,24 +203,24 @@ VALUES
|
|||||||
@Zakaz,
|
@Zakaz,
|
||||||
@SummaZakaza,
|
@SummaZakaza,
|
||||||
@supplier_price_id
|
@supplier_price_id
|
||||||
)
|
)";
|
||||||
";
|
|
||||||
|
|
||||||
using (SQLiteConnection sqliteCon = new SQLiteConnection(connectionStringToLocalDB))
|
using (var sqliteCon = new SQLiteConnection(_connectionStringToLocalDb))
|
||||||
{
|
{
|
||||||
sqliteCon.Open();
|
sqliteCon.Open();
|
||||||
|
|
||||||
SQLiteCommand cmdDeleteBeforeInsert = new SQLiteCommand(commandToDelete, sqliteCon);
|
using (var cmdDeleteBeforeInsert = new SQLiteCommand(commandToDelete, sqliteCon))
|
||||||
SQLiteCommand cmdDeleteTempOrder = new SQLiteCommand(commandTODeleteTempOrder, sqliteCon);
|
using (var cmdDeleteTempOrder = new SQLiteCommand(commandToDeleteTempOrder, sqliteCon))
|
||||||
|
{
|
||||||
cmdDeleteBeforeInsert.ExecuteNonQuery();
|
cmdDeleteBeforeInsert.ExecuteNonQuery();
|
||||||
cmdDeleteTempOrder.ExecuteNonQuery();
|
cmdDeleteTempOrder.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
|
||||||
using (var transaction = sqliteCon.BeginTransaction())
|
using (var transaction = sqliteCon.BeginTransaction())
|
||||||
{
|
{
|
||||||
foreach (DataRow row in tablePrice.Rows)
|
foreach (DataRow row in tablePrice.Rows)
|
||||||
{
|
{
|
||||||
using (SQLiteCommand cmd = new SQLiteCommand(commandToInsert, sqliteCon, transaction))
|
using (var cmd = new SQLiteCommand(commandToInsert, sqliteCon, transaction))
|
||||||
{
|
{
|
||||||
cmd.Parameters.AddWithValue("@guid_es", row["guid_es"]);
|
cmd.Parameters.AddWithValue("@guid_es", row["guid_es"]);
|
||||||
cmd.Parameters.AddWithValue("@es_code", row["es_code"]);
|
cmd.Parameters.AddWithValue("@es_code", row["es_code"]);
|
||||||
@ -278,39 +237,36 @@ VALUES
|
|||||||
cmd.ExecuteNonQuery();
|
cmd.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
}
|
}
|
||||||
sqliteCon.Close();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DialogResult = DialogResult.OK;
|
private void AppendStatus(string message)
|
||||||
ToastNotification.ShowSuccess($"Прайс обновлён: {tablePrice.Rows.Count} позиций");
|
|
||||||
}
|
|
||||||
catch (UnauthorizedAccessException ex)
|
|
||||||
{
|
{
|
||||||
ToastNotification.ShowError($"Ошибка авторизации: {ex.Message}");
|
if (InvokeRequired)
|
||||||
}
|
|
||||||
catch (HttpRequestException ex)
|
|
||||||
{
|
{
|
||||||
ToastNotification.ShowError($"Ошибка сети: {ex.Message}");
|
BeginInvoke(new Action<string>(AppendStatus), message);
|
||||||
}
|
return;
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
ToastNotification.ShowError($"Ошибка загрузки прайса: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
private void HF_DownloadDataFromServer_FormClosing(object sender, FormClosingEventArgs e)
|
||||||
{
|
{
|
||||||
this.Dispose();
|
Dispose();
|
||||||
this.Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnCloseThis_Click(object sender, EventArgs e)
|
private void btnCloseThis_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
this.Close();
|
Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,7 +56,8 @@ namespace Электронная_Фармация.HelpForms
|
|||||||
loginRequest.Username = strLogin.Trim();
|
loginRequest.Username = strLogin.Trim();
|
||||||
loginRequest.Password = strPassword.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
|
try
|
||||||
{
|
{
|
||||||
@ -66,9 +67,10 @@ namespace Электронная_Фармация.HelpForms
|
|||||||
Settings.Default.stringPassword = strPassword;
|
Settings.Default.stringPassword = strPassword;
|
||||||
Settings.Default.stringToken = token;
|
Settings.Default.stringToken = token;
|
||||||
Settings.Default.LocationId = txtLocationId.Text.Trim();
|
Settings.Default.LocationId = txtLocationId.Text.Trim();
|
||||||
Settings.Default.ApiBaseUrl = txtApiUrl.Text.Trim();
|
Settings.Default.ApiBaseUrl = client.BaseUrl;
|
||||||
Settings.Default.Save();
|
Settings.Default.Save();
|
||||||
|
|
||||||
|
AppDebugLog.Info("Auth", $"Авторизация успешна. token={AppDebugLog.MaskToken(token)}");
|
||||||
ToastNotification.ShowSuccess("Авторизация успешно выполнена");
|
ToastNotification.ShowSuccess("Авторизация успешно выполнена");
|
||||||
//MessageBox.Show("Данные были сохранены", "Регистрация программы успешно выполнена", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
//MessageBox.Show("Данные были сохранены", "Регистрация программы успешно выполнена", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
|
||||||
@ -79,13 +81,13 @@ namespace Электронная_Фармация.HelpForms
|
|||||||
}
|
}
|
||||||
catch (UnauthorizedAccessException ex)
|
catch (UnauthorizedAccessException ex)
|
||||||
{
|
{
|
||||||
|
AppDebugLog.Error("Auth", "Ошибка авторизации", ex);
|
||||||
ToastNotification.ShowError($"Ошибка авторизации: {ex.Message}");
|
ToastNotification.ShowError($"Ошибка авторизации: {ex.Message}");
|
||||||
//MessageBox.Show($"Ошибка авторизации: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
catch (HttpRequestException ex)
|
catch (HttpRequestException ex)
|
||||||
{
|
{
|
||||||
|
AppDebugLog.Error("Auth", "Ошибка сети при авторизации", ex);
|
||||||
ToastNotification.ShowError($"Ошибка сети: {ex.Message}");
|
ToastNotification.ShowError($"Ошибка сети: {ex.Message}");
|
||||||
//MessageBox.Show($"Ошибка сети: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using System.Net;
|
||||||
|
using Электронная_Фармация.Classes;
|
||||||
|
|
||||||
namespace Электронная_Фармация
|
namespace Электронная_Фармация
|
||||||
{
|
{
|
||||||
@ -8,6 +10,9 @@ namespace Электронная_Фармация
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
static void Main()
|
static void Main()
|
||||||
{
|
{
|
||||||
|
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
|
||||||
|
AppConfig.MigrateLegacySettings();
|
||||||
|
AppDebugLog.Info("App", $"Запуск. ApiBaseUrl={AppConfig.ApiBaseUrl}, Log={AppDebugLog.CurrentLogFilePath}");
|
||||||
Application.EnableVisualStyles();
|
Application.EnableVisualStyles();
|
||||||
Application.SetCompatibleTextRenderingDefault(false);
|
Application.SetCompatibleTextRenderingDefault(false);
|
||||||
Application.Run(new ElectroPharmacy());
|
Application.Run(new ElectroPharmacy());
|
||||||
|
|||||||
@ -85,7 +85,7 @@ namespace Электронная_Фармация.Properties {
|
|||||||
|
|
||||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
[global::System.Configuration.DefaultSettingValueAttribute("http://195.34.241.84:9988")]
|
[global::System.Configuration.DefaultSettingValueAttribute("https://24pharmdata.ru")]
|
||||||
public string ApiBaseUrl {
|
public string ApiBaseUrl {
|
||||||
get {
|
get {
|
||||||
return ((string)(this["ApiBaseUrl"]));
|
return ((string)(this["ApiBaseUrl"]));
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
<Value Profile="(Default)" />
|
<Value Profile="(Default)" />
|
||||||
</Setting>
|
</Setting>
|
||||||
<Setting Name="ApiBaseUrl" Type="System.String" Scope="User">
|
<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>
|
||||||
<Setting Name="RegionId" Type="System.String" Scope="User">
|
<Setting Name="RegionId" Type="System.String" Scope="User">
|
||||||
<Value Profile="(Default)" />
|
<Value Profile="(Default)" />
|
||||||
|
|||||||
@ -46,13 +46,54 @@ namespace Электронная_Фармация.UserControls
|
|||||||
ApplyToolbarLayout();
|
ApplyToolbarLayout();
|
||||||
ModernButtonStyles.ApplyClear(btnClearGoodSearch);
|
ModernButtonStyles.ApplyClear(btnClearGoodSearch);
|
||||||
ModernButtonStyles.ApplyClear(btnClearOrderNumberFilter);
|
ModernButtonStyles.ApplyClear(btnClearOrderNumberFilter);
|
||||||
|
InitializeOrderContextMenu();
|
||||||
label3.BringToFront();
|
label3.BringToFront();
|
||||||
panelToolbar.Resize += (_, __) => ApplyToolbarLayout();
|
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 _comboOrderStatus;
|
||||||
private ModernComboBox _comboConsignees;
|
private ModernComboBox _comboConsignees;
|
||||||
private ModernButton _btnSendOrder;
|
private ModernButton _btnSendOrder;
|
||||||
|
private ContextMenuStrip _ordersMenu;
|
||||||
|
private ToolStripMenuItem _getInvoiceMenuItem;
|
||||||
|
|
||||||
private void InitOrderToolbar()
|
private void InitOrderToolbar()
|
||||||
{
|
{
|
||||||
@ -319,6 +360,44 @@ namespace Электронная_Фармация.UserControls
|
|||||||
ApplyOrderFilters();
|
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)
|
void LoadOrders(string dateFrom, string dateTo, string filters)
|
||||||
{
|
{
|
||||||
string connectionStringToLocalDB = AppConfig.SqliteConnectionString;
|
string connectionStringToLocalDB = AppConfig.SqliteConnectionString;
|
||||||
|
|||||||
@ -15,9 +15,28 @@ namespace Электронная_Фармация.UserControls
|
|||||||
{
|
{
|
||||||
public partial class UCInvoices : UserControl
|
public partial class UCInvoices : UserControl
|
||||||
{
|
{
|
||||||
|
private string _initialSourceOrderId;
|
||||||
|
private ContextMenuStrip _invoiceMenu;
|
||||||
|
private ToolStripMenuItem _exportInvoiceMenuItem;
|
||||||
|
|
||||||
public UCInvoices()
|
public UCInvoices()
|
||||||
|
: this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public UCInvoices(string sourceOrderId)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
_initialSourceOrderId = sourceOrderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetSourceOrderFilter(string sourceOrderId)
|
||||||
|
{
|
||||||
|
_initialSourceOrderId = sourceOrderId;
|
||||||
|
if (IsHandleCreated)
|
||||||
|
{
|
||||||
|
getDatesFromCalendar();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void getDatesFromCalendar()
|
void getDatesFromCalendar()
|
||||||
@ -94,52 +113,80 @@ namespace Электронная_Фармация.UserControls
|
|||||||
|
|
||||||
void loadInvoicesList(string InvoicesDateFrom, string InvoicesDateTo, string dateFromYearFormat)
|
void loadInvoicesList(string InvoicesDateFrom, string InvoicesDateTo, string dateFromYearFormat)
|
||||||
{
|
{
|
||||||
|
|
||||||
string connectionStringToLocalDB = AppConfig.SqliteConnectionString;
|
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))
|
using (SQLiteConnection conInvoices = new SQLiteConnection(connectionStringToLocalDB))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
conInvoices.Open();
|
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();
|
DataTable tableInvoices = new DataTable();
|
||||||
SQLiteDataAdapter daInvoicesList = new SQLiteDataAdapter(cmdShowInvoicesList);
|
SQLiteDataAdapter daInvoicesList = new SQLiteDataAdapter(cmdShowInvoicesList);
|
||||||
daInvoicesList.Fill(tableInvoices);
|
daInvoicesList.Fill(tableInvoices);
|
||||||
dgvInvoice.DataSource = tableInvoices;
|
dgvInvoice.DataSource = tableInvoices;
|
||||||
|
|
||||||
|
if (dgvInvoice.Columns.Count > 0)
|
||||||
|
{
|
||||||
|
dgvInvoice.Columns["ИД накладной"].Visible = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -162,6 +209,7 @@ order by i.InvoiceDate asc
|
|||||||
{
|
{
|
||||||
UiThemeHelper.ApplyToControlTree(this);
|
UiThemeHelper.ApplyToControlTree(this);
|
||||||
Font = new Font("Segoe UI", 9.75f, FontStyle.Regular);
|
Font = new Font("Segoe UI", 9.75f, FontStyle.Regular);
|
||||||
|
InitializeInvoiceContextMenu();
|
||||||
|
|
||||||
button1.Visible = true;
|
button1.Visible = true;
|
||||||
button1.Text = "Обновить с сервера";
|
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)
|
private void dtpDateTo_Leave(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
@ -226,28 +311,22 @@ order by i.InvoiceDate asc
|
|||||||
|
|
||||||
void selectInvoice()
|
void selectInvoice()
|
||||||
{
|
{
|
||||||
if (dgvInvoice.Rows.Count > 0)
|
if (dgvInvoice.Rows.Count > 0 && dgvInvoice.CurrentCell != null)
|
||||||
{
|
{
|
||||||
int SelectedInvoiceIndex = dgvInvoice.CurrentCell.RowIndex;
|
int SelectedInvoiceIndex = dgvInvoice.CurrentCell.RowIndex;
|
||||||
string SelectedInvoiceNumber = dgvInvoice.Rows[SelectedInvoiceIndex].Cells[0].Value.ToString();
|
string SelectedInvoiceId = dgvInvoice.Rows[SelectedInvoiceIndex].Cells["ИД накладной"]?.Value?.ToString() ?? string.Empty;
|
||||||
loadInvoiceItems(SelectedInvoiceNumber);
|
loadInvoiceItems(SelectedInvoiceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadInvoiceItems(string InvoiceNumber)
|
void loadInvoiceItems(string invoiceId)
|
||||||
{
|
{
|
||||||
string filterAddons = string.Empty;
|
|
||||||
|
|
||||||
|
|
||||||
string InvNum = InvoiceNumber;
|
|
||||||
if (dgvInvoice.Rows.Count > 0)
|
if (dgvInvoice.Rows.Count > 0)
|
||||||
{
|
{
|
||||||
if (InvNum != "" | InvoiceNumber != string.Empty)
|
if (!string.IsNullOrWhiteSpace(invoiceId))
|
||||||
{
|
{
|
||||||
filterAddons = $"where i.InvoiceNumber = '{InvNum}'";
|
|
||||||
|
|
||||||
string connectionStringToLocalDB = AppConfig.SqliteConnectionString;
|
string connectionStringToLocalDB = AppConfig.SqliteConnectionString;
|
||||||
string qShowMeInvoiceItems = $@"
|
string qShowMeInvoiceItems = @"
|
||||||
select
|
select
|
||||||
ii.GoodCode as [Код товара],
|
ii.GoodCode as [Код товара],
|
||||||
ii.Good as [Товар],
|
ii.Good as [Товар],
|
||||||
@ -270,21 +349,23 @@ ii.Sertificate as [Сертификат],
|
|||||||
ii.Marked as [Признак маркировки],
|
ii.Marked as [Признак маркировки],
|
||||||
ii.GTIN as [GTIN]
|
ii.GTIN as [GTIN]
|
||||||
from InvoiceItem ii
|
from InvoiceItem ii
|
||||||
inner join Invoice i on i.idInvoice = ii.idInvoice
|
where ii.idInvoice = @invoiceId;";
|
||||||
{filterAddons}
|
|
||||||
";
|
|
||||||
using (SQLiteConnection conInvoiceItems = new SQLiteConnection(connectionStringToLocalDB))
|
using (SQLiteConnection conInvoiceItems = new SQLiteConnection(connectionStringToLocalDB))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
conInvoiceItems.Open();
|
conInvoiceItems.Open();
|
||||||
SQLiteCommand cmdShowMeInvoiceItems = new SQLiteCommand(qShowMeInvoiceItems, conInvoiceItems);
|
SQLiteCommand cmdShowMeInvoiceItems = new SQLiteCommand(qShowMeInvoiceItems, conInvoiceItems);
|
||||||
|
cmdShowMeInvoiceItems.Parameters.AddWithValue("@invoiceId", invoiceId);
|
||||||
DataTable tableInvoiceItems = new DataTable();
|
DataTable tableInvoiceItems = new DataTable();
|
||||||
SQLiteDataAdapter daInvoiceItems = new SQLiteDataAdapter(cmdShowMeInvoiceItems);
|
SQLiteDataAdapter daInvoiceItems = new SQLiteDataAdapter(cmdShowMeInvoiceItems);
|
||||||
daInvoiceItems.Fill(tableInvoiceItems);
|
daInvoiceItems.Fill(tableInvoiceItems);
|
||||||
dgvInvoiceItems.DataSource = tableInvoiceItems;
|
dgvInvoiceItems.DataSource = tableInvoiceItems;
|
||||||
|
if (dgvInvoiceItems.Columns.Count > 10)
|
||||||
|
{
|
||||||
dgvInvoiceItems.Columns[10].ValueType = typeof(bool);
|
dgvInvoiceItems.Columns[10].ValueType = typeof(bool);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
ToastNotification.ShowError($"Ошибка загрузки позиций накладной: {ex.Message}");
|
ToastNotification.ShowError($"Ошибка загрузки позиций накладной: {ex.Message}");
|
||||||
@ -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)
|
private void txtInvoiceNumber_KeyPress(object sender, KeyPressEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.KeyChar == (char)Keys.Enter)
|
if (e.KeyChar == (char)Keys.Enter)
|
||||||
|
|||||||
@ -824,16 +824,47 @@ and tempOrder.GoodName = PriceList.GoodName
|
|||||||
|
|
||||||
private void dgvPriceList_KeyPress(object sender, KeyPressEventArgs e)
|
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)
|
if (timerForResetSearch.Enabled == false)
|
||||||
|
{
|
||||||
|
if (RowIndex >= 0 && !string.IsNullOrEmpty(idPriceListItem))
|
||||||
{
|
{
|
||||||
deleteGood(RowIndex, idPriceListItem);
|
deleteGood(RowIndex, idPriceListItem);
|
||||||
timerForResetSearch.Enabled = true;
|
timerForResetSearch.Enabled = true;
|
||||||
timerForResetSearch.Start();
|
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))
|
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
|
#endregion
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if ((e.KeyChar >= (Char)Keys.D1 && e.KeyChar <= (Char)Keys.D9) ||
|
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))
|
(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;
|
txtSearchGoodNameInPriceList.Text = string.Empty;
|
||||||
GoodsFilter = string.Empty;
|
GoodsFilter = string.Empty;
|
||||||
ApplyPriceListFilters();
|
ApplyPriceListFilters();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (e.KeyChar == (Char)Keys.Back)
|
else if (e.KeyChar == (Char)Keys.Back)
|
||||||
{
|
{
|
||||||
|
if (RowIndex < 0) return;
|
||||||
|
|
||||||
string countToBuy = dgvPriceList.Rows[RowIndex].Cells[9].Value.ToString();
|
string countToBuy = dgvPriceList.Rows[RowIndex].Cells[9].Value.ToString();
|
||||||
if (countToBuy.Length > 1)
|
if (countToBuy.Length > 1)
|
||||||
@ -945,14 +979,16 @@ and tempOrder.GoodName = PriceList.GoodName
|
|||||||
}
|
}
|
||||||
else if (e.KeyChar == (Char)Keys.Delete)
|
else if (e.KeyChar == (Char)Keys.Delete)
|
||||||
{
|
{
|
||||||
|
if (RowIndex < 0 || string.IsNullOrEmpty(idPriceListItem)) return;
|
||||||
deleteGood(RowIndex, idPriceListItem);
|
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)
|
else if (e.KeyChar == (Char)Keys.Down || e.KeyChar == (Char)Keys.Up)
|
||||||
{
|
{
|
||||||
|
if (RowIndex < 0 || string.IsNullOrEmpty(idPriceListItem)) return;
|
||||||
timerForResetSearch.Enabled = false;
|
timerForResetSearch.Enabled = false;
|
||||||
timerForResetSearch.Stop();
|
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)
|
if (summaZakaza.Trim().Length > 0)
|
||||||
@ -1662,6 +1700,8 @@ and SumOrderedItems = '{SumOrder}'";
|
|||||||
private void timerForResetSearch_Tick(object sender, EventArgs e)
|
private void timerForResetSearch_Tick(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
txtSearchGoodNameInPriceList.Text = string.Empty;
|
txtSearchGoodNameInPriceList.Text = string.Empty;
|
||||||
|
GoodsFilter = string.Empty;
|
||||||
|
ApplyPriceListFilters();
|
||||||
timerForResetSearch.Enabled = false;
|
timerForResetSearch.Enabled = false;
|
||||||
timerForResetSearch.Stop();
|
timerForResetSearch.Stop();
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user