Improve order UX and harden API debugging.

Prompt for Location ID after login, move cart delete there, refine price-list Backspace navigation, and log every API failure with stack and response body.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Magomed 2026-07-16 16:31:00 +03:00
parent 74390c4918
commit a6601567f2
20 changed files with 799 additions and 267 deletions

View File

@ -118,27 +118,45 @@ namespace Электронная_Фармация.Classes
}
var responseBody = await SendAuthorizedGetAsync(path, "загрузку прайса");
try
{
var summary = JsonSerializer.Deserialize<PriceSummaryResponse>(responseBody, JsonOptions);
var count = summary?.Summary?.Count ?? 0;
AppDebugLog.Info("ApiClient", $"Прайс получен: {count} позиций");
return summary;
}
catch (Exception ex)
{
AppDebugLog.Error("ApiClient", "Не удалось разобрать ответ прайса", ex);
throw new HttpRequestException($"Некорректный ответ сервера при загрузке прайса: {ex.Message}", ex);
}
}
public async Task<List<InvoiceApiItem>> GetInvoicesAsync()
{
EnsureAuthenticated();
var responseBody = await SendAuthorizedGetAsync("/api/buyer/invoices", "загрузку накладных");
try
{
var items = JsonSerializer.Deserialize<List<InvoiceApiItem>>(responseBody, JsonOptions) ?? new List<InvoiceApiItem>();
AppDebugLog.Info("ApiClient", $"Накладные получены: {items.Count} шт.");
return items;
}
catch (Exception ex)
{
AppDebugLog.Error("ApiClient", "Не удалось разобрать ответ накладных", ex);
throw new HttpRequestException($"Некорректный ответ сервера при загрузке накладных: {ex.Message}", ex);
}
}
private void EnsureAuthenticated()
{
if (!IsAuthenticated)
{
throw new InvalidOperationException("Необходимо выполнить авторизацию перед запросом данных");
var ex = new InvalidOperationException("Необходимо выполнить авторизацию перед запросом данных");
AppDebugLog.Error("ApiClient", "Запрос без токена авторизации", ex);
throw ex;
}
}
@ -169,11 +187,19 @@ namespace Электронная_Фармация.Classes
{
_token = null;
var error = TryDeserializeError(responseBody);
throw new UnauthorizedAccessException($"Сессия истекла: {error?.Error ?? "Необходимо войти заново"}");
var message = $"Сессия истекла: {error?.Error ?? "Необходимо войти заново"}";
AppDebugLog.ApiHttpError("GET", fullUrl, (int)response.StatusCode, responseBody, operationName);
var unauthorized = new UnauthorizedAccessException(message);
AppDebugLog.Error("ApiClient", message, unauthorized);
throw unauthorized;
}
var httpError = TryDeserializeError(responseBody);
throw new HttpRequestException($"Ошибка HTTP {response.StatusCode}: {httpError?.Error ?? responseBody}");
var httpMessage = $"Ошибка HTTP {response.StatusCode}: {httpError?.Error ?? responseBody}";
AppDebugLog.ApiHttpError("GET", fullUrl, (int)response.StatusCode, responseBody, operationName);
var httpEx = new HttpRequestException(httpMessage);
AppDebugLog.Error("ApiClient", httpMessage, httpEx);
throw httpEx;
}
}
catch (HttpRequestException ex) when (!ex.Message.StartsWith("Ошибка HTTP"))
@ -188,7 +214,15 @@ namespace Электронная_Фармация.Classes
AppDebugLog.ApiFailure("GET", fullUrl, stopwatch.ElapsedMilliseconds, ex);
throw WrapNetworkError(ex, operationName);
}
catch (Exception ex) when (!(ex is UnauthorizedAccessException) && !(ex is HttpRequestException))
catch (UnauthorizedAccessException)
{
throw;
}
catch (HttpRequestException)
{
throw;
}
catch (Exception ex)
{
stopwatch.Stop();
AppDebugLog.ApiFailure("GET", fullUrl, stopwatch.ElapsedMilliseconds, ex);
@ -199,26 +233,48 @@ namespace Электронная_Фармация.Classes
private string ParseLoginResponse(HttpResponseMessage response, string responseBody)
{
if (response.IsSuccessStatusCode)
{
try
{
var loginResponse = JsonSerializer.Deserialize<LoginResponse>(responseBody, JsonOptions);
if (loginResponse == null || string.IsNullOrWhiteSpace(loginResponse.Token))
{
throw new HttpRequestException($"Сервер {_baseUrl} вернул пустой токен авторизации");
var emptyTokenEx = new HttpRequestException($"Сервер {_baseUrl} вернул пустой токен авторизации");
AppDebugLog.Error("ApiClient", "Пустой токен в ответе логина", emptyTokenEx);
throw emptyTokenEx;
}
_token = loginResponse.Token;
AppDebugLog.Info("ApiClient", $"Авторизация успешна. token={AppDebugLog.MaskToken(_token)}");
return _token;
}
catch (HttpRequestException)
{
throw;
}
catch (Exception ex)
{
AppDebugLog.Error("ApiClient", "Не удалось разобрать ответ логина", ex);
throw new HttpRequestException($"Некорректный ответ сервера при авторизации: {ex.Message}", ex);
}
}
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
var error = TryDeserializeError(responseBody);
throw new UnauthorizedAccessException($"Ошибка авторизации: {error?.Error ?? "Неверные учетные данные"}");
var message = $"Ошибка авторизации: {error?.Error ?? "Неверные учетные данные"}";
AppDebugLog.ApiHttpError("POST", BuildFullUrl("/auth/login"), (int)response.StatusCode, responseBody, "login");
var unauthorized = new UnauthorizedAccessException(message);
AppDebugLog.Error("ApiClient", message, unauthorized);
throw unauthorized;
}
var httpError = TryDeserializeError(responseBody);
throw new HttpRequestException($"Ошибка HTTP {response.StatusCode}: {httpError?.Error ?? responseBody}");
var httpMessage = $"Ошибка HTTP {response.StatusCode}: {httpError?.Error ?? responseBody}";
AppDebugLog.ApiHttpError("POST", BuildFullUrl("/auth/login"), (int)response.StatusCode, responseBody, "login");
var httpEx = new HttpRequestException(httpMessage);
AppDebugLog.Error("ApiClient", httpMessage, httpEx);
throw httpEx;
}
private string BuildFullUrl(string path)

View File

@ -102,5 +102,11 @@ namespace Электронная_Фармация.Classes
{
get { return Settings.Default.LocationId ?? string.Empty; }
}
public static void SetLocationId(string locationId)
{
Settings.Default.LocationId = (locationId ?? string.Empty).Trim();
Settings.Default.Save();
}
}
}

View File

@ -13,7 +13,7 @@ namespace Электронная_Фармация.Classes
public static class AppDebugLog
{
private static readonly object Sync = new object();
private const int MaxBodyLength = 4000;
private const int MaxBodyLength = 8000;
private const string DisableEnvVar = "ELF_API_DEBUG";
private const string DisableMarkerFile = "api-debug.off";
@ -88,10 +88,36 @@ namespace Электронная_Фармация.Classes
public static void ApiFailure(string method, string url, long elapsedMs, Exception exception)
{
var message = $"{method} {url} failed after {elapsedMs} ms: {exception.Message}";
var message = $"{method} {url} failed after {elapsedMs} ms: {exception?.Message}";
Write("FAIL", "Api", message, exception);
}
/// <summary>
/// Логирует ошибку HTTP-ответа API (4xx/5xx) с телом ответа.
/// </summary>
public static void ApiHttpError(
string method,
string url,
int statusCode,
string responseBody,
string context = null)
{
var message = new StringBuilder();
message.Append(method).Append(" ").Append(url);
message.Append(" -> HTTP ").Append(statusCode);
if (!string.IsNullOrWhiteSpace(context))
{
message.Append(" | ").Append(context);
}
if (!string.IsNullOrWhiteSpace(responseBody))
{
message.Append(" | body=").Append(Truncate(SanitizeJson(responseBody)));
}
Write("HTTP-ERR", "Api", message.ToString(), null);
}
public static string MaskToken(string token)
{
if (string.IsNullOrWhiteSpace(token))
@ -148,6 +174,12 @@ namespace Электронная_Фармация.Classes
}
sb.Append(current.GetType().Name).Append(": ").Append(current.Message);
if (!string.IsNullOrWhiteSpace(current.StackTrace) && depth == 0)
{
sb.AppendLine();
sb.Append(current.StackTrace);
}
current = current.InnerException;
depth++;
}
@ -172,7 +204,8 @@ namespace Электронная_Фармация.Classes
if (exception != null)
{
line.Append(" | ").Append(FormatException(exception));
line.AppendLine();
line.Append(FormatException(exception));
}
lock (Sync)

View File

@ -10,6 +10,8 @@ using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using System.Windows.Forms;
using Электроннаяармация.HelpForms;
using Электроннаяармация.Properties;
namespace Электроннаяармация.Classes
@ -28,20 +30,36 @@ namespace Электронная_Фармация.Classes
if (string.IsNullOrWhiteSpace(_token))
{
AppDebugLog.Error("DataSender", "Отправка заказов: токен отсутствует");
ToastNotification.ShowError("Не найден токен авторизации.");
return;
}
if (string.IsNullOrWhiteSpace(AppConfig.LocationId))
{
if (!HF_LocationId.PromptAndSave(Form.ActiveForm)
|| string.IsNullOrWhiteSpace(AppConfig.LocationId))
{
AppDebugLog.Error("DataSender", "Отправка заказов: location_id не указан");
ToastNotification.ShowCustom(
"Не указан location_id. Заполните его в окне авторизации/настроек.",
"Не указан location_id. Заполните его после авторизации.",
Color.DarkOrange,
Color.White);
return;
}
}
var orders = FetchDataFromSql(null);
List<BuyerOrderRequest> orders;
try
{
orders = FetchDataFromSql(null);
}
catch (Exception ex)
{
AppDebugLog.Error("DataSender", "Ошибка подготовки заказов к отправке", ex);
ToastNotification.ShowError($"Ошибка подготовки заказов: {ex.Message}");
return;
}
if (orders.Count == 0)
{
ToastNotification.ShowCustom("Нет новых заказов для отправки.", Color.DarkOrange, Color.White);
@ -87,20 +105,36 @@ namespace Электронная_Фармация.Classes
if (string.IsNullOrWhiteSpace(_token))
{
AppDebugLog.Error("DataSender", $"Отправка заказа {localOrderId}: токен отсутствует");
ToastNotification.ShowError("Не найден токен авторизации.");
return false;
}
if (string.IsNullOrWhiteSpace(AppConfig.LocationId))
{
if (!HF_LocationId.PromptAndSave(Form.ActiveForm)
|| string.IsNullOrWhiteSpace(AppConfig.LocationId))
{
AppDebugLog.Error("DataSender", $"Отправка заказа {localOrderId}: location_id не указан");
ToastNotification.ShowCustom(
"Не указан location_id. Заполните его в окне авторизации/настроек.",
"Не указан location_id. Заполните его после авторизации.",
Color.DarkOrange,
Color.White);
return false;
}
}
var orders = FetchDataFromSql(localOrderId);
List<BuyerOrderRequest> orders;
try
{
orders = FetchDataFromSql(localOrderId);
}
catch (Exception ex)
{
AppDebugLog.Error("DataSender", $"Ошибка подготовки заказа {localOrderId}", ex);
ToastNotification.ShowError($"Ошибка подготовки заказа: {ex.Message}");
return false;
}
if (orders.Count == 0)
{
ToastNotification.ShowCustom("Заказ не найден или уже отправлен.", Color.DarkOrange, Color.White);
@ -218,7 +252,15 @@ WHERE Orders.OrderState = 'НОВЫЙ'
if (!response.IsSuccessStatusCode)
{
AppDebugLog.Error("DataSender", $"Ошибка отправки заказа {order.LocalOrderId}");
AppDebugLog.ApiHttpError(
"POST",
fullUrl,
(int)response.StatusCode,
responseString,
$"order={order.LocalOrderId}");
AppDebugLog.Error(
"DataSender",
$"Ошибка отправки заказа {order.LocalOrderId}: HTTP {(int)response.StatusCode} {response.ReasonPhrase}");
ToastNotification.ShowError(
$"Ошибка отправки заказа {order.LocalOrderId}: {(int)response.StatusCode} {response.ReasonPhrase}\n{responseString}");
return false;

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Threading.Tasks;
@ -15,13 +16,26 @@ namespace Электронная_Фармация.Classes
var token = Settings.Default.stringToken;
if (string.IsNullOrWhiteSpace(token))
{
throw new System.InvalidOperationException("Необходима авторизация перед загрузкой накладных.");
var ex = new InvalidOperationException("Необходима авторизация перед загрузкой накладных.");
AppDebugLog.Error("InvoiceSync", "Синхронизация накладных без токена", ex);
throw ex;
}
try
{
var client = new ApiClient();
client.SetToken(token);
AppDebugLog.Info("InvoiceSync", $"Старт синхронизации накладных. Сервер={client.BaseUrl}");
var items = await client.GetInvoicesAsync();
return SaveInvoices(items);
var saved = SaveInvoices(items);
AppDebugLog.Info("InvoiceSync", $"Накладные сохранены локально: {saved} шт.");
return saved;
}
catch (Exception ex)
{
AppDebugLog.Error("InvoiceSync", "Ошибка синхронизации накладных с API", ex);
throw;
}
}
private static int SaveInvoices(IReadOnlyList<InvoiceApiItem> items)

View File

@ -175,6 +175,12 @@
<Compile Include="HelpForms\HF_DownloadDataFromServer.Designer.cs">
<DependentUpon>HF_DownloadDataFromServer.cs</DependentUpon>
</Compile>
<Compile Include="HelpForms\HF_LocationId.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="HelpForms\HF_LocationId.Designer.cs">
<DependentUpon>HF_LocationId.cs</DependentUpon>
</Compile>
<Compile Include="HelpForms\HF_Registration.cs">
<SubType>Form</SubType>
</Compile>

View File

@ -500,6 +500,7 @@ namespace Электронная_Фармация
}
catch (Exception ex)
{
AppDebugLog.Error("Form1", "Не удалось отправить данные (меню Обмен)", ex);
ToastNotification.ShowError($"Не удалось отправить данные. {ex.Message}");
//MessageBox.Show(ex.ToString(), "Ошибка отправки данных на сервер", MessageBoxButtons.OK, MessageBoxIcon.Error);
//MessageBox.Show($"Не удалось отправить данные на сервер. Текст ошибки:\n{ex.ToString()}", "Ошибка отправки данных на сервер", MessageBoxButtons.OK, MessageBoxIcon.Error);
@ -604,6 +605,7 @@ namespace Электронная_Фармация
}
catch (Exception ex)
{
AppDebugLog.Error("Form1", "Не удалось отправить данные (кнопка ОТПРАВИТЬ)", ex);
ToastNotification.ShowError($"Не удалось отправить данные. {ex.Message}");
}
finally

View File

@ -0,0 +1,131 @@
namespace Электроннаяармация.HelpForms
{
partial class HF_LocationId
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
this.panelHeader = new System.Windows.Forms.Panel();
this.lblTitle = new System.Windows.Forms.Label();
this.lblHint = new System.Windows.Forms.Label();
this.lblLocationId = new System.Windows.Forms.Label();
this.txtLocationId = new Elfisa.UI.Controls.ModernTextBox();
this.btnCancel = new Elfisa.UI.Controls.ModernButton();
this.btnSave = new Elfisa.UI.Controls.ModernButton();
this.panelHeader.SuspendLayout();
this.SuspendLayout();
//
// panelHeader
//
this.panelHeader.Controls.Add(this.lblTitle);
this.panelHeader.Dock = System.Windows.Forms.DockStyle.Top;
this.panelHeader.Location = new System.Drawing.Point(0, 0);
this.panelHeader.Name = "panelHeader";
this.panelHeader.Size = new System.Drawing.Size(460, 56);
this.panelHeader.TabIndex = 0;
//
// lblTitle
//
this.lblTitle.Font = new System.Drawing.Font("Segoe UI Semibold", 13.8F, System.Drawing.FontStyle.Bold);
this.lblTitle.ForeColor = System.Drawing.Color.White;
this.lblTitle.Location = new System.Drawing.Point(12, 10);
this.lblTitle.Name = "lblTitle";
this.lblTitle.Size = new System.Drawing.Size(430, 36);
this.lblTitle.TabIndex = 0;
this.lblTitle.Text = "Location ID";
this.lblTitle.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// lblHint
//
this.lblHint.Font = new System.Drawing.Font("Segoe UI", 10F);
this.lblHint.Location = new System.Drawing.Point(20, 70);
this.lblHint.Name = "lblHint";
this.lblHint.Size = new System.Drawing.Size(420, 48);
this.lblHint.TabIndex = 1;
this.lblHint.Text = "Укажите UUID торговой точки. Он будет использоваться при отправке заказов на сервер.";
//
// lblLocationId
//
this.lblLocationId.AutoSize = true;
this.lblLocationId.Font = new System.Drawing.Font("Segoe UI", 12F);
this.lblLocationId.Location = new System.Drawing.Point(20, 128);
this.lblLocationId.Name = "lblLocationId";
this.lblLocationId.Size = new System.Drawing.Size(108, 28);
this.lblLocationId.TabIndex = 2;
this.lblLocationId.Text = "Location ID";
//
// txtLocationId
//
this.txtLocationId.Font = new System.Drawing.Font("Segoe UI", 12F);
this.txtLocationId.Location = new System.Drawing.Point(24, 160);
this.txtLocationId.Name = "txtLocationId";
this.txtLocationId.Size = new System.Drawing.Size(412, 34);
this.txtLocationId.TabIndex = 3;
//
// btnCancel
//
this.btnCancel.Location = new System.Drawing.Point(24, 220);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(125, 40);
this.btnCancel.TabIndex = 4;
this.btnCancel.Text = "Отмена";
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
//
// btnSave
//
this.btnSave.Location = new System.Drawing.Point(221, 220);
this.btnSave.Name = "btnSave";
this.btnSave.Size = new System.Drawing.Size(215, 40);
this.btnSave.TabIndex = 5;
this.btnSave.Text = "Сохранить";
this.btnSave.Click += new System.EventHandler(this.btnSave_Click);
//
// HF_LocationId
//
this.AcceptButton = this.btnSave;
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.btnCancel;
this.ClientSize = new System.Drawing.Size(460, 280);
this.Controls.Add(this.btnSave);
this.Controls.Add(this.btnCancel);
this.Controls.Add(this.txtLocationId);
this.Controls.Add(this.lblLocationId);
this.Controls.Add(this.lblHint);
this.Controls.Add(this.panelHeader);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "HF_LocationId";
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Load += new System.EventHandler(this.HF_LocationId_Load);
this.panelHeader.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Panel panelHeader;
private System.Windows.Forms.Label lblTitle;
private System.Windows.Forms.Label lblHint;
private System.Windows.Forms.Label lblLocationId;
private Elfisa.UI.Controls.ModernTextBox txtLocationId;
private Elfisa.UI.Controls.ModernButton btnCancel;
private Elfisa.UI.Controls.ModernButton btnSave;
}
}

View File

@ -0,0 +1,62 @@
using System;
using System.Drawing;
using System.Windows.Forms;
using Elfisa.UI.Helpers;
using Elfisa.UI.Theming;
using Электроннаяармация.Classes;
using Электроннаяармация.Properties;
namespace Электроннаяармация.HelpForms
{
public partial class HF_LocationId : Form
{
public HF_LocationId()
{
InitializeComponent();
Shown += (_, __) => WindowChromeHelper.ApplyTitleBarTheme(this, ThemeManager.Colors);
}
/// <summary>
/// Показывает диалог Location ID. Возвращает true, если значение сохранено.
/// </summary>
public static bool PromptAndSave(IWin32Window owner)
{
using (var form = new HF_LocationId())
{
UiThemeHelper.ApplyToControlTree(form);
return form.ShowDialog(owner) == DialogResult.OK;
}
}
private void HF_LocationId_Load(object sender, EventArgs e)
{
UiThemeHelper.ApplyToControlTree(this);
WindowChromeHelper.ApplyDialogChrome(this, panelHeader, lblTitle);
txtLocationId.Text = Settings.Default.LocationId ?? string.Empty;
txtLocationId.SelectAll();
txtLocationId.Focus();
}
private void btnSave_Click(object sender, EventArgs e)
{
var locationId = (txtLocationId.Text ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(locationId))
{
UiDialogs.ShowError("Укажите Location ID торговой точки.", "Location ID", this);
txtLocationId.Focus();
return;
}
AppConfig.SetLocationId(locationId);
AppDebugLog.Info("Auth", $"LocationId сохранён: {locationId}");
DialogResult = DialogResult.OK;
Close();
}
private void btnCancel_Click(object sender, EventArgs e)
{
DialogResult = DialogResult.Cancel;
Close();
}
}
}

View File

@ -38,8 +38,6 @@
this.checkTokenGained = new System.Windows.Forms.CheckBox();
this.panel1 = new System.Windows.Forms.Panel();
this.label3 = new System.Windows.Forms.Label();
this.label4 = new System.Windows.Forms.Label();
this.txtLocationId = new Elfisa.UI.Controls.ModernTextBox();
this.label5 = new System.Windows.Forms.Label();
this.txtApiUrl = new Elfisa.UI.Controls.ModernTextBox();
this.panel1.SuspendLayout();
@ -83,16 +81,16 @@
//
// btnConfirmRegistration
//
this.btnConfirmRegistration.Location = new System.Drawing.Point(188, 401);
this.btnConfirmRegistration.Location = new System.Drawing.Point(188, 300);
this.btnConfirmRegistration.Name = "btnConfirmRegistration";
this.btnConfirmRegistration.Size = new System.Drawing.Size(215, 43);
this.btnConfirmRegistration.TabIndex = 5;
this.btnConfirmRegistration.Text = "Зарегистрировать";
this.btnConfirmRegistration.Text = "Войти";
this.btnConfirmRegistration.Click += new System.EventHandler(this.btnConfirmRegistration_Click);
//
// btnCloseThis
//
this.btnCloseThis.Location = new System.Drawing.Point(28, 401);
this.btnCloseThis.Location = new System.Drawing.Point(28, 300);
this.btnCloseThis.Name = "btnCloseThis";
this.btnCloseThis.Size = new System.Drawing.Size(125, 43);
this.btnCloseThis.TabIndex = 6;
@ -104,7 +102,7 @@
this.checkTokenGained.AutoCheck = false;
this.checkTokenGained.AutoSize = true;
this.checkTokenGained.Font = new System.Drawing.Font("Segoe UI", 12F);
this.checkTokenGained.Location = new System.Drawing.Point(28, 342);
this.checkTokenGained.Location = new System.Drawing.Point(28, 260);
this.checkTokenGained.Name = "checkTokenGained";
this.checkTokenGained.Size = new System.Drawing.Size(171, 32);
this.checkTokenGained.TabIndex = 7;
@ -135,29 +133,11 @@
this.label3.Text = "АВТОРИЗАЦИЯ";
this.label3.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// label4
//
this.label4.AutoSize = true;
this.label4.Font = new System.Drawing.Font("Segoe UI", 12F);
this.label4.Location = new System.Drawing.Point(23, 225);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(108, 28);
this.label4.TabIndex = 9;
this.label4.Text = "Location ID";
//
// txtLocationId
//
this.txtLocationId.Font = new System.Drawing.Font("Segoe UI", 12F);
this.txtLocationId.Location = new System.Drawing.Point(28, 256);
this.txtLocationId.Name = "txtLocationId";
this.txtLocationId.Size = new System.Drawing.Size(375, 34);
this.txtLocationId.TabIndex = 10;
//
// label5
//
this.label5.AutoSize = true;
this.label5.Font = new System.Drawing.Font("Segoe UI", 12F);
this.label5.Location = new System.Drawing.Point(23, 296);
this.label5.Location = new System.Drawing.Point(23, 225);
this.label5.Name = "label5";
this.label5.Size = new System.Drawing.Size(103, 28);
this.label5.TabIndex = 11;
@ -166,7 +146,7 @@
// txtApiUrl
//
this.txtApiUrl.Font = new System.Drawing.Font("Segoe UI", 12F);
this.txtApiUrl.Location = new System.Drawing.Point(132, 296);
this.txtApiUrl.Location = new System.Drawing.Point(132, 225);
this.txtApiUrl.Name = "txtApiUrl";
this.txtApiUrl.Size = new System.Drawing.Size(271, 34);
this.txtApiUrl.TabIndex = 12;
@ -175,11 +155,9 @@
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(436, 456);
this.ClientSize = new System.Drawing.Size(436, 360);
this.Controls.Add(this.txtApiUrl);
this.Controls.Add(this.label5);
this.Controls.Add(this.txtLocationId);
this.Controls.Add(this.label4);
this.Controls.Add(this.panel1);
this.Controls.Add(this.checkTokenGained);
this.Controls.Add(this.btnCloseThis);
@ -199,7 +177,6 @@
this.panel1.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
@ -213,8 +190,6 @@
private System.Windows.Forms.CheckBox checkTokenGained;
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.Label label4;
private Elfisa.UI.Controls.ModernTextBox txtLocationId;
private System.Windows.Forms.Label label5;
private Elfisa.UI.Controls.ModernTextBox txtApiUrl;
}

View File

@ -1,19 +1,10 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Электроннаяармация.Properties;
using System.Text.Json;
using System.Text.Json.Serialization;
using Электроннаяармация.Classes;
using System.Net.Http;
using System.Windows.Forms;
using Elfisa.UI.Helpers;
using Elfisa.UI.Theming;
using Электроннаяармация.Classes;
using Электроннаяармация.Properties;
namespace Электроннаяармация.HelpForms
{
@ -29,17 +20,16 @@ namespace Электронная_Фармация.HelpForms
string password = Settings.Default.stringPassword.ToString();
string doWeHaveAToken = Settings.Default.stringToken.ToString();
private void btnCloseThis_Click(object sender, EventArgs e)
{
this.Close();
Close();
}
private async void btnConfirmRegistration_Click(object sender, EventArgs e)
{
btnConfirmRegistration.Enabled = false;
btnCloseThis.Enabled = false;
this.UseWaitCursor = true;
UseWaitCursor = true;
string strLogin = txtLogin.Text;
string strPassword = txtPassword.Text;
@ -49,7 +39,6 @@ namespace Электронная_Фармация.HelpForms
// на старый сохранённый/дефолтный адрес, а не на введённый пользователем.
string apiUrl = txtApiUrl.Text.Trim();
Settings.Default.ApiBaseUrl = apiUrl;
Settings.Default.LocationId = txtLocationId.Text.Trim();
Settings.Default.Save();
LoginRequest loginRequest = new LoginRequest();
@ -66,18 +55,24 @@ namespace Электронная_Фармация.HelpForms
Settings.Default.stringLogin = strLogin;
Settings.Default.stringPassword = strPassword;
Settings.Default.stringToken = token;
Settings.Default.LocationId = txtLocationId.Text.Trim();
Settings.Default.ApiBaseUrl = client.BaseUrl;
Settings.Default.Save();
AppDebugLog.Info("Auth", $"Авторизация успешна. token={AppDebugLog.MaskToken(token)}");
ToastNotification.ShowSuccess("Авторизация успешно выполнена");
//MessageBox.Show("Данные были сохранены", "Регистрация программы успешно выполнена", MessageBoxButtons.OK, MessageBoxIcon.Information);
// Токен автоматически сохраняется в клиенте
// и будет использоваться для всех последующих запросов
// После входа обязательно запрашиваем Location ID для отправки заказов.
if (!HF_LocationId.PromptAndSave(this))
{
ToastNotification.ShowCustom(
"Location ID не указан. Отправка заказов будет недоступна, пока его не заполните.",
System.Drawing.Color.DarkOrange,
System.Drawing.Color.White);
}
DialogResult = DialogResult.OK;
Close();
return;
}
catch (UnauthorizedAccessException ex)
{
@ -94,10 +89,10 @@ namespace Электронная_Фармация.HelpForms
{
UiDialogs.ShowError("Есть незаполненные данные. Регистрация программы не пройдена", "Ошибка ввода", this);
}
btnConfirmRegistration.Enabled = true;
btnCloseThis.Enabled = true;
this.UseWaitCursor = false;
UseWaitCursor = false;
}
private void HF_Registration_Load(object sender, EventArgs e)
@ -106,19 +101,11 @@ namespace Электронная_Фармация.HelpForms
WindowChromeHelper.ApplyDialogChrome(this, panel1, label3);
txtLogin.Text = login;
txtPassword.Text = password;
txtLocationId.Text = Settings.Default.LocationId ?? string.Empty;
txtApiUrl.Text = string.IsNullOrWhiteSpace(Settings.Default.ApiBaseUrl)
? AppConfig.DefaultApiBaseUrl
: Settings.Default.ApiBaseUrl;
if (doWeHaveAToken.Length > 0)
{
checkTokenGained.Checked = true;
}
else
{
checkTokenGained.Checked = false;
}
checkTokenGained.Checked = doWeHaveAToken.Length > 0;
}
}
}

View File

@ -39,9 +39,8 @@ namespace Электронная_Фармация.HelpForms
}
catch(Exception ex)
{
AppDebugLog.Error("Upload", "Не удалось отправить данные на сервер", ex);
ToastNotification.ShowError($"Не удалось отправить данные. {ex.Message}");
//MessageBox.Show(ex.ToString(), "Ошибка отправки данных на сервер", MessageBoxButtons.OK, MessageBoxIcon.Error);
//MessageBox.Show($"Не удалось отправить данные на сервер. Текст ошибки:\n{ex.ToString()}", "Ошибка отправки данных на сервер", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}

View File

@ -355,8 +355,17 @@ namespace Электронная_Фармация.UserControls
}
var orderId = DGVOrders.SelectedRows[0].Cells["ИД заказа"].Value.ToString();
try
{
var sender = new DataSender();
await sender.SendSingleOrderAsync(orderId);
}
catch (Exception ex)
{
AppDebugLog.Error("Orders", $"Не удалось отправить заказ {orderId}", ex);
ToastNotification.ShowError($"Не удалось отправить заказ: {ex.Message}");
}
ApplyOrderFilters();
}

View File

@ -237,8 +237,7 @@
//
// UCInvoices
//
this.AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
this.Controls.Add(this.btnClearConsignees);
this.Controls.Add(this.btnClearSuppliers);
this.Controls.Add(this.PContentInvoice);

View File

@ -18,6 +18,8 @@ namespace Электронная_Фармация.UserControls
private string _initialSourceOrderId;
private ContextMenuStrip _invoiceMenu;
private ToolStripMenuItem _exportInvoiceMenuItem;
private Panel panelToolbar;
private bool _toolbarReady;
public UCInvoices()
: this(null)
@ -210,18 +212,170 @@ where date(i.[InvoiceDate]) between date(@dateFrom) and date(@dateTo)";
UiThemeHelper.ApplyToControlTree(this);
Font = new Font("Segoe UI", 9.75f, FontStyle.Regular);
InitializeInvoiceContextMenu();
EnsureInvoiceToolbar();
button1.Visible = true;
button1.Text = "Обновить с сервера";
button1.Width = 180;
ModernButtonStyles.ApplyAuto(button1);
button1.Click += async (_, __) => await SyncFromServerAsync();
button1.Click -= Button1_SyncClick;
button1.Click += Button1_SyncClick;
ModernButtonStyles.ApplyClear(btnClearSuppliers);
ModernButtonStyles.ApplyClear(btnClearConsignees);
btnClearSuppliers.Text = "×";
btnClearConsignees.Text = "×";
ApplyToolbarLayout();
loadConsignees();
loadSuppliers();
getDatesFromCalendar();
}
private async void Button1_SyncClick(object sender, EventArgs e)
{
await SyncFromServerAsync();
}
private void EnsureInvoiceToolbar()
{
if (_toolbarReady)
{
return;
}
panelToolbar = new Panel
{
Name = "panelToolbar",
Dock = DockStyle.Top,
Height = 96,
Padding = new Padding(12, 10, 12, 14),
Tag = "toolbar"
};
var toolbarControls = new Control[]
{
button1,
dtpDateFrom,
label1,
dtpDateTo,
txtInvoiceNumber,
comboSuppliers,
btnClearSuppliers,
comboConsignees,
btnClearConsignees
};
foreach (var control in toolbarControls)
{
Controls.Remove(control);
panelToolbar.Controls.Add(control);
}
Controls.Add(panelToolbar);
panelToolbar.BringToFront();
PContentInvoice.Dock = DockStyle.Fill;
PContentInvoice.BringToFront();
panelToolbar.BringToFront();
panelToolbar.Resize += (_, __) => ApplyToolbarLayout();
Resize += (_, __) => ApplyToolbarLayout();
_toolbarReady = true;
}
private void ApplyToolbarLayout()
{
if (panelToolbar == null)
{
return;
}
const int pad = 12;
const int gap = 8;
const int clearGap = 4;
const int controlHeight = 32;
const int buttonHeight = 34;
const int rowGap = 10;
const int refreshWidth = 180;
const int dateWidth = 112;
const int clearSize = 32;
const int invoicePreferred = 200;
const int invoiceMin = 140;
const int supplierPreferred = 170;
const int consigneePreferred = 190;
const int supplierMin = 130;
const int consigneeMin = 150;
var panelWidth = Math.Max(panelToolbar.ClientSize.Width, 360);
int y = 10;
int x = pad;
button1.SetBounds(x, y, refreshWidth, buttonHeight);
x = button1.Right + gap;
dtpDateFrom.SetBounds(x, y + 1, dateWidth, controlHeight);
x = dtpDateFrom.Right + 6;
label1.AutoSize = true;
label1.Location = new Point(x, y + 5);
x = label1.Right + 6;
dtpDateTo.SetBounds(x, y + 1, dateWidth, controlHeight);
x = dtpDateTo.Right + gap;
int invoiceWidth = Math.Max(invoiceMin, Math.Min(invoicePreferred, panelWidth - pad - x));
if (invoiceWidth < invoiceMin || x + invoiceMin > panelWidth - pad)
{
y += buttonHeight + rowGap;
x = pad;
invoiceWidth = Math.Max(invoiceMin, Math.Min(invoicePreferred, panelWidth - pad * 2));
}
txtInvoiceNumber.SetBounds(x, y + 1, invoiceWidth, controlHeight);
y += buttonHeight + rowGap;
x = pad;
int filtersAvailable = panelWidth - pad * 2 - clearSize * 2 - clearGap * 2 - gap;
int supplierWidth;
int consigneeWidth;
if (filtersAvailable >= supplierPreferred + consigneePreferred)
{
supplierWidth = supplierPreferred;
consigneeWidth = consigneePreferred;
}
else
{
float scale = filtersAvailable / (float)(supplierPreferred + consigneePreferred);
supplierWidth = Math.Max(supplierMin, (int)(supplierPreferred * scale));
consigneeWidth = Math.Max(consigneeMin, filtersAvailable - supplierWidth);
}
if (pad + supplierWidth + clearGap + clearSize + gap + consigneeWidth + clearGap + clearSize > panelWidth)
{
// Wrap consignee to next row on very narrow widths.
supplierWidth = Math.Max(supplierMin, panelWidth - pad * 2 - clearGap - clearSize);
comboSuppliers.SetBounds(x, y + 1, supplierWidth, controlHeight);
btnClearSuppliers.SetBounds(comboSuppliers.Right + clearGap, y, clearSize, controlHeight);
y += buttonHeight + rowGap;
x = pad;
consigneeWidth = Math.Max(consigneeMin, panelWidth - pad * 2 - clearGap - clearSize);
comboConsignees.SetBounds(x, y + 1, consigneeWidth, controlHeight);
btnClearConsignees.SetBounds(comboConsignees.Right + clearGap, y, clearSize, controlHeight);
}
else
{
comboSuppliers.SetBounds(x, y + 1, supplierWidth, controlHeight);
btnClearSuppliers.SetBounds(comboSuppliers.Right + clearGap, y, clearSize, controlHeight);
x = btnClearSuppliers.Right + gap;
comboConsignees.SetBounds(x, y + 1, consigneeWidth, controlHeight);
btnClearConsignees.SetBounds(comboConsignees.Right + clearGap, y, clearSize, controlHeight);
}
panelToolbar.Height = y + buttonHeight + pad + 4;
panelToolbar.Padding = new Padding(pad, 10, pad, 14);
}
private async Task SyncFromServerAsync()
{
button1.Enabled = false;
@ -235,6 +389,7 @@ where date(i.[InvoiceDate]) between date(@dateFrom) and date(@dateTo)";
}
catch (Exception ex)
{
AppDebugLog.Error("Invoices", "Не удалось загрузить накладные с API", ex);
ToastNotification.ShowError($"Не удалось загрузить накладные: {ex.Message}");
}
finally

View File

@ -32,6 +32,7 @@
this.dgvOrder = new Elfisa.UI.Controls.ModernDataGridView();
this.lblSumOfOrder = new System.Windows.Forms.Label();
this.btnEraseOrder = new Elfisa.UI.Controls.ModernButton();
this.btnDeleteGood = new Elfisa.UI.Controls.ModernButton();
this.btnSendOrder = new Elfisa.UI.Controls.ModernButton();
this.button1 = new Elfisa.UI.Controls.ModernButton();
this.lblMinimalSumOfOrder = new System.Windows.Forms.Label();
@ -93,6 +94,18 @@
this.btnEraseOrder.Text = "Очистить корзину";
this.btnEraseOrder.Click += new System.EventHandler(this.btnEraseOrder_Click);
//
// btnDeleteGood
//
this.btnDeleteGood.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnDeleteGood.Font = new System.Drawing.Font("Segoe UI Semibold", 12F, System.Drawing.FontStyle.Bold);
this.btnDeleteGood.Location = new System.Drawing.Point(538, 36);
this.btnDeleteGood.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.btnDeleteGood.Name = "btnDeleteGood";
this.btnDeleteGood.Size = new System.Drawing.Size(260, 38);
this.btnDeleteGood.TabIndex = 17;
this.btnDeleteGood.Text = "Удалить товар";
this.btnDeleteGood.Click += new System.EventHandler(this.btnDeleteGood_Click);
//
// btnSendOrder
//
this.btnSendOrder.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
@ -184,6 +197,7 @@
this.Controls.Add(this.btnSaveAndSendOrder);
this.Controls.Add(this.lblMinimalSumOfOrder);
this.Controls.Add(this.lblSumOfOrder);
this.Controls.Add(this.btnDeleteGood);
this.Controls.Add(this.btnEraseOrder);
this.Controls.Add(this.btnSendOrder);
this.Controls.Add(this.button1);
@ -205,6 +219,7 @@
private Elfisa.UI.Controls.ModernDataGridView dgvOrder;
private System.Windows.Forms.Label lblSumOfOrder;
private Elfisa.UI.Controls.ModernButton btnEraseOrder;
private Elfisa.UI.Controls.ModernButton btnDeleteGood;
private Elfisa.UI.Controls.ModernButton btnSendOrder;
private Elfisa.UI.Controls.ModernButton button1;
private System.Windows.Forms.Label lblMinimalSumOfOrder;

View File

@ -248,6 +248,78 @@ and ConsigneesName = '{consigneeName}'
}
}
private void btnDeleteGood_Click(object sender, EventArgs e)
{
if (dgvOrder.SelectedRows.Count == 0 || dgvOrder.CurrentRow == null)
{
ToastNotification.ShowCustom("Выберите наименование в корзине для удаления.", Color.DarkOrange, Color.White);
return;
}
var row = dgvOrder.SelectedRows[0];
string idPriceListItem = null;
if (dgvOrder.Columns.Contains("id_PriceList_Item"))
{
idPriceListItem = row.Cells["id_PriceList_Item"]?.Value?.ToString();
}
else if (row.Cells.Count > 11)
{
idPriceListItem = row.Cells[11]?.Value?.ToString();
}
if (string.IsNullOrWhiteSpace(idPriceListItem))
{
ToastNotification.ShowCustom("Не удалось определить позицию для удаления.", Color.DarkOrange, Color.White);
return;
}
var drugName = dgvOrder.Columns.Contains("Наименование")
? row.Cells["Наименование"]?.Value?.ToString()
: row.Cells.Count > 3
? row.Cells[3]?.Value?.ToString()
: null;
DeleteCartItem(idPriceListItem, drugName);
}
private void DeleteCartItem(string idPriceListItem, string drugName)
{
var safeId = idPriceListItem.Replace("'", "''");
string qClearPriceList = $"update [PriceList] set [Zakaz] = null, [SummaZakaza] = null where id_PriceList_Item = '{safeId}'";
string qDeleteFromTempOrder = $"delete from [TempOrderItems] where [id_PriceList_Item] = '{safeId}'";
using (var sqlCon = new SQLiteConnection(connectionStringToLocalDB))
{
try
{
sqlCon.Open();
using (var cmdClear = new SQLiteCommand(qClearPriceList, sqlCon))
using (var cmdDelete = new SQLiteCommand(qDeleteFromTempOrder, sqlCon))
{
cmdClear.ExecuteNonQuery();
cmdDelete.ExecuteNonQuery();
}
if (!string.IsNullOrWhiteSpace(drugName))
{
ToastNotification.ShowSuccess($"Удалено: {drugName}");
}
}
catch (Exception ex)
{
ToastNotification.ShowError($"Ошибка удаления товара: {ex.Message}");
}
finally
{
sqlCon.Close();
loadDataAboutOrderV2(_supplierName);
ParentForm?.loadPriceListV2();
ParentForm?.loadTempOrderItems();
}
}
}
void EraseOrder(string Supplier)
{
using(SQLiteConnection sqlConForDeleteTempOrder = new SQLiteConnection(connectionStringToLocalDB))

View File

@ -36,7 +36,6 @@
this.TLPPriceList = new System.Windows.Forms.TableLayoutPanel();
this.dgvPriceList = new Elfisa.UI.Controls.ModernDataGridView();
this.panel1 = new System.Windows.Forms.Panel();
this.btnDeleteGood = new Elfisa.UI.Controls.ModernButton();
this.checkUsePercentageAsDefault = new System.Windows.Forms.CheckBox();
this.lblPriceForGood = new System.Windows.Forms.Label();
this.numericPercent = new System.Windows.Forms.NumericUpDown();
@ -149,7 +148,6 @@
//
// panel1
//
this.panel1.Controls.Add(this.btnDeleteGood);
this.panel1.Controls.Add(this.checkUsePercentageAsDefault);
this.panel1.Controls.Add(this.lblPriceForGood);
this.panel1.Controls.Add(this.numericPercent);
@ -163,17 +161,6 @@
this.panel1.Size = new System.Drawing.Size(1039, 54);
this.panel1.TabIndex = 1;
//
// btnDeleteGood
//
this.btnDeleteGood.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnDeleteGood.Font = new System.Drawing.Font("Segoe UI Semibold", 12F, System.Drawing.FontStyle.Bold);
this.btnDeleteGood.Location = new System.Drawing.Point(8, 62);
this.btnDeleteGood.Name = "btnDeleteGood";
this.btnDeleteGood.Size = new System.Drawing.Size(360, 34);
this.btnDeleteGood.TabIndex = 13;
this.btnDeleteGood.Text = "Удалить товар (BACKSPACE)";
this.btnDeleteGood.Click += new System.EventHandler(this.btnDeleteGood_Click);
//
// checkUsePercentageAsDefault
//
this.checkUsePercentageAsDefault.AutoSize = true;
@ -514,6 +501,5 @@
private Elfisa.UI.Controls.ModernButton btnClearSupplierFilter;
private System.Windows.Forms.CheckBox checkUsePercentageAsDefault;
private System.Windows.Forms.Timer timerForResetSearch;
private Elfisa.UI.Controls.ModernButton btnDeleteGood;
}
}

View File

@ -75,7 +75,6 @@ namespace Электронная_Фармация.UserControls
UiThemeHelper.ApplyToControlTree(this);
Tag = "chrome-child";
btnDeleteGood.Height = 34;
AlignSearchRow();
panel1.SizeChanged += (_, __) => LayoutMarkupRow();
@ -870,6 +869,61 @@ namespace Электронная_Фармация.UserControls
ShowBrowseView();
}
private void HandleBackspaceInPriceList(int rowIndex, string idPriceListItem)
{
if (_browseMode == PriceBrowseMode.Offers)
{
if (rowIndex >= 0
&& rowIndex < dgvPriceList.Rows.Count
&& dgvPriceList.Rows[rowIndex].Cells.Count > 9)
{
var qty = dgvPriceList.Rows[rowIndex].Cells[9].Value?.ToString() ?? string.Empty;
if (!string.IsNullOrWhiteSpace(qty))
{
if (!string.IsNullOrEmpty(idPriceListItem))
{
deleteGood(rowIndex, idPriceListItem);
}
else
{
dgvPriceList.Rows[rowIndex].Cells[9].Value = string.Empty;
dgvPriceList.Rows[rowIndex].Cells[10].Value = string.Empty;
}
return;
}
}
ResetBrowseToNames();
if (dgvBrowseNames != null && dgvBrowseNames.Visible)
{
dgvBrowseNames.Focus();
}
return;
}
if (GoodsFilter.Length == 0)
{
return;
}
GoodsFilter = GoodsFilter.Substring(0, GoodsFilter.Length - 1);
txtSearchGoodNameInPriceList.Text = GoodsFilter;
ResetBrowseToNames();
if (GoodsFilter.Length == 0)
{
timerForResetSearch.Enabled = false;
timerForResetSearch.Stop();
}
else
{
timerForResetSearch.Enabled = true;
timerForResetSearch.Start();
}
}
void loadInfoAboutDefaultMarkup()
{
@ -1411,28 +1465,14 @@ and tempOrder.GoodName = PriceList.GoodName
}
}
if (_browseMode != PriceBrowseMode.Offers)
if (e.KeyChar == (Char)Keys.Back)
{
if (e.KeyChar == (Char)Keys.Back && GoodsFilter.Length > 0)
{
GoodsFilter = GoodsFilter.Substring(0, GoodsFilter.Length - 1);
txtSearchGoodNameInPriceList.Text = GoodsFilter;
ResetBrowseToNames();
if (GoodsFilter.Length == 0)
{
timerForResetSearch.Enabled = false;
timerForResetSearch.Stop();
}
else
{
timerForResetSearch.Enabled = true;
timerForResetSearch.Start();
}
HandleBackspaceInPriceList(RowIndex, idPriceListItem);
return;
}
if (_browseMode != PriceBrowseMode.Offers)
{
if ((e.KeyChar >= 'A' && e.KeyChar <= 'Z')
|| (e.KeyChar >= 'a' && e.KeyChar <= 'z')
|| (e.KeyChar >= 'А' && e.KeyChar <= 'Я')
@ -1443,7 +1483,7 @@ and tempOrder.GoodName = PriceList.GoodName
GoodsFilter += e.KeyChar.ToString();
txtSearchGoodNameInPriceList.Text = GoodsFilter;
ResetBrowseToNames();
if (dgvPriceList.Rows.Count == 0)
if (dgvBrowseNames != null && dgvBrowseNames.Rows.Count == 0)
{
UiDialogs.ShowInfo("Подходящий товар не найден.", "Поиск", FindForm());
GoodsFilter = string.Empty;
@ -1455,36 +1495,6 @@ and tempOrder.GoodName = PriceList.GoodName
return;
}
if (e.KeyChar == (Char)Keys.Back && GoodsFilter.Length > 0)
{
GoodsFilter = GoodsFilter.Substring(0, GoodsFilter.Length - 1);
txtSearchGoodNameInPriceList.Text = GoodsFilter;
ResetBrowseToNames();
if (GoodsFilter.Length == 0)
{
timerForResetSearch.Enabled = false;
timerForResetSearch.Stop();
}
else
{
timerForResetSearch.Enabled = true;
timerForResetSearch.Start();
}
return;
}
if (timerForResetSearch.Enabled == false)
{
if (RowIndex >= 0 && !string.IsNullOrEmpty(idPriceListItem))
{
deleteGood(RowIndex, idPriceListItem);
timerForResetSearch.Enabled = true;
timerForResetSearch.Start();
}
}
if ((e.KeyChar >= 'A' && e.KeyChar <= 'Z') || (e.KeyChar >= 'a' && e.KeyChar <= 'z') || (e.KeyChar >= 'А' && e.KeyChar <= 'Я') || (e.KeyChar >= 'а' && e.KeyChar <= 'я') || (e.KeyChar == (Char)Keys.Subtract))
{
if (e.KeyChar == (Char)Keys.Enter)
@ -1573,26 +1583,6 @@ and tempOrder.GoodName = PriceList.GoodName
}
}
}
else if (e.KeyChar == (Char)Keys.Back)
{
if (RowIndex < 0) return;
string countToBuy = dgvPriceList.Rows[RowIndex].Cells[9].Value.ToString();
if (countToBuy.Length > 1)
{
string result = countToBuy.Remove(countToBuy.Length - 1);
dgvPriceList.Rows[RowIndex].Cells[9].Value = result;
if (!TryReadDecimal(dgvPriceList.Rows[RowIndex].Cells[5].Value, out var priceForOne) ||
!TryReadInt32(result, out var count))
{
return;
}
dgvPriceList.Rows[RowIndex].Cells[10].Value = (count * priceForOne).ToString();
}
}
else if (e.KeyChar == (Char)Keys.Delete)
{
if (RowIndex < 0 || string.IsNullOrEmpty(idPriceListItem)) return;
@ -2341,14 +2331,6 @@ and SumOrderedItems = '{SumOrder}'";
timerForResetSearch.Stop();
}
private void btnDeleteGood_Click(object sender, EventArgs e)
{
int RowIndex = dgvPriceList.SelectedRows[0].Index;
string idPriceListItem = dgvPriceList.Rows[RowIndex].Cells[11].Value.ToString();
deleteGood(RowIndex, idPriceListItem);
}
private void btnMoreInfoAboutGood_Click(object sender, EventArgs e)
{
if (dgvPriceList.CurrentRow == null)

View File

@ -109,6 +109,7 @@ namespace Электронная_Фармация.UserControls
}
catch (Exception ex)
{
AppDebugLog.Error("Refusals", "Не удалось загрузить отказы с API", ex);
ToastNotification.ShowError($"Не удалось загрузить отказы: {ex.Message}");
}
finally