diff --git a/src/ElectronicPharmacy/Classes/ConsigneeHelper.cs b/src/ElectronicPharmacy/Classes/ConsigneeHelper.cs index cd8dcda..19e75b2 100644 --- a/src/ElectronicPharmacy/Classes/ConsigneeHelper.cs +++ b/src/ElectronicPharmacy/Classes/ConsigneeHelper.cs @@ -215,6 +215,113 @@ namespace Электронная_Фармация.Classes return result; } + /// + /// Процент наценки, который клиент задал вручную для аптеки. + /// null = ещё не сохраняли, брать fallback. + /// + public static decimal? GetClientMarkupPercent(string consigneeName) + { + if (string.IsNullOrWhiteSpace(consigneeName)) + { + return null; + } + + try + { + using (var connection = new SQLiteConnection(AppConfig.SqliteConnectionString)) + { + connection.Open(); + using (var command = new SQLiteCommand( + @"SELECT ClientMarkupPercent + FROM Consignees + WHERE ConsigneesName = @name + LIMIT 1", + connection)) + { + command.Parameters.AddWithValue("@name", consigneeName.Trim()); + var result = command.ExecuteScalar(); + if (result == null || result == DBNull.Value) + { + return null; + } + + if (result is decimal d) + { + return d; + } + + if (decimal.TryParse( + Convert.ToString(result, System.Globalization.CultureInfo.InvariantCulture), + System.Globalization.NumberStyles.Any, + System.Globalization.CultureInfo.InvariantCulture, + out var parsed)) + { + return parsed; + } + + return null; + } + } + } + catch + { + return null; + } + } + + /// + /// Сохраняет процент наценки клиента для конкретной аптеки. + /// Не сбрасывается при обновлении прайса. + /// + public static void SetClientMarkupPercent(string consigneeName, decimal markupPercent) + { + if (string.IsNullOrWhiteSpace(consigneeName)) + { + return; + } + + using (var connection = new SQLiteConnection(AppConfig.SqliteConnectionString)) + { + connection.Open(); + using (var command = new SQLiteCommand( + @"UPDATE Consignees + SET ClientMarkupPercent = @pct + WHERE ConsigneesName = @name", + connection)) + { + command.Parameters.AddWithValue("@pct", markupPercent); + command.Parameters.AddWithValue("@name", consigneeName.Trim()); + command.ExecuteNonQuery(); + } + } + } + + /// + /// Если у аптеки ещё нет сохранённого процента — записать стартовое значение с сервера. + /// Уже введённые клиентом значения не трогаем. + /// + public static void SeedClientMarkupPercentIfEmpty(decimal? serverMarkupPercent) + { + if (!serverMarkupPercent.HasValue) + { + return; + } + + using (var connection = new SQLiteConnection(AppConfig.SqliteConnectionString)) + { + connection.Open(); + using (var command = new SQLiteCommand( + @"UPDATE Consignees + SET ClientMarkupPercent = @pct + WHERE ClientMarkupPercent IS NULL", + connection)) + { + command.Parameters.AddWithValue("@pct", serverMarkupPercent.Value); + command.ExecuteNonQuery(); + } + } + } + /// /// Синхронизирует поля заказа в `PriceList` (Zakaz/SummaZakaza) с корзиной выбранной аптеки. /// Данные корзины хранятся в TempOrderItems с фильтром по ConsigneeName, а PriceList обновляется diff --git a/src/ElectronicPharmacy/Forms/FCheckDatabaseIntegrary.cs b/src/ElectronicPharmacy/Forms/FCheckDatabaseIntegrary.cs index 6c96beb..2e8d278 100644 --- a/src/ElectronicPharmacy/Forms/FCheckDatabaseIntegrary.cs +++ b/src/ElectronicPharmacy/Forms/FCheckDatabaseIntegrary.cs @@ -593,6 +593,7 @@ FROM [TempOrderItems];"; TryAddColumn(conForCheckTables, "PriceList", "TradeName", "nvarchar(256)"); TryAddColumn(conForCheckTables, "PriceList", "Dosage", "nvarchar(128)"); TryAddColumn(conForCheckTables, "PriceList", "MarkupPercent", "real"); + TryAddColumn(conForCheckTables, "Consignees", "ClientMarkupPercent", "real"); ConsigneeHelper.MigrateGlobalLocationIdIfNeeded(conForCheckTables); } catch (Exception ex) diff --git a/src/ElectronicPharmacy/HelpForms/HFConsignees.Designer.cs b/src/ElectronicPharmacy/HelpForms/HFConsignees.Designer.cs index 3594399..41b4cc4 100644 --- a/src/ElectronicPharmacy/HelpForms/HFConsignees.Designer.cs +++ b/src/ElectronicPharmacy/HelpForms/HFConsignees.Designer.cs @@ -45,14 +45,14 @@ this.dgvConsignees.BackgroundColor = System.Drawing.SystemColors.ButtonFace; this.dgvConsignees.BorderStyle = System.Windows.Forms.BorderStyle.None; this.dgvConsignees.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; - this.dgvConsignees.Location = new System.Drawing.Point(12, 56); + this.dgvConsignees.Location = new System.Drawing.Point(16, 56); this.dgvConsignees.MultiSelect = false; this.dgvConsignees.Name = "dgvConsignees"; this.dgvConsignees.ReadOnly = true; this.dgvConsignees.RowHeadersVisible = false; this.dgvConsignees.RowHeadersWidth = 51; this.dgvConsignees.RowTemplate.Height = 24; - this.dgvConsignees.Size = new System.Drawing.Size(409, 295); + this.dgvConsignees.Size = new System.Drawing.Size(640, 295); this.dgvConsignees.TabIndex = 0; // // label1 @@ -69,7 +69,7 @@ // this.btnConfirmConsignee.FlatStyle = System.Windows.Forms.FlatStyle.Popup; this.btnConfirmConsignee.Font = new System.Drawing.Font("Segoe UI Semibold", 10.8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(204))); - this.btnConfirmConsignee.Location = new System.Drawing.Point(336, 361); + this.btnConfirmConsignee.Location = new System.Drawing.Point(567, 361); this.btnConfirmConsignee.Name = "btnConfirmConsignee"; this.btnConfirmConsignee.Size = new System.Drawing.Size(89, 43); this.btnConfirmConsignee.TabIndex = 2; @@ -81,7 +81,7 @@ // this.btnCloseThis.FlatStyle = System.Windows.Forms.FlatStyle.Popup; this.btnCloseThis.Font = new System.Drawing.Font("Segoe UI Semibold", 10.8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(204))); - this.btnCloseThis.Location = new System.Drawing.Point(174, 361); + this.btnCloseThis.Location = new System.Drawing.Point(420, 361); this.btnCloseThis.Name = "btnCloseThis"; this.btnCloseThis.Size = new System.Drawing.Size(125, 43); this.btnCloseThis.TabIndex = 3; @@ -93,7 +93,7 @@ // this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(434, 412); + this.ClientSize = new System.Drawing.Size(672, 412); this.Controls.Add(this.btnCloseThis); this.Controls.Add(this.btnConfirmConsignee); this.Controls.Add(this.label1); diff --git a/src/ElectronicPharmacy/HelpForms/HFConsignees.cs b/src/ElectronicPharmacy/HelpForms/HFConsignees.cs index 6d719c7..6e146c1 100644 --- a/src/ElectronicPharmacy/HelpForms/HFConsignees.cs +++ b/src/ElectronicPharmacy/HelpForms/HFConsignees.cs @@ -28,7 +28,6 @@ namespace Электронная_Фармация.HelpForms string qShowMeConsigneesList = @" select ConsigneesName as [Грузополучатель], - ifnull(LocationId, '') as [Location ID], ConsigneesAddress as [Адрес] from Consignees order by ConsigneesUseAsDefault desc, ConsigneesName"; @@ -38,6 +37,16 @@ order by ConsigneesUseAsDefault desc, ConsigneesName"; DataTable tableConsignees = new DataTable(); daConsigneesList.Fill(tableConsignees); dgvConsignees.DataSource = tableConsignees; + + if (dgvConsignees.Columns.Contains("Грузополучатель")) + { + dgvConsignees.Columns["Грузополучатель"].FillWeight = 45; + } + + if (dgvConsignees.Columns.Contains("Адрес")) + { + dgvConsignees.Columns["Адрес"].FillWeight = 55; + } } catch (Exception ex) { diff --git a/src/ElectronicPharmacy/HelpForms/HF_DownloadDataFromServer.cs b/src/ElectronicPharmacy/HelpForms/HF_DownloadDataFromServer.cs index 7b33c64..9c5babd 100644 --- a/src/ElectronicPharmacy/HelpForms/HF_DownloadDataFromServer.cs +++ b/src/ElectronicPharmacy/HelpForms/HF_DownloadDataFromServer.cs @@ -368,15 +368,14 @@ namespace Электронная_Фармация.HelpForms private static void ApplyClientMarkupSettings(PriceSummaryResponse response) { + // Серверный % используем только как стартовое значение для аптек, + // у которых клиент ещё ничего не сохранил. Уже введённые проценты не трогаем. if (response?.MarkupPercent == null) { return; } - var markup = response.MarkupPercent.Value; - Settings.Default.ClientMarkupPercent = markup; - Settings.Default.IntDefaultPercentMarkup = (int)Math.Round(markup, MidpointRounding.AwayFromZero); - Settings.Default.Save(); + ConsigneeHelper.SeedClientMarkupPercentIfEmpty(response.MarkupPercent); } private void ReportTransferProgress(HttpTransferProgress transfer) diff --git a/src/ElectronicPharmacy/UserControls/UCPriceList.cs b/src/ElectronicPharmacy/UserControls/UCPriceList.cs index f0d61ae..53ec0e0 100644 --- a/src/ElectronicPharmacy/UserControls/UCPriceList.cs +++ b/src/ElectronicPharmacy/UserControls/UCPriceList.cs @@ -35,6 +35,7 @@ namespace Электронная_Фармация.UserControls DataTable tablePriceList; string _WorkWithConsignee; decimal _percentageAsDefault; + bool _suppressMarkupPersist; string _selectedSupplierFilter = string.Empty; bool _suppressSupplierFilterEvents; bool _suppressPriceListSelectionEvents; @@ -1006,34 +1007,59 @@ namespace Электронная_Фармация.UserControls void loadInfoAboutDefaultMarkup() { - decimal percentageMarkupDefault = Properties.Settings.Default.ClientMarkupPercent; - if (percentageMarkupDefault <= 0) + decimal percentageMarkupDefault = 0m; + var fromConsignee = ConsigneeHelper.GetClientMarkupPercent(_WorkWithConsignee); + if (fromConsignee.HasValue) { - percentageMarkupDefault = Properties.Settings.Default.IntDefaultPercentMarkup; + percentageMarkupDefault = fromConsignee.Value; } - - if (percentageMarkupDefault <= 0) + else { - percentageMarkupDefault = MarkupHelper.FallbackMarkupPercent; + percentageMarkupDefault = Properties.Settings.Default.ClientMarkupPercent; + if (percentageMarkupDefault <= 0) + { + percentageMarkupDefault = Properties.Settings.Default.IntDefaultPercentMarkup; + } + + if (percentageMarkupDefault < 0) + { + percentageMarkupDefault = MarkupHelper.FallbackMarkupPercent; + } + + // Первый запуск для этой аптеки — сразу закрепим текущее значение за аптекой. + if (!string.IsNullOrWhiteSpace(_WorkWithConsignee)) + { + try + { + ConsigneeHelper.SetClientMarkupPercent(_WorkWithConsignee, percentageMarkupDefault); + } + catch + { + // колонка может ещё не быть — при следующем старте миграция добавит + } + } } _percentageAsDefault = percentageMarkupDefault; - numericPercent.DecimalPlaces = 2; - numericPercent.Increment = 0.1m; - numericPercent.Value = Math.Max(numericPercent.Minimum, Math.Min(numericPercent.Maximum, percentageMarkupDefault)); + _suppressMarkupPersist = true; + try + { + numericPercent.DecimalPlaces = 2; + numericPercent.Increment = 0.1m; + numericPercent.Value = Math.Max( + numericPercent.Minimum, + Math.Min(numericPercent.Maximum, percentageMarkupDefault)); + } + finally + { + _suppressMarkupPersist = false; + } } private decimal GetMarkupPercentForRow(DataGridViewRow row) { - if (row?.DataGridView?.Columns.Contains("Наценка %") == true) - { - var cellValue = row.Cells["Наценка %"]?.Value; - if (MarkupHelper.TryParseMarkup(cellValue, out var itemMarkup) && itemMarkup >= 0) - { - return itemMarkup; - } - } - + // Для превью/спиннера всегда используем процент аптеки, который задал клиент. + // Серверные значения в колонке «Наценка %» не перетирают ручной ввод. return _percentageAsDefault; } @@ -2028,7 +2054,18 @@ and SumOrderedItems = '{SumOrder}'"; } var markupPercentage = GetMarkupPercentForRow(currentRow); - numericPercent.Value = Math.Max(numericPercent.Minimum, Math.Min(numericPercent.Maximum, markupPercentage)); + _suppressMarkupPersist = true; + try + { + numericPercent.Value = Math.Max( + numericPercent.Minimum, + Math.Min(numericPercent.Maximum, markupPercentage)); + } + finally + { + _suppressMarkupPersist = false; + } + lblPriceForGood.Text = $"= {priceForGood:0.##} руб"; } catch @@ -2405,6 +2442,11 @@ and SumOrderedItems = '{SumOrder}'"; private void numericPercent_ValueChanged(object sender, EventArgs e) { + if (!_suppressMarkupPersist) + { + PersistClientMarkupPercent(); + } + if (numericPercent.Value != _percentageAsDefault) { checkUsePercentageAsDefault.Checked = false; @@ -2413,6 +2455,30 @@ and SumOrderedItems = '{SumOrder}'"; ShowMePriceWithMarkupPercentForSelectedGood(); } + private void PersistClientMarkupPercent() + { + var value = numericPercent.Value; + _percentageAsDefault = value; + + if (!string.IsNullOrWhiteSpace(_WorkWithConsignee)) + { + try + { + ConsigneeHelper.SetClientMarkupPercent(_WorkWithConsignee, value); + } + catch + { + // ignore transient DB errors; value stays in UI + } + } + + // Глобальный fallback на случай новой аптеки без своего значения. + Properties.Settings.Default.ClientMarkupPercent = value; + Properties.Settings.Default.IntDefaultPercentMarkup = + Convert.ToInt32(Math.Round(value, MidpointRounding.AwayFromZero)); + Properties.Settings.Default.Save(); + } + private void UCPriceList_Paint(object sender, PaintEventArgs e) { if (comboSupplierFilter.DroppedDown) @@ -2435,13 +2501,9 @@ and SumOrderedItems = '{SumOrder}'"; private void checkUsePercentageAsDefault_CheckedChanged(object sender, EventArgs e) { - if (_percentageAsDefault != numericPercent.Value && checkUsePercentageAsDefault.Checked == true) + if (checkUsePercentageAsDefault.Checked) { - var newDefaultPercent = numericPercent.Value; - Properties.Settings.Default.ClientMarkupPercent = newDefaultPercent; - Properties.Settings.Default.IntDefaultPercentMarkup = Convert.ToInt32(Math.Round(newDefaultPercent, MidpointRounding.AwayFromZero)); - Properties.Settings.Default.Save(); - _percentageAsDefault = newDefaultPercent; + PersistClientMarkupPercent(); } }