Fix price list parsing crashes and invoice grid initialization.

Harden UCPriceList number parsing and selection handling after download, guard ModernDataGridView row header autosize, and refresh price list from menu load action.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Magomed 2026-07-15 12:15:40 +03:00
parent 5cc202dea7
commit 0c73d7e40b
8 changed files with 335 additions and 57 deletions

View File

@ -3,5 +3,6 @@
1. Распакуйте архив в любую папку.
2. Запустите `Электронная Фармация.exe`.
3. При первом запуске выполните авторизацию (меню АВТОРИЗАЦИЯ).
4. Укажите Location ID точки в настройках авторизации.
База данных: `efClient.db` (рядом с exe).
База данных: `Data\efClient.db` (или `efClient.db` рядом с exe).

View File

@ -210,17 +210,27 @@ namespace Электронная_Фармация
HF_DownloadDataFromServer hF_DownloadDataFromServer = new HF_DownloadDataFromServer();
UiThemeHelper.ApplyToControlTree(hF_DownloadDataFromServer);
DialogResult downloadResult = DialogResult.Cancel;
if (countDataTempOrder > 0)
{
DialogResult drShowDataDownload = UiDialogs.ConfirmYesNoCancel("В корзине найдены товары. При обновлении прайса она будет очищена.\nПродолжить?", "ТОВАРЫ В КОРЗИНЕ", this);
if(drShowDataDownload == DialogResult.Yes)
{
hF_DownloadDataFromServer.ShowDialog();
downloadResult = hF_DownloadDataFromServer.ShowDialog();
}
}
else
{
hF_DownloadDataFromServer.ShowDialog();
downloadResult = hF_DownloadDataFromServer.ShowDialog();
}
if (downloadResult == DialogResult.OK)
{
RefreshOpenPriceLists();
}
else if (!HasDocumentTabs())
{
loadDefaultPriceList();
}
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@ -35,6 +36,7 @@ namespace Электронная_Фармация.UserControls
decimal _percentageAsDefault;
string _selectedSupplierFilter = string.Empty;
bool _suppressSupplierFilterEvents;
bool _suppressPriceListSelectionEvents;
int _committedSupplierFilterIndex;
string connectionStringToLocalDB = AppConfig.SqliteConnectionString;
@ -275,41 +277,53 @@ namespace Электронная_Фармация.UserControls
tablePrice.Columns[10].ColumnName = "Сумма";
tablePrice.Columns[12].ColumnName = "ИДПрайсЛистАйтем";
bsPriceList.DataSource = tablePrice;
dgvPriceList.DataSource = bsPriceList;
dgvPriceList.AutoGenerateColumns = true;
dgvPriceList.Font = new Font("Segoe UI", 13, FontStyle.Regular);
try
{
_suppressPriceListSelectionEvents = true;
bsPriceList.DataSource = tablePrice;
dgvPriceList.DataSource = bsPriceList;
dgvPriceList.AutoGenerateColumns = true;
dgvPriceList.Font = new Font("Segoe UI", 13, FontStyle.Regular);
dgvPriceList.Columns[0].Visible = false;
dgvPriceList.Columns[1].Visible = false;
dgvPriceList.Columns[2].Visible = false;
dgvPriceList.Columns[7].Visible = true;
dgvPriceList.Columns[8].Visible = false;
dgvPriceList.Columns[11].Visible = false;
dgvPriceList.Columns[12].Visible = false;
dgvPriceList.Columns[0].Visible = false;
dgvPriceList.Columns[1].Visible = false;
dgvPriceList.Columns[2].Visible = false;
dgvPriceList.Columns[7].Visible = true;
dgvPriceList.Columns[8].Visible = false;
dgvPriceList.Columns[11].Visible = false;
dgvPriceList.Columns[12].Visible = false;
dgvPriceList.Columns[3].Width = 760;
dgvPriceList.Columns[4].Width = 150;
dgvPriceList.Columns[5].Width = 120;
dgvPriceList.Columns[6].Width = 100;
dgvPriceList.Columns[7].Width = 140;
dgvPriceList.Columns[9].Width = 100;
dgvPriceList.Columns[10].Width = 100;
dgvPriceList.Columns[3].Width = 760;
dgvPriceList.Columns[4].Width = 150;
dgvPriceList.Columns[5].Width = 120;
dgvPriceList.Columns[6].Width = 100;
dgvPriceList.Columns[7].Width = 140;
dgvPriceList.Columns[9].Width = 100;
dgvPriceList.Columns[10].Width = 100;
if (dgvPriceList.Rows.Count > 0)
{
dgvPriceList.ClearSelection();
}
}
finally
{
_suppressPriceListSelectionEvents = false;
ShowMePriceWithMarkupPercentForSelectedGood();
}
}
void loadInfoAboutDefaultMarkup()
{
decimal percentageMarkupDefault = Convert.ToDecimal(Properties.Settings.Default.IntDefaultPercentMarkup);
if (percentageMarkupDefault == 0)
var percentageMarkupDefault = Properties.Settings.Default.IntDefaultPercentMarkup;
if (percentageMarkupDefault <= 0)
{
percentageMarkupDefault = 30;
}
_percentageAsDefault = percentageMarkupDefault;
numericPercent.Value = Convert.ToDecimal(Properties.Settings.Default.IntDefaultPercentMarkup);
numericPercent.Value = Math.Max(numericPercent.Minimum, Math.Min(numericPercent.Maximum, percentageMarkupDefault));
}
public void loadSupplierTabs(string SupplierName)
@ -865,30 +879,42 @@ and tempOrder.GoodName = PriceList.GoodName
(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))
{
int QuantityForSale = Convert.ToInt32(dgvPriceList.Rows[RowIndex].Cells[6].Value.ToString());
if (!TryReadInt32(dgvPriceList.Rows[RowIndex].Cells[6].Value, out var quantityForSale))
{
return;
}
stringIWantBuy = dgvPriceList.Rows[RowIndex].Cells[9].Value.ToString() + e.KeyChar.ToString();
int IWantBuy = Convert.ToInt32(stringIWantBuy);
if (IWantBuy <= QuantityForSale)
if (!TryReadInt32(stringIWantBuy, out var iWantBuy))
{
dgvPriceList.Rows[RowIndex].Cells[9].Value = IWantBuy.ToString();
decimal PriceForOne = Convert.ToDecimal(dgvPriceList.Rows[RowIndex].Cells[5].Value.ToString());
decimal sumOrder = IWantBuy * PriceForOne;
dgvPriceList.Rows[RowIndex].Cells[10].Value = sumOrder.ToString();
return;
}
else if (IWantBuy > QuantityForSale)
if (iWantBuy <= quantityForSale)
{
DialogResult drIWantBuyAll = UiDialogs.ConfirmYesNoCancel($"Для заказа доступно только {QuantityForSale.ToString()}. Заказать доступное количество?", "Заказ", FindForm());
dgvPriceList.Rows[RowIndex].Cells[9].Value = iWantBuy.ToString();
if (!TryReadDecimal(dgvPriceList.Rows[RowIndex].Cells[5].Value, out var priceForOne))
{
return;
}
dgvPriceList.Rows[RowIndex].Cells[10].Value = (iWantBuy * priceForOne).ToString();
}
else if (iWantBuy > quantityForSale)
{
DialogResult drIWantBuyAll = UiDialogs.ConfirmYesNoCancel($"Для заказа доступно только {quantityForSale.ToString()}. Заказать доступное количество?", "Заказ", FindForm());
if (drIWantBuyAll == DialogResult.Yes)
{
dgvPriceList.Rows[RowIndex].Cells[9].Value = QuantityForSale.ToString();
dgvPriceList.Rows[RowIndex].Cells[9].Value = quantityForSale.ToString();
decimal PriceForOne = Convert.ToDecimal(dgvPriceList.Rows[RowIndex].Cells[5].Value.ToString());
decimal sumOrder = QuantityForSale * PriceForOne;
dgvPriceList.Rows[RowIndex].Cells[10].Value = sumOrder.ToString();
if (!TryReadDecimal(dgvPriceList.Rows[RowIndex].Cells[5].Value, out var priceForOne))
{
return;
}
dgvPriceList.Rows[RowIndex].Cells[10].Value = (quantityForSale * priceForOne).ToString();
}
}
}
@ -908,9 +934,13 @@ and tempOrder.GoodName = PriceList.GoodName
dgvPriceList.Rows[RowIndex].Cells[9].Value = result;
decimal PriceForOne = Convert.ToDecimal(dgvPriceList.Rows[RowIndex].Cells[5].Value.ToString());
decimal sumOrder = Convert.ToDecimal(result) * PriceForOne;
dgvPriceList.Rows[RowIndex].Cells[10].Value = sumOrder.ToString();
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)
@ -928,7 +958,7 @@ and tempOrder.GoodName = PriceList.GoodName
foreach (DataGridViewRow row in dgvPriceList.Rows)
{
if (Convert.ToInt32(row.Cells[9].Value) == 0)
if (TryReadInt32(row.Cells[9].Value, out var orderedCount) && orderedCount == 0)
{
int rowIndex = row.Index;
deleteGood(rowIndex, idPriceListItem);
@ -1197,30 +1227,229 @@ and SumOrderedItems = '{SumOrder}'";
void ShowMePriceWithMarkupPercentForSelectedGood()
{
if(dgvPriceList.RowCount > 0)
try
{
lblSelectedGoodName.Text = dgvPriceList.CurrentRow.Cells[3].Value.ToString();
var currentRow = dgvPriceList.CurrentRow;
if (currentRow == null || currentRow.IsNewRow)
{
lblSelectedGoodName.Text = string.Empty;
lblPriceForGood.Text = string.Empty;
return;
}
var nameCell = currentRow.Cells[3].Value;
lblSelectedGoodName.Text = nameCell == null || nameCell == DBNull.Value
? string.Empty
: nameCell.ToString();
double priceForGood = Convert.ToDouble(dgvPriceList.CurrentRow.Cells[5].Value.ToString());
double MarkupPercentage = Convert.ToDouble(numericPercent.Value);
if (!TryReadDouble(currentRow.Cells[5].Value, out var priceForGood))
{
lblPriceForGood.Text = string.Empty;
return;
}
double priceWithPercentage = priceForGood + (priceForGood / 100 * MarkupPercentage);
lblPriceForGood.Text = $"= {priceWithPercentage.ToString()} руб";
var markupPercentage = (double)numericPercent.Value;
var priceWithPercentage = priceForGood + (priceForGood / 100 * markupPercentage);
lblPriceForGood.Text = $"= {priceWithPercentage} руб";
}
catch
{
lblSelectedGoodName.Text = string.Empty;
lblPriceForGood.Text = string.Empty;
}
}
private static bool TryReadDouble(object value, out double result)
{
return TryReadNumber(value, out result);
}
private static bool TryReadDecimal(object value, out decimal result)
{
return TryReadNumber(value, out result);
}
private static bool TryReadInt32(object value, out int result)
{
return TryReadNumber(value, out result);
}
private static bool TryReadNumber<T>(object value, out T result) where T : struct
{
result = default;
if (value == null || value == DBNull.Value)
{
return false;
}
if (value is T typedValue)
{
result = typedValue;
return true;
}
if (value is decimal decimalValue && typeof(T) == typeof(double))
{
result = (T)(object)(double)decimalValue;
return true;
}
if (value is double doubleValue && typeof(T) == typeof(decimal))
{
result = (T)(object)(decimal)doubleValue;
return true;
}
if (value is float floatValue && typeof(T) == typeof(double))
{
result = (T)(object)(double)floatValue;
return true;
}
if (value is float floatValueDecimal && typeof(T) == typeof(decimal))
{
result = (T)(object)(decimal)floatValueDecimal;
return true;
}
var text = NormalizeNumberText(value.ToString());
if (string.IsNullOrEmpty(text))
{
return false;
}
if (typeof(T) == typeof(double))
{
if (double.TryParse(text, NumberStyles.Number, CultureInfo.InvariantCulture, out var invariantDouble))
{
result = (T)(object)invariantDouble;
return true;
}
if (double.TryParse(text, NumberStyles.Number, CultureInfo.CurrentCulture, out var currentDouble))
{
result = (T)(object)currentDouble;
return true;
}
if (double.TryParse(text.Replace(',', '.'), NumberStyles.Number, CultureInfo.InvariantCulture, out var normalizedDouble))
{
result = (T)(object)normalizedDouble;
return true;
}
return false;
}
if (typeof(T) == typeof(decimal))
{
if (decimal.TryParse(text, NumberStyles.Number, CultureInfo.InvariantCulture, out var invariantDecimal))
{
result = (T)(object)invariantDecimal;
return true;
}
if (decimal.TryParse(text, NumberStyles.Number, CultureInfo.CurrentCulture, out var currentDecimal))
{
result = (T)(object)currentDecimal;
return true;
}
if (decimal.TryParse(text.Replace(',', '.'), NumberStyles.Number, CultureInfo.InvariantCulture, out var normalizedDecimal))
{
result = (T)(object)normalizedDecimal;
return true;
}
return false;
}
if (typeof(T) == typeof(int))
{
if (int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var invariantInt))
{
result = (T)(object)invariantInt;
return true;
}
if (int.TryParse(text, NumberStyles.Integer, CultureInfo.CurrentCulture, out var currentInt))
{
result = (T)(object)currentInt;
return true;
}
}
return false;
}
private static string NormalizeNumberText(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return string.Empty;
}
text = text.Trim().Replace('\u00A0', ' ').Replace(" ", string.Empty).Replace(',', '.');
var builder = new StringBuilder();
var started = false;
var hasDecimalSeparator = false;
foreach (var ch in text)
{
if (char.IsDigit(ch))
{
builder.Append(ch);
started = true;
continue;
}
if (ch == '.' && started && !hasDecimalSeparator)
{
builder.Append(ch);
hasDecimalSeparator = true;
continue;
}
if (ch == '-' && !started)
{
builder.Append(ch);
continue;
}
if (started)
{
break;
}
}
return builder.ToString();
}
private void dgvPriceList_SelectionChanged(object sender, EventArgs e)
{
SumIWantBuy = string.Empty;
if (timerForResetSearch.Enabled == true)
if (_suppressPriceListSelectionEvents)
{
timerForResetSearch.Enabled = false;
timerForResetSearch.Stop();
return;
}
ShowMePriceWithMarkupPercentForSelectedGood();
try
{
SumIWantBuy = string.Empty;
if (timerForResetSearch.Enabled == true)
{
timerForResetSearch.Enabled = false;
timerForResetSearch.Stop();
}
ShowMePriceWithMarkupPercentForSelectedGood();
}
catch
{
lblSelectedGoodName.Text = string.Empty;
lblPriceForGood.Text = string.Empty;
}
}

View File

@ -22,7 +22,9 @@ namespace Elfisa.UI.Controls
AllowUserToResizeRows = false;
AllowUserToResizeColumns = true;
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;
AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None;
ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
RowHeadersWidthSizeMode = DataGridViewRowHeadersWidthSizeMode.DisableResizing;
ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.None;
DoubleBuffered = true;
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true);
@ -30,6 +32,42 @@ namespace Elfisa.UI.Controls
ApplyTheme(ThemeManager.Colors);
}
public new DataGridViewAutoSizeRowsMode AutoSizeRowsMode
{
get => base.AutoSizeRowsMode;
set => base.AutoSizeRowsMode = NormalizeAutoSizeRowsMode(value);
}
public new DataGridViewRowHeadersWidthSizeMode RowHeadersWidthSizeMode
{
get => base.RowHeadersWidthSizeMode;
set => base.RowHeadersWidthSizeMode = NormalizeRowHeadersWidthSizeMode(value);
}
private DataGridViewAutoSizeRowsMode NormalizeAutoSizeRowsMode(DataGridViewAutoSizeRowsMode value)
{
if (!RowHeadersVisible &&
(value == DataGridViewAutoSizeRowsMode.DisplayedHeaders ||
value == DataGridViewAutoSizeRowsMode.AllHeaders))
{
return DataGridViewAutoSizeRowsMode.None;
}
return value;
}
private DataGridViewRowHeadersWidthSizeMode NormalizeRowHeadersWidthSizeMode(
DataGridViewRowHeadersWidthSizeMode value)
{
if (!RowHeadersVisible &&
value == DataGridViewRowHeadersWidthSizeMode.AutoSizeToDisplayedHeaders)
{
return DataGridViewRowHeadersWidthSizeMode.DisableResizing;
}
return value;
}
private void ApplyBorderStyles(ThemeColors theme)
{
GridColor = theme.GridBorder;