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. Распакуйте архив в любую папку. 1. Распакуйте архив в любую папку.
2. Запустите `Электронная Фармация.exe`. 2. Запустите `Электронная Фармация.exe`.
3. При первом запуске выполните авторизацию (меню АВТОРИЗАЦИЯ). 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(); HF_DownloadDataFromServer hF_DownloadDataFromServer = new HF_DownloadDataFromServer();
UiThemeHelper.ApplyToControlTree(hF_DownloadDataFromServer); UiThemeHelper.ApplyToControlTree(hF_DownloadDataFromServer);
DialogResult downloadResult = DialogResult.Cancel;
if (countDataTempOrder > 0) if (countDataTempOrder > 0)
{ {
DialogResult drShowDataDownload = UiDialogs.ConfirmYesNoCancel("В корзине найдены товары. При обновлении прайса она будет очищена.\nПродолжить?", "ТОВАРЫ В КОРЗИНЕ", this); DialogResult drShowDataDownload = UiDialogs.ConfirmYesNoCancel("В корзине найдены товары. При обновлении прайса она будет очищена.\nПродолжить?", "ТОВАРЫ В КОРЗИНЕ", this);
if(drShowDataDownload == DialogResult.Yes) if(drShowDataDownload == DialogResult.Yes)
{ {
hF_DownloadDataFromServer.ShowDialog(); downloadResult = hF_DownloadDataFromServer.ShowDialog();
} }
} }
else 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.ComponentModel;
using System.Data; using System.Data;
using System.Drawing; using System.Drawing;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -35,6 +36,7 @@ namespace Электронная_Фармация.UserControls
decimal _percentageAsDefault; decimal _percentageAsDefault;
string _selectedSupplierFilter = string.Empty; string _selectedSupplierFilter = string.Empty;
bool _suppressSupplierFilterEvents; bool _suppressSupplierFilterEvents;
bool _suppressPriceListSelectionEvents;
int _committedSupplierFilterIndex; int _committedSupplierFilterIndex;
string connectionStringToLocalDB = AppConfig.SqliteConnectionString; string connectionStringToLocalDB = AppConfig.SqliteConnectionString;
@ -275,6 +277,9 @@ namespace Электронная_Фармация.UserControls
tablePrice.Columns[10].ColumnName = "Сумма"; tablePrice.Columns[10].ColumnName = "Сумма";
tablePrice.Columns[12].ColumnName = "ИДПрайсЛистАйтем"; tablePrice.Columns[12].ColumnName = "ИДПрайсЛистАйтем";
try
{
_suppressPriceListSelectionEvents = true;
bsPriceList.DataSource = tablePrice; bsPriceList.DataSource = tablePrice;
dgvPriceList.DataSource = bsPriceList; dgvPriceList.DataSource = bsPriceList;
dgvPriceList.AutoGenerateColumns = true; dgvPriceList.AutoGenerateColumns = true;
@ -295,21 +300,30 @@ namespace Электронная_Фармация.UserControls
dgvPriceList.Columns[7].Width = 140; dgvPriceList.Columns[7].Width = 140;
dgvPriceList.Columns[9].Width = 100; dgvPriceList.Columns[9].Width = 100;
dgvPriceList.Columns[10].Width = 100; dgvPriceList.Columns[10].Width = 100;
if (dgvPriceList.Rows.Count > 0)
{
dgvPriceList.ClearSelection();
}
}
finally
{
_suppressPriceListSelectionEvents = false;
ShowMePriceWithMarkupPercentForSelectedGood();
}
} }
void loadInfoAboutDefaultMarkup() void loadInfoAboutDefaultMarkup()
{ {
decimal percentageMarkupDefault = Convert.ToDecimal(Properties.Settings.Default.IntDefaultPercentMarkup); var percentageMarkupDefault = Properties.Settings.Default.IntDefaultPercentMarkup;
if (percentageMarkupDefault <= 0)
if (percentageMarkupDefault == 0)
{ {
percentageMarkupDefault = 30; percentageMarkupDefault = 30;
} }
_percentageAsDefault = percentageMarkupDefault; _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) 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)) (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(); stringIWantBuy = dgvPriceList.Rows[RowIndex].Cells[9].Value.ToString() + e.KeyChar.ToString();
int IWantBuy = Convert.ToInt32(stringIWantBuy); if (!TryReadInt32(stringIWantBuy, out var iWantBuy))
if (IWantBuy <= QuantityForSale)
{ {
dgvPriceList.Rows[RowIndex].Cells[9].Value = IWantBuy.ToString(); return;
decimal PriceForOne = Convert.ToDecimal(dgvPriceList.Rows[RowIndex].Cells[5].Value.ToString());
decimal sumOrder = IWantBuy * PriceForOne;
dgvPriceList.Rows[RowIndex].Cells[10].Value = sumOrder.ToString();
} }
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) 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()); if (!TryReadDecimal(dgvPriceList.Rows[RowIndex].Cells[5].Value, out var priceForOne))
decimal sumOrder = QuantityForSale * PriceForOne; {
dgvPriceList.Rows[RowIndex].Cells[10].Value = sumOrder.ToString(); 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; dgvPriceList.Rows[RowIndex].Cells[9].Value = result;
decimal PriceForOne = Convert.ToDecimal(dgvPriceList.Rows[RowIndex].Cells[5].Value.ToString()); if (!TryReadDecimal(dgvPriceList.Rows[RowIndex].Cells[5].Value, out var priceForOne) ||
decimal sumOrder = Convert.ToDecimal(result) * PriceForOne; !TryReadInt32(result, out var count))
dgvPriceList.Rows[RowIndex].Cells[10].Value = sumOrder.ToString(); {
return;
}
dgvPriceList.Rows[RowIndex].Cells[10].Value = (count * priceForOne).ToString();
} }
} }
else if (e.KeyChar == (Char)Keys.Delete) else if (e.KeyChar == (Char)Keys.Delete)
@ -928,7 +958,7 @@ and tempOrder.GoodName = PriceList.GoodName
foreach (DataGridViewRow row in dgvPriceList.Rows) 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; int rowIndex = row.Index;
deleteGood(rowIndex, idPriceListItem); deleteGood(rowIndex, idPriceListItem);
@ -1197,20 +1227,213 @@ and SumOrderedItems = '{SumOrder}'";
void ShowMePriceWithMarkupPercentForSelectedGood() void ShowMePriceWithMarkupPercentForSelectedGood()
{ {
if(dgvPriceList.RowCount > 0) try
{ {
lblSelectedGoodName.Text = dgvPriceList.CurrentRow.Cells[3].Value.ToString(); var currentRow = dgvPriceList.CurrentRow;
if (currentRow == null || currentRow.IsNewRow)
{
double priceForGood = Convert.ToDouble(dgvPriceList.CurrentRow.Cells[5].Value.ToString()); lblSelectedGoodName.Text = string.Empty;
double MarkupPercentage = Convert.ToDouble(numericPercent.Value); lblPriceForGood.Text = string.Empty;
return;
double priceWithPercentage = priceForGood + (priceForGood / 100 * MarkupPercentage);
lblPriceForGood.Text = $"= {priceWithPercentage.ToString()} руб";
} }
var nameCell = currentRow.Cells[3].Value;
lblSelectedGoodName.Text = nameCell == null || nameCell == DBNull.Value
? string.Empty
: nameCell.ToString();
if (!TryReadDouble(currentRow.Cells[5].Value, out var priceForGood))
{
lblPriceForGood.Text = string.Empty;
return;
}
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) private void dgvPriceList_SelectionChanged(object sender, EventArgs e)
{
if (_suppressPriceListSelectionEvents)
{
return;
}
try
{ {
SumIWantBuy = string.Empty; SumIWantBuy = string.Empty;
@ -1222,6 +1445,12 @@ and SumOrderedItems = '{SumOrder}'";
ShowMePriceWithMarkupPercentForSelectedGood(); ShowMePriceWithMarkupPercentForSelectedGood();
} }
catch
{
lblSelectedGoodName.Text = string.Empty;
lblPriceForGood.Text = string.Empty;
}
}

View File

@ -22,7 +22,9 @@ namespace Elfisa.UI.Controls
AllowUserToResizeRows = false; AllowUserToResizeRows = false;
AllowUserToResizeColumns = true; AllowUserToResizeColumns = true;
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None; AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;
AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None;
ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing; ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
RowHeadersWidthSizeMode = DataGridViewRowHeadersWidthSizeMode.DisableResizing;
ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.None; ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.None;
DoubleBuffered = true; DoubleBuffered = true;
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true); SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true);
@ -30,6 +32,42 @@ namespace Elfisa.UI.Controls
ApplyTheme(ThemeManager.Colors); 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) private void ApplyBorderStyles(ThemeColors theme)
{ {
GridColor = theme.GridBorder; GridColor = theme.GridBorder;