Add multi-pharmacy LocationId, batched price download, and SQL debug tools.

Persist last successful login only, show active pharmacy in the status bar, and keep settings compact at the bottom-right.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Magomed 2026-07-29 10:09:04 +03:00
parent babc067863
commit 458b70e036
15 changed files with 1571 additions and 290 deletions

View File

@ -0,0 +1,218 @@
using System;
using System.Collections.Generic;
using System.Data.SQLite;
namespace Электроннаяармация.Classes
{
/// <summary>
/// Аптека = грузополучатель + LocationId. Прайс общий, заказы уходят с location_id выбранной аптеки.
/// </summary>
public static class ConsigneeHelper
{
public static string GetDefaultConsigneeName()
{
try
{
using (var connection = new SQLiteConnection(AppConfig.SqliteConnectionString))
{
connection.Open();
using (var command = new SQLiteCommand(
@"SELECT ConsigneesName
FROM Consignees
ORDER BY ConsigneesUseAsDefault DESC, ConsigneesName
LIMIT 1",
connection))
{
var result = command.ExecuteScalar();
return result == null || result == DBNull.Value
? string.Empty
: result.ToString().Trim();
}
}
}
catch
{
return string.Empty;
}
}
public static string GetLocationIdByName(string consigneeName)
{
if (string.IsNullOrWhiteSpace(consigneeName))
{
return string.Empty;
}
using (var connection = new SQLiteConnection(AppConfig.SqliteConnectionString))
{
connection.Open();
using (var command = new SQLiteCommand(
@"SELECT ifnull(LocationId, '')
FROM Consignees
WHERE ConsigneesName = @name
LIMIT 1",
connection))
{
command.Parameters.AddWithValue("@name", consigneeName.Trim());
var result = command.ExecuteScalar();
return result == null || result == DBNull.Value
? string.Empty
: result.ToString().Trim();
}
}
}
public static string ResolveLocationId(string consigneeName)
{
var fromConsignee = GetLocationIdByName(consigneeName);
if (!string.IsNullOrWhiteSpace(fromConsignee))
{
return fromConsignee;
}
return AppConfig.LocationId ?? string.Empty;
}
/// <summary>
/// Помечает аптеку активной и синхронизирует AppConfig.LocationId.
/// </summary>
public static void SetActiveConsignee(string consigneeName)
{
if (string.IsNullOrWhiteSpace(consigneeName))
{
return;
}
var name = consigneeName.Trim();
using (var connection = new SQLiteConnection(AppConfig.SqliteConnectionString))
{
connection.Open();
using (var tx = connection.BeginTransaction())
{
using (var clear = new SQLiteCommand(
"UPDATE Consignees SET ConsigneesUseAsDefault = 0",
connection,
tx))
{
clear.ExecuteNonQuery();
}
using (var setDefault = new SQLiteCommand(
@"UPDATE Consignees
SET ConsigneesUseAsDefault = 1
WHERE ConsigneesName = @name",
connection,
tx))
{
setDefault.Parameters.AddWithValue("@name", name);
setDefault.ExecuteNonQuery();
}
tx.Commit();
}
var locationId = GetLocationIdByName(name);
if (!string.IsNullOrWhiteSpace(locationId))
{
AppConfig.SetLocationId(locationId);
}
}
}
public static void SetLocationId(string consigneeName, string locationId)
{
if (string.IsNullOrWhiteSpace(consigneeName))
{
return;
}
using (var connection = new SQLiteConnection(AppConfig.SqliteConnectionString))
{
connection.Open();
using (var command = new SQLiteCommand(
@"UPDATE Consignees
SET LocationId = @loc
WHERE ConsigneesName = @name",
connection))
{
command.Parameters.AddWithValue("@loc", (locationId ?? string.Empty).Trim());
command.Parameters.AddWithValue("@name", consigneeName.Trim());
command.ExecuteNonQuery();
}
}
}
public static void MigrateGlobalLocationIdIfNeeded(SQLiteConnection connection)
{
var global = AppConfig.LocationId;
if (string.IsNullOrWhiteSpace(global))
{
return;
}
using (var command = new SQLiteCommand(
@"UPDATE Consignees
SET LocationId = @loc
WHERE (LocationId IS NULL OR trim(LocationId) = '')
AND (
ConsigneesUseAsDefault = 1
OR (SELECT COUNT(*) FROM Consignees) = 1
)",
connection))
{
command.Parameters.AddWithValue("@loc", global.Trim());
command.ExecuteNonQuery();
}
}
public static void ApplyLocationIdToEmptyConsignees(string locationId)
{
if (string.IsNullOrWhiteSpace(locationId))
{
return;
}
using (var connection = new SQLiteConnection(AppConfig.SqliteConnectionString))
{
connection.Open();
using (var command = new SQLiteCommand(
@"UPDATE Consignees
SET LocationId = @loc
WHERE LocationId IS NULL OR trim(LocationId) = ''",
connection))
{
command.Parameters.AddWithValue("@loc", locationId.Trim());
command.ExecuteNonQuery();
}
}
}
public static IList<string> GetKnownSupplierIds()
{
var result = new List<string>();
using (var connection = new SQLiteConnection(AppConfig.SqliteConnectionString))
{
connection.Open();
using (var command = new SQLiteCommand(
@"SELECT DISTINCT trim(supplier_id)
FROM PriceList
WHERE supplier_id IS NOT NULL AND trim(supplier_id) <> ''
ORDER BY 1",
connection))
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
var id = reader.GetString(0);
if (!string.IsNullOrWhiteSpace(id))
{
result.Add(id);
}
}
}
}
return result;
}
}
}

View File

@ -35,20 +35,6 @@ namespace Электронная_Фармация.Classes
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. Заполните его после авторизации.",
Color.DarkOrange,
Color.White);
return;
}
}
List<BuyerOrderRequest> orders;
try
{
@ -66,6 +52,11 @@ namespace Электронная_Фармация.Classes
return;
}
if (!EnsureOrdersHaveLocationId(orders))
{
return;
}
int successCount = 0;
int failCount = 0;
@ -110,20 +101,6 @@ namespace Электронная_Фармация.Classes
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. Заполните его после авторизации.",
Color.DarkOrange,
Color.White);
return false;
}
}
List<BuyerOrderRequest> orders;
try
{
@ -141,6 +118,11 @@ namespace Электронная_Фармация.Classes
return false;
}
if (!EnsureOrdersHaveLocationId(orders))
{
return false;
}
bool ok = await SendOrderAsync(orders[0]);
if (ok)
{
@ -153,7 +135,7 @@ namespace Электронная_Фармация.Classes
public List<BuyerOrderRequest> FetchDataFromSql(string onlyOrderId)
{
var orders = new Dictionary<string, BuyerOrderRequest>();
string locationId = AppConfig.LocationId;
var locationCache = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
using (var connection = new SQLiteConnection(AppConfig.SqliteConnectionString))
{
@ -161,6 +143,7 @@ namespace Электронная_Фармация.Classes
string query = @"
SELECT
Orders.Id_Order,
Orders.ConsigneeName,
OrderItems.supplier_price_id,
OrderItems.DrugName,
OrderItems.es_code,
@ -189,6 +172,9 @@ WHERE Orders.OrderState = 'НОВЫЙ'
while (reader.Read())
{
string orderId = reader["Id_Order"].ToString().Trim();
string consigneeName = reader["ConsigneeName"] == DBNull.Value
? string.Empty
: reader["ConsigneeName"].ToString().Trim();
string supplierPriceId = reader["supplier_price_id"].ToString().Trim();
string drugName = reader["DrugName"] == DBNull.Value ? null : reader["DrugName"].ToString().Trim();
string esCode = reader["es_code"] == DBNull.Value ? null : reader["es_code"].ToString().Trim();
@ -206,6 +192,7 @@ WHERE Orders.OrderState = 'НОВЫЙ'
if (!orders.TryGetValue(orderId, out BuyerOrderRequest order))
{
string locationId = ResolveLocationIdCached(consigneeName, locationCache);
order = new BuyerOrderRequest
{
LocalOrderId = orderId,
@ -230,6 +217,65 @@ WHERE Orders.OrderState = 'НОВЫЙ'
return orders.Values.ToList();
}
private static string ResolveLocationIdCached(string consigneeName, Dictionary<string, string> cache)
{
var key = consigneeName ?? string.Empty;
if (cache.TryGetValue(key, out var cached))
{
return cached;
}
var locationId = ConsigneeHelper.ResolveLocationId(consigneeName);
cache[key] = locationId ?? string.Empty;
return cache[key];
}
private bool EnsureOrdersHaveLocationId(List<BuyerOrderRequest> orders)
{
var missing = orders
.Where(o => string.IsNullOrWhiteSpace(o.LocationId))
.Select(o => o.LocalOrderId)
.Distinct()
.ToList();
if (missing.Count == 0)
{
return true;
}
if (string.IsNullOrWhiteSpace(AppConfig.LocationId))
{
if (!HF_LocationId.PromptAndSave(Form.ActiveForm)
|| string.IsNullOrWhiteSpace(AppConfig.LocationId))
{
AppDebugLog.Error("DataSender", "Отправка заказов: location_id не указан");
ToastNotification.ShowCustom(
"Не указан Location ID у аптеки заказа. Задайте его в «Грузополучатели» или в настройках.",
Color.DarkOrange,
Color.White);
return false;
}
ConsigneeHelper.ApplyLocationIdToEmptyConsignees(AppConfig.LocationId);
}
foreach (var order in orders.Where(o => string.IsNullOrWhiteSpace(o.LocationId)))
{
order.LocationId = AppConfig.LocationId;
}
if (orders.Any(o => string.IsNullOrWhiteSpace(o.LocationId)))
{
ToastNotification.ShowCustom(
"У части заказов нет Location ID. Укажите ID у грузополучателя.",
Color.DarkOrange,
Color.White);
return false;
}
return true;
}
private async Task<bool> SendOrderAsync(BuyerOrderRequest order)
{
var options = new JsonSerializerOptions

View File

@ -0,0 +1,153 @@
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Windows.Forms;
namespace Электроннаяармация.Classes
{
/// <summary>
/// Лёгкая подсветка SQL в RichTextBox (ключевые слова, строки, комментарии, числа, параметры).
/// </summary>
public static class SqlSyntaxHighlighter
{
private static readonly Regex TokenRegex = new Regex(
@"(--[^\r\n]*)|(/\*[\s\S]*?\*/)|('(?:''|[^'])*')|(""(?:""""|[^""])*"")|(\[[^\]]*\])|(@[A-Za-z_][A-Za-z0-9_]*)|(\{\{[A-Za-z_][A-Za-z0-9_]*\}\})|(\b\d+(?:\.\d+)?\b)|(\b[A-Za-z_][A-Za-z0-9_]*\b)",
RegexOptions.Compiled);
private static readonly System.Collections.Generic.HashSet<string> Keywords =
new System.Collections.Generic.HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"SELECT", "FROM", "WHERE", "AND", "OR", "NOT", "IN", "IS", "NULL", "AS",
"JOIN", "INNER", "LEFT", "RIGHT", "OUTER", "CROSS", "ON", "USING",
"GROUP", "BY", "ORDER", "HAVING", "LIMIT", "OFFSET", "DISTINCT", "ALL",
"INSERT", "INTO", "VALUES", "UPDATE", "SET", "DELETE", "REPLACE",
"CREATE", "ALTER", "DROP", "TABLE", "INDEX", "VIEW", "TRIGGER", "IF", "EXISTS",
"PRIMARY", "KEY", "FOREIGN", "REFERENCES", "UNIQUE", "DEFAULT", "CHECK",
"BEGIN", "COMMIT", "ROLLBACK", "TRANSACTION", "PRAGMA", "EXPLAIN", "WITH",
"CASE", "WHEN", "THEN", "ELSE", "END", "BETWEEN", "LIKE", "GLOB", "ESCAPE",
"UNION", "INTERSECT", "EXCEPT", "CAST", "COLLATE", "ASC", "DESC",
"COUNT", "SUM", "AVG", "MIN", "MAX", "IFNULL", "COALESCE", "NULLIF",
"LENGTH", "TRIM", "UPPER", "LOWER", "SUBSTR", "REPLACE", "ROUND", "ABS",
"DATETIME", "DATE", "TIME", "STRFTIME", "ADD", "COLUMN", "RENAME", "TO"
};
private const int WM_SETREDRAW = 0x000B;
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
public static void Apply(RichTextBox box, bool darkTheme)
{
if (box == null || box.IsDisposed)
{
return;
}
string text = box.Text ?? string.Empty;
int selStart = box.SelectionStart;
int selLength = box.SelectionLength;
int firstVisibleChar = box.GetCharIndexFromPosition(new Point(1, 1));
Color defaultFg = darkTheme ? Color.FromArgb(220, 220, 220) : Color.FromArgb(30, 30, 30);
Color keyword = darkTheme ? Color.FromArgb(86, 156, 214) : Color.FromArgb(0, 0, 180);
Color str = darkTheme ? Color.FromArgb(206, 145, 120) : Color.FromArgb(163, 21, 21);
Color comment = darkTheme ? Color.FromArgb(106, 153, 85) : Color.FromArgb(0, 128, 0);
Color number = darkTheme ? Color.FromArgb(181, 206, 168) : Color.FromArgb(9, 134, 88);
Color param = darkTheme ? Color.FromArgb(197, 134, 192) : Color.FromArgb(128, 0, 128);
Color ident = darkTheme ? Color.FromArgb(156, 220, 254) : Color.FromArgb(0, 100, 120);
SuspendRedraw(box);
try
{
box.SelectAll();
box.SelectionColor = defaultFg;
box.SelectionFont = box.Font;
foreach (Match match in TokenRegex.Matches(text))
{
Color color = defaultFg;
bool bold = false;
if (match.Groups[1].Success || match.Groups[2].Success)
{
color = comment;
}
else if (match.Groups[3].Success || match.Groups[4].Success || match.Groups[5].Success)
{
color = str;
}
else if (match.Groups[6].Success || match.Groups[7].Success)
{
color = param;
bold = true;
}
else if (match.Groups[8].Success)
{
color = number;
}
else if (match.Groups[9].Success)
{
var word = match.Groups[9].Value;
if (Keywords.Contains(word))
{
color = keyword;
bold = true;
}
else
{
color = ident;
}
}
if (color == defaultFg && !bold)
{
continue;
}
box.Select(match.Index, match.Length);
box.SelectionColor = color;
if (bold)
{
box.SelectionFont = new Font(box.Font, FontStyle.Bold);
}
else
{
box.SelectionFont = box.Font;
}
}
}
finally
{
box.Select(selStart, selLength);
try
{
box.Select(firstVisibleChar, 0);
var pt = box.GetPositionFromCharIndex(firstVisibleChar);
if (pt.Y < 0)
{
// keep best-effort scroll
}
}
catch
{
// ignore scroll restore issues
}
box.Select(selStart, selLength);
ResumeRedraw(box);
box.Invalidate();
}
}
private static void SuspendRedraw(Control control)
{
SendMessage(control.Handle, WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero);
}
private static void ResumeRedraw(Control control)
{
SendMessage(control.Handle, WM_SETREDRAW, new IntPtr(1), IntPtr.Zero);
}
}
}

View File

@ -104,6 +104,8 @@
<Compile Include="Classes\ApiClient.cs" />
<Compile Include="Classes\AppConfig.cs" />
<Compile Include="Classes\AppDebugLog.cs" />
<Compile Include="Classes\ConsigneeHelper.cs" />
<Compile Include="Classes\SqlSyntaxHighlighter.cs" />
<Compile Include="Classes\DataSender.cs" />
<Compile Include="Classes\ErrorResponse.cs" />
<Compile Include="Classes\InvoiceExportService.cs" />

View File

@ -67,9 +67,9 @@ namespace Электронная_Фармация
{
Name = "statusFooter",
Dock = DockStyle.Bottom,
Height = 30,
Height = 28,
Tag = "status-footer",
Padding = new Padding(12, 0, 16, 0)
Padding = new Padding(10, 0, 4, 0)
};
_statusFooter.Paint += (_, e) =>
{
@ -86,19 +86,27 @@ namespace Электронная_Фармация
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleLeft,
AutoEllipsis = true,
Tag = "muted"
Tag = "muted",
Padding = new Padding(0, 0, 4, 0)
};
_btnSettings = new ModernButton
{
Name = "btnSettings",
Dock = DockStyle.Right,
Width = 150,
Margin = new Padding(0),
Text = "Настройки"
Margin = new Padding(0, 2, 2, 2),
Text = "Настройки",
CompactIconMode = false,
AllowTextEllipsis = false,
MinimumSize = new Size(88, 22),
Size = new Size(88, 22),
Padding = new Padding(6, 0, 6, 0),
Font = new Font("Segoe UI Semibold", 8.5f, FontStyle.Bold),
TabIndex = 0
};
_btnSettings.Click += (_, __) => OpenSettingsDialog();
_statusFooter.Controls.Add(_btnSettings);
// Сначала Fill (пользователь), потом Right (кнопка) — кнопка справа, текст не перекрывается.
_statusFooter.Controls.Add(_lblCurrentUser);
_statusFooter.Controls.Add(_btnSettings);
Controls.Add(_loadingOverlay);
Controls.Add(_documentHost);
@ -111,6 +119,8 @@ namespace Электронная_Фармация
contractorsItem.Click += (_, __) => tsItemContractors_Click(_, __);
var consigneesItem = new ToolStripMenuItem("Грузополучатели");
consigneesItem.Click += (_, __) => tsConsignee_Click(_, __);
var switchConsigneeItem = new ToolStripMenuItem("Сменить грузополучателя");
switchConsigneeItem.Click += (_, __) => SwitchConsignee();
var invoicesItem = new ToolStripMenuItem("Накладные");
invoicesItem.Click += (_, __) => tsItemInvoices_Click(_, __);
var debugItem = new ToolStripMenuItem("Отладка");
@ -119,6 +129,7 @@ namespace Электронная_Фармация
{
contractorsItem,
consigneesItem,
switchConsigneeItem,
invoicesItem,
new ToolStripSeparator(),
debugItem
@ -194,18 +205,38 @@ namespace Электронная_Фармация
var login = Settings.Default.stringLogin?.Trim();
var hasToken = !string.IsNullOrWhiteSpace(Settings.Default.stringToken);
var pharmacy = ConsigneeName?.Trim();
if (string.IsNullOrWhiteSpace(pharmacy) || pharmacy == "Прайс-Лист")
{
pharmacy = ConsigneeHelper.GetDefaultConsigneeName();
if (!string.IsNullOrWhiteSpace(pharmacy) &&
(string.IsNullOrWhiteSpace(ConsigneeName) || ConsigneeName == "Прайс-Лист"))
{
ConsigneeName = pharmacy;
}
}
string userPart;
if (!string.IsNullOrWhiteSpace(login) && hasToken)
{
_lblCurrentUser.Text = $"Пользователь: {login}";
userPart = $"Пользователь: {login}";
}
else if (!string.IsNullOrWhiteSpace(login))
{
_lblCurrentUser.Text = $"Пользователь: {login} (не авторизован)";
userPart = $"Пользователь: {login} (не авторизован)";
}
else
{
_lblCurrentUser.Text = "Пользователь не авторизован";
userPart = "Пользователь не авторизован";
}
if (!string.IsNullOrWhiteSpace(pharmacy) && pharmacy != "Прайс-Лист")
{
_lblCurrentUser.Text = $"{userPart} | Аптека: {pharmacy}";
}
else
{
_lblCurrentUser.Text = $"{userPart} | Аптека: не выбрана";
}
}

View File

@ -74,17 +74,45 @@ namespace Электронная_Фармация
UiThemeHelper.ApplyToControlTree(consigneeDialog);
consigneeDialog.ShowDialog();
if (string.IsNullOrWhiteSpace(ConsigneeName))
{
var defaultName = ConsigneeHelper.GetDefaultConsigneeName();
if (!string.IsNullOrWhiteSpace(defaultName))
{
ConsigneeName = defaultName;
ConsigneeHelper.SetActiveConsignee(defaultName);
}
}
if (!HasDocumentTabs())
{
loadDefaultPriceList();
}
RefreshCurrentUserStatus();
}
void loadDefaultPriceList()
{
var ucPriceList = new UCPriceList("Прайс-Лист");
OpenDocumentTab("Прайс-лист", "price", ucPriceList, allowDuplicate: true);
var consignee = !string.IsNullOrWhiteSpace(ConsigneeName)
? ConsigneeName
: ConsigneeHelper.GetDefaultConsigneeName();
if (string.IsNullOrWhiteSpace(consignee))
{
consignee = "Прайс-Лист";
}
else
{
ConsigneeName = consignee;
}
var title = consignee == "Прайс-Лист"
? "Прайс-лист"
: $"Прайс-лист ({consignee})";
var ucPriceList = new UCPriceList(consignee);
OpenDocumentTab(title, "price", ucPriceList, allowDuplicate: true);
ucPriceList.dgvPriceList.Focus();
RefreshCurrentUserStatus();
}
#region загрузкаПрайсЛиста_старая версия
@ -164,9 +192,23 @@ namespace Электронная_Фармация
public void showMePriceList(string consignee)
{
ConsigneeName = consignee ?? string.Empty;
if (!string.IsNullOrWhiteSpace(ConsigneeName))
{
ConsigneeHelper.SetActiveConsignee(ConsigneeName);
}
var ucPriceList = new UCPriceList(consignee);
OpenDocumentTab($"Прайс-лист ({consignee})", "price", ucPriceList, allowDuplicate: true);
ucPriceList.dgvPriceList.Focus();
RefreshCurrentUserStatus();
}
private void SwitchConsignee()
{
var consigneeDialog = new HFConsignees { ParentForm = this };
UiThemeHelper.ApplyToControlTree(consigneeDialog);
consigneeDialog.ShowDialog(this);
RefreshCurrentUserStatus();
}
private void tsDebug_Click(object sender, EventArgs e)

View File

@ -76,7 +76,8 @@ namespace Электронная_Фармация.Forms
[codeConsignees] integer not null,
[ConsigneesName] nvarchar(128) not null,
[ConsigneesAddress] nvarchar(256) not null,
[ConsigneesUseAsDefault] int(1) not null
[ConsigneesUseAsDefault] int(1) not null,
[LocationId] nvarchar(64)
)
";
@ -405,6 +406,8 @@ create table if not exists [OrderItems] (
TryAddColumn(conForCheckTables, "PriceList", "TradeName", "nvarchar(256)");
TryAddColumn(conForCheckTables, "PriceList", "Dosage", "nvarchar(128)");
TryAddColumn(conForCheckTables, "PriceList", "MarkupPercent", "real");
TryAddColumn(conForCheckTables, "Consignees", "LocationId", "nvarchar(64)");
ConsigneeHelper.MigrateGlobalLocationIdIfNeeded(conForCheckTables);
}
catch (Exception ex)
{

View File

@ -2,15 +2,8 @@
{
partial class FConsignees
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
@ -22,13 +15,12 @@
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.panel1 = new System.Windows.Forms.Panel();
this.btnSetDefault = new Elfisa.UI.Controls.ModernButton();
this.btnAdd = new Elfisa.UI.Controls.ModernButton();
this.btnSave = new Elfisa.UI.Controls.ModernButton();
this.txtInteractiveShowMeConsignees = new Elfisa.UI.Controls.ModernTextBox();
this.label2 = new System.Windows.Forms.Label();
this.dgvConsignees = new Elfisa.UI.Controls.ModernDataGridView();
@ -39,31 +31,67 @@
// panel1
//
this.panel1.BackColor = System.Drawing.SystemColors.ButtonHighlight;
this.panel1.Controls.Add(this.btnSetDefault);
this.panel1.Controls.Add(this.btnAdd);
this.panel1.Controls.Add(this.btnSave);
this.panel1.Controls.Add(this.txtInteractiveShowMeConsignees);
this.panel1.Controls.Add(this.label2);
this.panel1.Controls.Add(this.dgvConsignees);
this.panel1.Location = new System.Drawing.Point(13, 14);
this.panel1.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
this.panel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel1.Location = new System.Drawing.Point(0, 0);
this.panel1.Margin = new System.Windows.Forms.Padding(4);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(724, 423);
this.panel1.Padding = new System.Windows.Forms.Padding(12);
this.panel1.Size = new System.Drawing.Size(820, 480);
this.panel1.TabIndex = 4;
//
// btnSetDefault
//
this.btnSetDefault.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btnSetDefault.Location = new System.Drawing.Point(280, 430);
this.btnSetDefault.Name = "btnSetDefault";
this.btnSetDefault.Size = new System.Drawing.Size(180, 34);
this.btnSetDefault.TabIndex = 6;
this.btnSetDefault.Text = "По умолчанию";
this.btnSetDefault.Click += new System.EventHandler(this.btnSetDefault_Click);
//
// btnAdd
//
this.btnAdd.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btnAdd.Location = new System.Drawing.Point(148, 430);
this.btnAdd.Name = "btnAdd";
this.btnAdd.Size = new System.Drawing.Size(120, 34);
this.btnAdd.TabIndex = 5;
this.btnAdd.Text = "Добавить";
this.btnAdd.Click += new System.EventHandler(this.btnAdd_Click);
//
// btnSave
//
this.btnSave.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btnSave.Location = new System.Drawing.Point(16, 430);
this.btnSave.Name = "btnSave";
this.btnSave.Size = new System.Drawing.Size(120, 34);
this.btnSave.TabIndex = 4;
this.btnSave.Text = "Сохранить";
this.btnSave.Click += new System.EventHandler(this.btnSave_Click);
//
// txtInteractiveShowMeConsignees
//
this.txtInteractiveShowMeConsignees.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
this.txtInteractiveShowMeConsignees.Location = new System.Drawing.Point(91, 6);
this.txtInteractiveShowMeConsignees.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
this.txtInteractiveShowMeConsignees.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.txtInteractiveShowMeConsignees.Font = new System.Drawing.Font("Segoe UI", 11.25F);
this.txtInteractiveShowMeConsignees.Location = new System.Drawing.Point(91, 16);
this.txtInteractiveShowMeConsignees.Margin = new System.Windows.Forms.Padding(4);
this.txtInteractiveShowMeConsignees.Name = "txtInteractiveShowMeConsignees";
this.txtInteractiveShowMeConsignees.Size = new System.Drawing.Size(623, 32);
this.txtInteractiveShowMeConsignees.Size = new System.Drawing.Size(700, 32);
this.txtInteractiveShowMeConsignees.TabIndex = 3;
this.txtInteractiveShowMeConsignees.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.txtInteractiveShowMeConsignees_KeyPress);
//
// label2
//
this.label2.AutoSize = true;
this.label2.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
this.label2.Location = new System.Drawing.Point(5, 6);
this.label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label2.Font = new System.Drawing.Font("Segoe UI", 12F);
this.label2.Location = new System.Drawing.Point(16, 18);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(73, 28);
this.label2.TabIndex = 2;
@ -73,45 +101,48 @@
//
this.dgvConsignees.AllowUserToAddRows = false;
this.dgvConsignees.AllowUserToDeleteRows = false;
this.dgvConsignees.AllowUserToResizeColumns = false;
this.dgvConsignees.AllowUserToResizeRows = false;
this.dgvConsignees.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.dgvConsignees.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
this.dgvConsignees.AutoSizeRowsMode = System.Windows.Forms.DataGridViewAutoSizeRowsMode.DisplayedHeaders;
this.dgvConsignees.BackgroundColor = System.Drawing.SystemColors.ButtonFace;
this.dgvConsignees.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dgvConsignees.Location = new System.Drawing.Point(11, 44);
this.dgvConsignees.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
this.dgvConsignees.Location = new System.Drawing.Point(16, 56);
this.dgvConsignees.Name = "dgvConsignees";
this.dgvConsignees.ReadOnly = true;
this.dgvConsignees.ReadOnly = false;
this.dgvConsignees.RowHeadersWidthSizeMode = System.Windows.Forms.DataGridViewRowHeadersWidthSizeMode.AutoSizeToDisplayedHeaders;
this.dgvConsignees.Size = new System.Drawing.Size(704, 369);
this.dgvConsignees.Size = new System.Drawing.Size(775, 360);
this.dgvConsignees.TabIndex = 1;
//
// FConsignees
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(749, 450);
this.ClientSize = new System.Drawing.Size(820, 480);
this.Controls.Add(this.panel1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.MaximizeBox = false;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Sizable;
this.MaximizeBox = true;
this.MinimizeBox = false;
this.MinimumSize = new System.Drawing.Size(700, 420);
this.Name = "FConsignees";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Грузополучатели";
this.Text = "Грузополучатели (аптеки)";
this.Load += new System.EventHandler(this.FConsignees_Load);
this.panel1.ResumeLayout(false);
this.panel1.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.dgvConsignees)).EndInit();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Panel panel1;
private Elfisa.UI.Controls.ModernTextBox txtInteractiveShowMeConsignees;
private System.Windows.Forms.Label label2;
private Elfisa.UI.Controls.ModernDataGridView dgvConsignees;
private Elfisa.UI.Controls.ModernButton btnSave;
private Elfisa.UI.Controls.ModernButton btnAdd;
private Elfisa.UI.Controls.ModernButton btnSetDefault;
}
}

View File

@ -1,14 +1,8 @@
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.Data.SQLite;
using System.Windows.Forms;
using Электроннаяармация.Classes;
using System.Data.SQLite;
namespace Электроннаяармация.Forms
{
@ -29,13 +23,15 @@ namespace Электронная_Фармация.Forms
{
using (SQLiteConnection conConsignees = new SQLiteConnection(AppConfig.SqliteConnectionString))
{
string selectFromConsignees = $@"
select [codeConsignees] as [Код грузополучателя],
string selectFromConsignees = @"
select [idConsignees] as [Id],
[codeConsignees] as [Код],
[ConsigneesName] as [Наименование],
[ConsigneesAddress] as [Адрес],
[ConsigneesUseAsDefault] as [По умолчанию?]
ifnull([LocationId], '') as [Location ID],
[ConsigneesUseAsDefault] as [По умолчанию]
from [Consignees]
order by [ConsigneesUseAsDefault] desc";
order by [ConsigneesUseAsDefault] desc, [ConsigneesName]";
try
{
conConsignees.Open();
@ -44,16 +40,20 @@ order by [ConsigneesUseAsDefault] desc";
SQLiteDataAdapter daConsigneeList = new SQLiteDataAdapter(cmdShowConsigneesList);
daConsigneeList.Fill(tableConsignees);
dgvConsignees.DataSource = tableConsignees;
dgvConsignees.Columns[3].ValueType = typeof(float);
if (dgvConsignees.Columns.Contains("Id"))
{
dgvConsignees.Columns["Id"].Visible = false;
}
foreach (DataGridViewColumn column in dgvConsignees.Columns)
{
column.ReadOnly = column.Name != "Location ID" && column.Name != "Адрес";
}
}
catch (Exception ex)
{
MessageBox.Show($"Возникла ошибка при заполнении данных о грузополучателях.\nТекст ошибки:\n{ex}", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
conConsignees.Close();
}
}
}
@ -61,9 +61,9 @@ order by [ConsigneesUseAsDefault] desc";
{
if (e.KeyChar == (char)Keys.Enter)
{
if (dgvConsignees.Rows.Count > 0)
if (dgvConsignees.Rows.Count > 0 && dgvConsignees.DataSource is DataTable table)
{
(dgvConsignees.DataSource as DataTable).DefaultView.RowFilter = string.Format($"[Наименование] like '%{txtInteractiveShowMeConsignees.Text}%'");
table.DefaultView.RowFilter = $"[Наименование] like '%{txtInteractiveShowMeConsignees.Text.Replace("'", "''")}%'";
if (dgvConsignees.Rows.Count == 0)
{
showMeContentFromConsigneesTable();
@ -77,5 +77,133 @@ order by [ConsigneesUseAsDefault] desc";
}
}
private void btnSave_Click(object sender, EventArgs e)
{
if (!(dgvConsignees.DataSource is DataTable table))
{
return;
}
dgvConsignees.EndEdit();
using (var connection = new SQLiteConnection(AppConfig.SqliteConnectionString))
{
connection.Open();
using (var tx = connection.BeginTransaction())
{
foreach (DataRow row in table.Rows)
{
if (row.RowState == DataRowState.Unchanged)
{
continue;
}
using (var cmd = new SQLiteCommand(
@"UPDATE Consignees
SET ConsigneesAddress = @address,
LocationId = @locationId
WHERE idConsignees = @id",
connection,
tx))
{
cmd.Parameters.AddWithValue("@address", row["Адрес"]?.ToString() ?? string.Empty);
cmd.Parameters.AddWithValue("@locationId", row["Location ID"]?.ToString()?.Trim() ?? string.Empty);
cmd.Parameters.AddWithValue("@id", Convert.ToInt64(row["Id"]));
cmd.ExecuteNonQuery();
}
}
tx.Commit();
}
}
table.AcceptChanges();
ToastNotification.ShowSuccess("Грузополучатели сохранены");
showMeContentFromConsigneesTable();
}
private void btnAdd_Click(object sender, EventArgs e)
{
using (var dialog = new Form())
{
dialog.Text = "Новая аптека";
dialog.FormBorderStyle = FormBorderStyle.FixedDialog;
dialog.StartPosition = FormStartPosition.CenterParent;
dialog.ClientSize = new System.Drawing.Size(420, 220);
dialog.MaximizeBox = false;
dialog.MinimizeBox = false;
var lblName = new Label { Text = "Наименование", Left = 16, Top = 16, Width = 380 };
var txtName = new TextBox { Left = 16, Top = 40, Width = 380 };
var lblAddress = new Label { Text = "Адрес", Left = 16, Top = 76, Width = 380 };
var txtAddress = new TextBox { Left = 16, Top = 100, Width = 380 };
var lblLocation = new Label { Text = "Location ID", Left = 16, Top = 136, Width = 380 };
var txtLocation = new TextBox { Left = 16, Top = 160, Width = 380 };
var btnOk = new Button { Text = "Добавить", DialogResult = DialogResult.OK, Left = 220, Top = 188, Width = 80 };
var btnCancel = new Button { Text = "Отмена", DialogResult = DialogResult.Cancel, Left = 310, Top = 188, Width = 80 };
dialog.Controls.AddRange(new Control[] { lblName, txtName, lblAddress, txtAddress, lblLocation, txtLocation, btnOk, btnCancel });
dialog.AcceptButton = btnOk;
dialog.CancelButton = btnCancel;
UiThemeHelper.ApplyToControlTree(dialog);
if (dialog.ShowDialog(this) != DialogResult.OK)
{
return;
}
var name = (txtName.Text ?? string.Empty).Trim();
var address = (txtAddress.Text ?? string.Empty).Trim();
var locationId = (txtLocation.Text ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(name))
{
UiDialogs.ShowError("Укажите наименование аптеки.", "Грузополучатели", this);
return;
}
using (var connection = new SQLiteConnection(AppConfig.SqliteConnectionString))
{
connection.Open();
int nextCode = 1;
using (var codeCmd = new SQLiteCommand("SELECT ifnull(MAX(codeConsignees), 0) + 1 FROM Consignees", connection))
{
nextCode = Convert.ToInt32(codeCmd.ExecuteScalar());
}
using (var insert = new SQLiteCommand(
@"INSERT INTO Consignees (codeConsignees, ConsigneesName, ConsigneesAddress, ConsigneesUseAsDefault, LocationId)
VALUES (@code, @name, @address, 0, @locationId)",
connection))
{
insert.Parameters.AddWithValue("@code", nextCode);
insert.Parameters.AddWithValue("@name", name);
insert.Parameters.AddWithValue("@address", address);
insert.Parameters.AddWithValue("@locationId", locationId);
insert.ExecuteNonQuery();
}
}
ToastNotification.ShowSuccess("Аптека добавлена");
showMeContentFromConsigneesTable();
}
}
private void btnSetDefault_Click(object sender, EventArgs e)
{
if (dgvConsignees.CurrentRow == null)
{
UiDialogs.ShowInfo("Выберите аптеку в списке.", "Грузополучатели", this);
return;
}
var name = dgvConsignees.CurrentRow.Cells["Наименование"]?.Value?.ToString();
if (string.IsNullOrWhiteSpace(name))
{
return;
}
ConsigneeHelper.SetActiveConsignee(name);
ToastNotification.ShowSuccess($"Аптека по умолчанию: {name}");
showMeContentFromConsigneesTable();
}
}
}

View File

@ -2,15 +2,8 @@
{
partial class FDebug
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
@ -22,115 +15,267 @@
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.RTXT_Sql = new System.Windows.Forms.RichTextBox();
this.label1 = new System.Windows.Forms.Label();
this.btnCleanSqlScript = new Elfisa.UI.Controls.ModernButton();
this.btnExecuteSqlScript = new Elfisa.UI.Controls.ModernButton();
this.splitMain = new System.Windows.Forms.SplitContainer();
this.listTemplates = new System.Windows.Forms.ListBox();
this.lblTemplates = new System.Windows.Forms.Label();
this.splitRight = new System.Windows.Forms.SplitContainer();
this.panelTop = new System.Windows.Forms.Panel();
this.btnScanParams = new Elfisa.UI.Controls.ModernButton();
this.btnExecuteSelectSqlScript = new Elfisa.UI.Controls.ModernButton();
this.btnExecuteSqlScript = new Elfisa.UI.Controls.ModernButton();
this.btnCleanSqlScript = new Elfisa.UI.Controls.ModernButton();
this.lblDbPath = new System.Windows.Forms.Label();
this.label1 = new System.Windows.Forms.Label();
this.RTXT_Sql = new System.Windows.Forms.RichTextBox();
this.lblParams = new System.Windows.Forms.Label();
this.panelParams = new System.Windows.Forms.Panel();
this.dgvSelectCommand = new Elfisa.UI.Controls.ModernDataGridView();
this.lblStatus = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)(this.splitMain)).BeginInit();
this.splitMain.Panel1.SuspendLayout();
this.splitMain.Panel2.SuspendLayout();
this.splitMain.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.splitRight)).BeginInit();
this.splitRight.Panel1.SuspendLayout();
this.splitRight.Panel2.SuspendLayout();
this.splitRight.SuspendLayout();
this.panelTop.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.dgvSelectCommand)).BeginInit();
this.SuspendLayout();
//
// RTXT_Sql
// splitMain
//
this.RTXT_Sql.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.RTXT_Sql.Location = new System.Drawing.Point(9, 70);
this.RTXT_Sql.Margin = new System.Windows.Forms.Padding(4);
this.RTXT_Sql.Name = "RTXT_Sql";
this.RTXT_Sql.Size = new System.Drawing.Size(565, 118);
this.RTXT_Sql.TabIndex = 0;
this.RTXT_Sql.Text = "";
this.splitMain.Dock = System.Windows.Forms.DockStyle.Fill;
this.splitMain.Location = new System.Drawing.Point(0, 0);
this.splitMain.Name = "splitMain";
//
// label1
// splitMain.Panel1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(6, 50);
this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(84, 16);
this.label1.TabIndex = 1;
this.label1.Text = "SQL-запрос";
this.splitMain.Panel1.Controls.Add(this.listTemplates);
this.splitMain.Panel1.Controls.Add(this.lblTemplates);
this.splitMain.Panel1MinSize = 180;
//
// btnCleanSqlScript
// splitMain.Panel2
//
this.btnCleanSqlScript.Location = new System.Drawing.Point(9, 15);
this.btnCleanSqlScript.Margin = new System.Windows.Forms.Padding(4);
this.btnCleanSqlScript.Name = "btnCleanSqlScript";
this.btnCleanSqlScript.Size = new System.Drawing.Size(100, 28);
this.btnCleanSqlScript.TabIndex = 2;
this.btnCleanSqlScript.Text = "Очистить";
this.btnCleanSqlScript.UseVisualStyleBackColor = true;
this.btnCleanSqlScript.Click += new System.EventHandler(this.btnCleanSqlScript_Click);
this.splitMain.Panel2.Controls.Add(this.splitRight);
this.splitMain.Size = new System.Drawing.Size(980, 620);
this.splitMain.SplitterDistance = 240;
this.splitMain.TabIndex = 0;
//
// btnExecuteSqlScript
// listTemplates
//
this.btnExecuteSqlScript.Location = new System.Drawing.Point(128, 15);
this.btnExecuteSqlScript.Margin = new System.Windows.Forms.Padding(4);
this.btnExecuteSqlScript.Name = "btnExecuteSqlScript";
this.btnExecuteSqlScript.Size = new System.Drawing.Size(100, 28);
this.btnExecuteSqlScript.TabIndex = 3;
this.btnExecuteSqlScript.Text = "Выполнить";
this.btnExecuteSqlScript.UseVisualStyleBackColor = true;
this.btnExecuteSqlScript.Click += new System.EventHandler(this.btnExecuteSqlScript_Click);
this.listTemplates.Dock = System.Windows.Forms.DockStyle.Fill;
this.listTemplates.Font = new System.Drawing.Font("Segoe UI", 10F);
this.listTemplates.FormattingEnabled = true;
this.listTemplates.ItemHeight = 23;
this.listTemplates.Location = new System.Drawing.Point(0, 28);
this.listTemplates.Name = "listTemplates";
this.listTemplates.Size = new System.Drawing.Size(240, 592);
this.listTemplates.TabIndex = 1;
this.listTemplates.SelectedIndexChanged += new System.EventHandler(this.listTemplates_SelectedIndexChanged);
//
// lblTemplates
//
this.lblTemplates.Dock = System.Windows.Forms.DockStyle.Top;
this.lblTemplates.Font = new System.Drawing.Font("Segoe UI Semibold", 10F, System.Drawing.FontStyle.Bold);
this.lblTemplates.Location = new System.Drawing.Point(0, 0);
this.lblTemplates.Name = "lblTemplates";
this.lblTemplates.Padding = new System.Windows.Forms.Padding(8, 4, 0, 0);
this.lblTemplates.Size = new System.Drawing.Size(240, 28);
this.lblTemplates.TabIndex = 0;
this.lblTemplates.Text = "Шаблоны SQL";
//
// splitRight
//
this.splitRight.Dock = System.Windows.Forms.DockStyle.Fill;
this.splitRight.Location = new System.Drawing.Point(0, 0);
this.splitRight.Name = "splitRight";
this.splitRight.Orientation = System.Windows.Forms.Orientation.Horizontal;
//
// splitRight.Panel1
//
this.splitRight.Panel1.Controls.Add(this.RTXT_Sql);
this.splitRight.Panel1.Controls.Add(this.panelParams);
this.splitRight.Panel1.Controls.Add(this.lblParams);
this.splitRight.Panel1.Controls.Add(this.panelTop);
this.splitRight.Panel1MinSize = 220;
//
// splitRight.Panel2
//
this.splitRight.Panel2.Controls.Add(this.dgvSelectCommand);
this.splitRight.Panel2.Controls.Add(this.lblStatus);
this.splitRight.Panel2MinSize = 120;
this.splitRight.Size = new System.Drawing.Size(736, 620);
this.splitRight.SplitterDistance = 320;
this.splitRight.TabIndex = 0;
//
// panelTop
//
this.panelTop.Controls.Add(this.btnScanParams);
this.panelTop.Controls.Add(this.btnExecuteSelectSqlScript);
this.panelTop.Controls.Add(this.btnExecuteSqlScript);
this.panelTop.Controls.Add(this.btnCleanSqlScript);
this.panelTop.Controls.Add(this.lblDbPath);
this.panelTop.Controls.Add(this.label1);
this.panelTop.Dock = System.Windows.Forms.DockStyle.Top;
this.panelTop.Location = new System.Drawing.Point(0, 0);
this.panelTop.Name = "panelTop";
this.panelTop.Size = new System.Drawing.Size(736, 72);
this.panelTop.TabIndex = 0;
//
// btnScanParams
//
this.btnScanParams.Location = new System.Drawing.Point(430, 36);
this.btnScanParams.Name = "btnScanParams";
this.btnScanParams.Size = new System.Drawing.Size(150, 28);
this.btnScanParams.TabIndex = 5;
this.btnScanParams.Text = "Параметры из SQL";
this.btnScanParams.Click += new System.EventHandler(this.btnScanParams_Click);
//
// btnExecuteSelectSqlScript
//
this.btnExecuteSelectSqlScript.Location = new System.Drawing.Point(254, 15);
this.btnExecuteSelectSqlScript.Margin = new System.Windows.Forms.Padding(4);
this.btnExecuteSelectSqlScript.Location = new System.Drawing.Point(232, 36);
this.btnExecuteSelectSqlScript.Name = "btnExecuteSelectSqlScript";
this.btnExecuteSelectSqlScript.Size = new System.Drawing.Size(182, 28);
this.btnExecuteSelectSqlScript.TabIndex = 4;
this.btnExecuteSelectSqlScript.Text = "Выполнить SELECT";
this.btnExecuteSelectSqlScript.UseVisualStyleBackColor = true;
this.btnExecuteSelectSqlScript.Click += new System.EventHandler(this.btnExecuteSelectSqlScript_Click);
//
// btnExecuteSqlScript
//
this.btnExecuteSqlScript.Location = new System.Drawing.Point(116, 36);
this.btnExecuteSqlScript.Name = "btnExecuteSqlScript";
this.btnExecuteSqlScript.Size = new System.Drawing.Size(100, 28);
this.btnExecuteSqlScript.TabIndex = 3;
this.btnExecuteSqlScript.Text = "Выполнить";
this.btnExecuteSqlScript.Click += new System.EventHandler(this.btnExecuteSqlScript_Click);
//
// btnCleanSqlScript
//
this.btnCleanSqlScript.Location = new System.Drawing.Point(10, 36);
this.btnCleanSqlScript.Name = "btnCleanSqlScript";
this.btnCleanSqlScript.Size = new System.Drawing.Size(100, 28);
this.btnCleanSqlScript.TabIndex = 2;
this.btnCleanSqlScript.Text = "Очистить";
this.btnCleanSqlScript.Click += new System.EventHandler(this.btnCleanSqlScript_Click);
//
// lblDbPath
//
this.lblDbPath.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.lblDbPath.Location = new System.Drawing.Point(100, 8);
this.lblDbPath.Name = "lblDbPath";
this.lblDbPath.Size = new System.Drawing.Size(620, 20);
this.lblDbPath.TabIndex = 1;
this.lblDbPath.Text = "db";
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(8, 8);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(84, 16);
this.label1.TabIndex = 0;
this.label1.Text = "SQL / база:";
//
// RTXT_Sql
//
this.RTXT_Sql.Dock = System.Windows.Forms.DockStyle.Fill;
this.RTXT_Sql.Font = new System.Drawing.Font("Consolas", 10F);
this.RTXT_Sql.Location = new System.Drawing.Point(0, 100);
this.RTXT_Sql.Name = "RTXT_Sql";
this.RTXT_Sql.Size = new System.Drawing.Size(736, 120);
this.RTXT_Sql.TabIndex = 3;
this.RTXT_Sql.Text = "";
//
// lblParams
//
this.lblParams.Dock = System.Windows.Forms.DockStyle.Top;
this.lblParams.Location = new System.Drawing.Point(0, 72);
this.lblParams.Name = "lblParams";
this.lblParams.Padding = new System.Windows.Forms.Padding(8, 4, 0, 0);
this.lblParams.Size = new System.Drawing.Size(736, 28);
this.lblParams.TabIndex = 1;
this.lblParams.Text = "Параметры (@name / {{name}})";
//
// panelParams
//
this.panelParams.AutoScroll = true;
this.panelParams.Dock = System.Windows.Forms.DockStyle.Bottom;
this.panelParams.Location = new System.Drawing.Point(0, 220);
this.panelParams.Name = "panelParams";
this.panelParams.Size = new System.Drawing.Size(736, 100);
this.panelParams.TabIndex = 2;
//
// dgvSelectCommand
//
this.dgvSelectCommand.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.dgvSelectCommand.AllowUserToAddRows = false;
this.dgvSelectCommand.AllowUserToDeleteRows = false;
this.dgvSelectCommand.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.DisplayedCells;
this.dgvSelectCommand.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dgvSelectCommand.Location = new System.Drawing.Point(9, 195);
this.dgvSelectCommand.Dock = System.Windows.Forms.DockStyle.Fill;
this.dgvSelectCommand.Location = new System.Drawing.Point(0, 0);
this.dgvSelectCommand.Name = "dgvSelectCommand";
this.dgvSelectCommand.ReadOnly = true;
this.dgvSelectCommand.RowHeadersWidth = 51;
this.dgvSelectCommand.RowTemplate.Height = 24;
this.dgvSelectCommand.Size = new System.Drawing.Size(562, 150);
this.dgvSelectCommand.TabIndex = 5;
this.dgvSelectCommand.Size = new System.Drawing.Size(736, 268);
this.dgvSelectCommand.TabIndex = 0;
//
// lblStatus
//
this.lblStatus.Dock = System.Windows.Forms.DockStyle.Bottom;
this.lblStatus.Location = new System.Drawing.Point(0, 268);
this.lblStatus.Name = "lblStatus";
this.lblStatus.Padding = new System.Windows.Forms.Padding(8, 4, 8, 4);
this.lblStatus.Size = new System.Drawing.Size(736, 28);
this.lblStatus.TabIndex = 1;
this.lblStatus.Text = "Готово";
//
// FDebug
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(583, 351);
this.Controls.Add(this.dgvSelectCommand);
this.Controls.Add(this.btnExecuteSelectSqlScript);
this.Controls.Add(this.btnExecuteSqlScript);
this.Controls.Add(this.btnCleanSqlScript);
this.Controls.Add(this.label1);
this.Controls.Add(this.RTXT_Sql);
this.Margin = new System.Windows.Forms.Padding(4);
this.ClientSize = new System.Drawing.Size(980, 620);
this.Controls.Add(this.splitMain);
this.MinimumSize = new System.Drawing.Size(800, 500);
this.Name = "FDebug";
this.Text = "FDebug";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "SQL-отладка";
this.Load += new System.EventHandler(this.FDebug_Load);
this.splitMain.Panel1.ResumeLayout(false);
this.splitMain.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.splitMain)).EndInit();
this.splitMain.ResumeLayout(false);
this.splitRight.Panel1.ResumeLayout(false);
this.splitRight.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.splitRight)).EndInit();
this.splitRight.ResumeLayout(false);
this.panelTop.ResumeLayout(false);
this.panelTop.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.dgvSelectCommand)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.RichTextBox RTXT_Sql;
private System.Windows.Forms.Label label1;
private Elfisa.UI.Controls.ModernButton btnCleanSqlScript;
private Elfisa.UI.Controls.ModernButton btnExecuteSqlScript;
private System.Windows.Forms.SplitContainer splitMain;
private System.Windows.Forms.ListBox listTemplates;
private System.Windows.Forms.Label lblTemplates;
private System.Windows.Forms.SplitContainer splitRight;
private System.Windows.Forms.Panel panelTop;
private Elfisa.UI.Controls.ModernButton btnScanParams;
private Elfisa.UI.Controls.ModernButton btnExecuteSelectSqlScript;
private Elfisa.UI.Controls.ModernButton btnExecuteSqlScript;
private Elfisa.UI.Controls.ModernButton btnCleanSqlScript;
private System.Windows.Forms.Label lblDbPath;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.RichTextBox RTXT_Sql;
private System.Windows.Forms.Label lblParams;
private System.Windows.Forms.Panel panelParams;
private Elfisa.UI.Controls.ModernDataGridView dgvSelectCommand;
private System.Windows.Forms.Label lblStatus;
}
}

View File

@ -1,81 +1,385 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SQLite;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using Elfisa.UI.Theming;
using Электроннаяармация.Classes;
using System.Data.SQLite;
namespace Электроннаяармация.Forms
{
public partial class FDebug : Form
{
private sealed class SqlTemplate
{
public string Title { get; set; }
public string Sql { get; set; }
public string[] Parameters { get; set; }
}
private readonly List<SqlTemplate> _templates = new List<SqlTemplate>();
private readonly Dictionary<string, TextBox> _paramBoxes = new Dictionary<string, TextBox>(StringComparer.OrdinalIgnoreCase);
private readonly Timer _highlightTimer = new Timer();
private bool _highlighting;
public FDebug()
{
InitializeComponent();
_highlightTimer.Interval = 180;
_highlightTimer.Tick += (_, __) =>
{
_highlightTimer.Stop();
ApplySqlHighlight();
};
}
private void btnExecuteSqlScript_Click(object sender, EventArgs e)
private void FDebug_Load(object sender, EventArgs e)
{
string connectionStringToLocalDB = AppConfig.SqliteConnectionString;
string sqlScript = RTXT_Sql.Text;
using (SQLiteConnection debugCon = new SQLiteConnection(connectionStringToLocalDB))
UiThemeHelper.ApplyToControlTree(this);
Text = "SQL-отладка";
lblDbPath.Text = AppConfig.SqliteDbPath;
RTXT_Sql.DetectUrls = false;
RTXT_Sql.AcceptsTab = true;
RTXT_Sql.TextChanged += RTXT_Sql_TextChanged;
BuildTemplates();
listTemplates.Items.Clear();
foreach (var template in _templates)
{
listTemplates.Items.Add(template.Title);
}
SetStatus("Готово. Выберите шаблон или введите свой SQL.");
ApplySqlHighlight();
FormClosed += (_, __) =>
{
_highlightTimer.Stop();
_highlightTimer.Dispose();
};
}
private void RTXT_Sql_TextChanged(object sender, EventArgs e)
{
if (_highlighting)
{
return;
}
_highlightTimer.Stop();
_highlightTimer.Start();
}
private void ApplySqlHighlight()
{
if (_highlighting || RTXT_Sql == null || RTXT_Sql.IsDisposed)
{
return;
}
_highlighting = true;
try
{
debugCon.Open();
SQLiteCommand cmdDebug = new SQLiteCommand(sqlScript, debugCon);
cmdDebug.ExecuteNonQuery();
}
catch (Exception ex)
{
MessageBox.Show($"При выполнении запроса возникла ошибка. Текст ошибки:\n{ex.ToString()}", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
SqlSyntaxHighlighter.Apply(RTXT_Sql, ThemeManager.IsDarkProductivityActive);
}
finally
{
debugCon.Close();
_highlighting = false;
}
}
private void BuildTemplates()
{
_templates.Clear();
_templates.Add(new SqlTemplate
{
Title = "Счётчик прайса",
Sql = "SELECT COUNT(*) AS [Позиций] FROM PriceList;"
});
_templates.Add(new SqlTemplate
{
Title = "Прайс по поставщикам",
Sql = @"SELECT SupplierName AS [Поставщик],
COUNT(*) AS [Позиций],
MIN(Price) AS [МинЦена],
MAX(Price) AS [МаксЦена]
FROM PriceList
GROUP BY SupplierName
ORDER BY COUNT(*) DESC;"
});
_templates.Add(new SqlTemplate
{
Title = "Заказы по статусам",
Sql = @"SELECT OrderState AS [Статус], COUNT(*) AS [Кол-во]
FROM Orders
GROUP BY OrderState
ORDER BY COUNT(*) DESC;"
});
_templates.Add(new SqlTemplate
{
Title = "Грузополучатели + LocationId",
Sql = @"SELECT codeConsignees AS [Код],
ConsigneesName AS [Аптека],
ConsigneesAddress AS [Адрес],
ifnull(LocationId, '') AS [LocationId],
ConsigneesUseAsDefault AS [ПоУмолчанию]
FROM Consignees
ORDER BY ConsigneesUseAsDefault DESC, ConsigneesName;"
});
_templates.Add(new SqlTemplate
{
Title = "Корзина TempOrderItems",
Sql = @"SELECT COUNT(*) AS [Строк],
ifnull(SUM(CAST(SummaZakaza AS REAL)), 0) AS [Сумма]
FROM TempOrderItems;"
});
_templates.Add(new SqlTemplate
{
Title = "Поиск по наименованию",
Sql = @"SELECT DrugName, SupplierName, Price, Quantity, supplier_price_id
FROM PriceList
WHERE DrugName LIKE '%' || @q || '%'
ORDER BY DrugName
LIMIT 200;",
Parameters = new[] { "q" }
});
_templates.Add(new SqlTemplate
{
Title = "Последние заказы",
Sql = @"SELECT Id_Order, OrderNumber, OrderDate, SupplierName, ConsigneeName, OrderState, SummaZakaza
FROM Orders
ORDER BY Id_Order DESC
LIMIT 50;"
});
_templates.Add(new SqlTemplate
{
Title = "Список таблиц",
Sql = @"SELECT name AS [Таблица], type AS [Тип]
FROM sqlite_master
WHERE type IN ('table', 'view')
ORDER BY name;"
});
_templates.Add(new SqlTemplate
{
Title = "Позиции заказа",
Sql = @"SELECT oi.Id_Order, oi.DrugName, oi.Zakaz, oi.Price, oi.supplier_price_id, o.ConsigneeName, o.OrderState
FROM OrderItems oi
INNER JOIN Orders o ON o.Id_Order = oi.Id_Order
WHERE oi.Id_Order = @orderId
ORDER BY oi.DrugName;",
Parameters = new[] { "orderId" }
});
_templates.Add(new SqlTemplate
{
Title = "Очистить корзину (DML)",
Sql = "DELETE FROM TempOrderItems;"
});
}
private void listTemplates_SelectedIndexChanged(object sender, EventArgs e)
{
if (listTemplates.SelectedIndex < 0 || listTemplates.SelectedIndex >= _templates.Count)
{
return;
}
var template = _templates[listTemplates.SelectedIndex];
RTXT_Sql.Text = template.Sql;
RebuildParamEditors(template.Parameters);
SetStatus($"Шаблон: {template.Title}");
}
private void RebuildParamEditors(string[] parameters)
{
panelParams.Controls.Clear();
_paramBoxes.Clear();
if (parameters == null || parameters.Length == 0)
{
var empty = new Label
{
Text = "Параметры не требуются. Можно писать {{name}} или @name в SQL.",
AutoSize = true,
MaximumSize = new Size(panelParams.Width - 20, 0),
Location = new Point(8, 8)
};
panelParams.Controls.Add(empty);
return;
}
int y = 8;
foreach (var name in parameters)
{
var lbl = new Label
{
Text = "@" + name,
AutoSize = true,
Location = new Point(8, y + 4)
};
var box = new TextBox
{
Name = "param_" + name,
Width = Math.Max(180, panelParams.Width - 140),
Location = new Point(120, y)
};
panelParams.Controls.Add(lbl);
panelParams.Controls.Add(box);
_paramBoxes[name] = box;
y += 34;
}
}
private void btnCleanSqlScript_Click(object sender, EventArgs e)
{
RTXT_Sql.Text = "";
RTXT_Sql.Text = string.Empty;
dgvSelectCommand.DataSource = null;
RebuildParamEditors(null);
SetStatus("Очищено.");
}
private void btnExecuteSqlScript_Click(object sender, EventArgs e)
{
ExecuteSql(forceSelect: false);
}
private void btnExecuteSelectSqlScript_Click(object sender, EventArgs e)
{
string connectionStringToLocalDB = AppConfig.SqliteConnectionString;
ExecuteSql(forceSelect: true);
}
string sqlScript = RTXT_Sql.Text;
private void ExecuteSql(bool forceSelect)
{
var sql = (RTXT_Sql.Text ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(sql))
{
SetStatus("Пустой SQL.");
return;
}
using (SQLiteConnection debugSelectCon = new SQLiteConnection(connectionStringToLocalDB))
sql = ApplyMustachePlaceholders(sql);
bool isSelect = forceSelect || LooksLikeSelect(sql);
var sw = Stopwatch.StartNew();
using (var connection = new SQLiteConnection(AppConfig.SqliteConnectionString))
{
try
{
debugSelectCon.Open();
SQLiteCommand cmdSelectSqlCommand = new SQLiteCommand(sqlScript, debugSelectCon);
SQLiteDataAdapter daSelectDataAdapter = new SQLiteDataAdapter(cmdSelectSqlCommand);
DataTable tableSelect = new DataTable();
daSelectDataAdapter.Fill(tableSelect);
dgvSelectCommand.DataSource = tableSelect;
connection.Open();
using (var command = new SQLiteCommand(sql, connection))
{
BindParameters(command, sql);
if (isSelect)
{
using (var adapter = new SQLiteDataAdapter(command))
{
var table = new DataTable();
adapter.Fill(table);
dgvSelectCommand.DataSource = table;
sw.Stop();
SetStatus($"SELECT: {table.Rows.Count} строк за {sw.ElapsedMilliseconds} мс");
}
}
else
{
int affected = command.ExecuteNonQuery();
dgvSelectCommand.DataSource = null;
sw.Stop();
SetStatus($"Выполнено. Затронуто строк: {affected}. {sw.ElapsedMilliseconds} мс");
}
}
}
catch (Exception ex)
{
MessageBox.Show($"Возникла ошибка при заполнении таблицы данными.\nТекст ошибки:\n{ex.ToString()}", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
sw.Stop();
SetStatus("Ошибка: " + ex.Message);
MessageBox.Show($"При выполнении запроса возникла ошибка.\n{ex}", "SQL-отладка", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
}
}
private static bool LooksLikeSelect(string sql)
{
debugSelectCon.Close();
var trimmed = sql.TrimStart();
return trimmed.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase)
|| trimmed.StartsWith("PRAGMA", StringComparison.OrdinalIgnoreCase)
|| trimmed.StartsWith("WITH", StringComparison.OrdinalIgnoreCase)
|| trimmed.StartsWith("EXPLAIN", StringComparison.OrdinalIgnoreCase);
}
private string ApplyMustachePlaceholders(string sql)
{
return Regex.Replace(sql, @"\{\{\s*([A-Za-z_][A-Za-z0-9_]*)\s*\}\}", match =>
{
var name = match.Groups[1].Value;
if (_paramBoxes.TryGetValue(name, out var box))
{
return (box.Text ?? string.Empty).Replace("'", "''");
}
return match.Value;
});
}
private void BindParameters(SQLiteCommand command, string sql)
{
var names = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (Match match in Regex.Matches(sql, @"@([A-Za-z_][A-Za-z0-9_]*)"))
{
names.Add(match.Groups[1].Value);
}
foreach (var name in names)
{
object value = string.Empty;
if (_paramBoxes.TryGetValue(name, out var box))
{
value = box.Text ?? string.Empty;
}
command.Parameters.AddWithValue("@" + name, value);
}
// Параметры из панели, даже если в SQL их ещё нет (удобно для шаблонов).
foreach (var pair in _paramBoxes)
{
if (!names.Contains(pair.Key))
{
command.Parameters.AddWithValue("@" + pair.Key, pair.Value.Text ?? string.Empty);
}
}
}
private void SetStatus(string text)
{
lblStatus.Text = text;
}
private void btnScanParams_Click(object sender, EventArgs e)
{
var sql = RTXT_Sql.Text ?? string.Empty;
var names = Regex.Matches(sql, @"@([A-Za-z_][A-Za-z0-9_]*)")
.Cast<Match>()
.Select(m => m.Groups[1].Value)
.Concat(Regex.Matches(sql, @"\{\{\s*([A-Za-z_][A-Za-z0-9_]*)\s*\}\}")
.Cast<Match>()
.Select(m => m.Groups[1].Value))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
var existing = _paramBoxes.ToDictionary(p => p.Key, p => p.Value.Text, StringComparer.OrdinalIgnoreCase);
RebuildParamEditors(names);
foreach (var pair in existing)
{
if (_paramBoxes.TryGetValue(pair.Key, out var box))
{
box.Text = pair.Value;
}
}
SetStatus(names.Length == 0 ? "Параметры в SQL не найдены." : $"Найдено параметров: {names.Length}");
}
}
}

View File

@ -1,21 +1,16 @@
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 Электроннаяармация.Classes;
using System.Data.SQLite;
using System.Windows.Forms;
using Электроннаяармация;
using Электроннаяармация.Classes;
namespace Электроннаяармация.HelpForms
{
public partial class HFConsignees : Form
{
public ElectroPharmacy ParentForm { get; set; }
public new ElectroPharmacy ParentForm { get; set; }
public HFConsignees()
{
InitializeComponent();
@ -31,7 +26,12 @@ namespace Электронная_Фармация.HelpForms
{
conConsigneesList.Open();
string qShowMeConsigneesList = "select ConsigneesName as [Грузополучатель] from Consignees order by ConsigneesUseAsDefault desc";
string qShowMeConsigneesList = @"
select ConsigneesName as [Грузополучатель],
ifnull(LocationId, '') as [Location ID],
ConsigneesAddress as [Адрес]
from Consignees
order by ConsigneesUseAsDefault desc, ConsigneesName";
SQLiteCommand cmdShowMeConsigneesList = new SQLiteCommand(qShowMeConsigneesList, conConsigneesList);
SQLiteDataAdapter daConsigneesList = new SQLiteDataAdapter(cmdShowMeConsigneesList);
@ -43,16 +43,13 @@ namespace Электронная_Фармация.HelpForms
{
UiDialogs.ShowError($"При загрузке списка получателей возникла ошибка.\n{ex}", "Ошибка", this);
}
finally
{
conConsigneesList.Close();
}
}
}
private void btnCloseThis_Click(object sender, EventArgs e)
{
this.Close();
DialogResult = DialogResult.Cancel;
Close();
}
private void btnConfirmConsignee_Click(object sender, EventArgs e)
@ -63,7 +60,27 @@ namespace Электронная_Фармация.HelpForms
return;
}
string consigneesName = dgvConsignees.CurrentRow.Cells[0].Value.ToString();
string consigneesName = dgvConsignees.CurrentRow.Cells[0].Value?.ToString();
if (string.IsNullOrWhiteSpace(consigneesName))
{
UiDialogs.ShowInfo("Выберите грузополучателя из списка.", "Грузополучатель", this);
return;
}
var locationId = ConsigneeHelper.GetLocationIdByName(consigneesName);
if (string.IsNullOrWhiteSpace(locationId))
{
var ask = UiDialogs.ConfirmYesNo(
$"У аптеки «{consigneesName}» не задан Location ID.\nПродолжить без него? Заказы этой аптеки нельзя будет отправить, пока ID не заполните.",
"Location ID",
this);
if (ask != DialogResult.Yes)
{
return;
}
}
ConsigneeHelper.SetActiveConsignee(consigneesName);
ParentForm?.showMePriceList(consigneesName);
DialogResult = DialogResult.OK;
Close();

View File

@ -149,27 +149,182 @@ namespace Электронная_Фармация.HelpForms
? null
: Settings.Default.RegionId;
// Один крупный запрос (OFFSET на больших страницах валит сервер).
const int pageSize = 200000;
const int maxPages = 100;
const int supplierPageLimit = 100000;
const int discoveryLimit = 50000;
var combined = new PriceSummaryResponse { Summary = new List<PriceSummaryItem>() };
var transferProgress = new Progress<HttpTransferProgress>(ReportTransferProgress);
for (int page = 0; page < maxPages; page++)
var supplierIds = new List<string>(ConsigneeHelper.GetKnownSupplierIds());
if (supplierIds.Count == 0)
{
int offset = page * pageSize;
AppendStatus("Первая загрузка: определяю список поставщиков...");
SetOverallProgress(3, "Определение поставщиков...");
var discovery = await GetPriceSummaryWithAuthRetryAsync(
client,
supplierId: null,
regionId: regionId,
limit: discoveryLimit,
offset: 0,
transferProgress: transferProgress);
if (discovery?.MarkupPercent.HasValue == true)
{
combined.MarkupPercent = discovery.MarkupPercent;
}
var discovered = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (discovery?.Summary != null)
{
foreach (var item in discovery.Summary)
{
if (!string.IsNullOrWhiteSpace(item.SupplierId))
{
discovered.Add(item.SupplierId.Trim());
}
}
}
supplierIds.AddRange(discovered);
AppendStatus($"Найдено поставщиков: {supplierIds.Count}");
}
if (supplierIds.Count == 0)
{
AppendStatus("Поставщики не найдены, пробую полный запрос...");
var fallback = await GetPriceSummaryWithAuthRetryAsync(
client,
supplierId: null,
regionId: regionId,
limit: 200000,
offset: 0,
transferProgress: transferProgress);
if (fallback?.MarkupPercent.HasValue == true)
{
combined.MarkupPercent = fallback.MarkupPercent;
}
if (fallback?.Summary != null)
{
combined.Summary.AddRange(fallback.Summary);
}
return combined;
}
int totalSuppliers = supplierIds.Count;
for (int i = 0; i < totalSuppliers; i++)
{
var supplierId = supplierIds[i];
int supplierProgressStart = 3 + (int)((DownloadPhaseEnd - 3) * ((double)i / totalSuppliers));
int supplierProgressEnd = 3 + (int)((DownloadPhaseEnd - 3) * ((double)(i + 1) / totalSuppliers));
SetOverallProgress(
Math.Min(DownloadPhaseEnd - 1, 3 + page),
page == 0 ? "Скачивание прайса с сервера..." : $"Скачивание страницы {page + 1}...");
supplierProgressStart,
$"Поставщик {i + 1}/{totalSuppliers}...");
AppendStatus($"Загрузка поставщика {i + 1}/{totalSuppliers}: {supplierId}");
PriceSummaryResponse chunk;
try
{
chunk = await client.GetPriceSummaryAsync(
chunk = await GetPriceSummaryWithAuthRetryAsync(
client,
supplierId: supplierId,
regionId: regionId,
limit: supplierPageLimit,
offset: 0,
transferProgress: transferProgress);
}
catch (Exception ex)
{
AppendStatus($"Ошибка поставщика {supplierId}: {ex.Message}");
AppDebugLog.Error("Download", $"Ошибка загрузки прайса поставщика {supplierId}", ex);
continue;
}
if (i == 0 && chunk?.MarkupPercent.HasValue == true && !combined.MarkupPercent.HasValue)
{
combined.MarkupPercent = chunk.MarkupPercent;
}
int got = chunk?.Summary?.Count ?? 0;
if (got > 0)
{
combined.Summary.AddRange(chunk.Summary);
}
SetOverallProgress(
Math.Max(supplierProgressStart, supplierProgressEnd - 1),
$"Поставщик {i + 1}/{totalSuppliers}: +{got}, всего {combined.Summary.Count}");
AppendStatus($"Поставщик {i + 1}/{totalSuppliers}: получено {got}, всего {combined.Summary.Count}");
}
// Лёгкий discovery новых поставщиков, которых ещё не было в локальной базе.
try
{
AppendStatus("Проверка новых поставщиков...");
var probe = await GetPriceSummaryWithAuthRetryAsync(
client,
supplierId: null,
regionId: regionId,
limit: pageSize,
limit: 5000,
offset: 0,
transferProgress: null);
var known = new HashSet<string>(supplierIds, StringComparer.OrdinalIgnoreCase);
var newcomers = new List<string>();
if (probe?.Summary != null)
{
foreach (var item in probe.Summary)
{
if (!string.IsNullOrWhiteSpace(item.SupplierId) && known.Add(item.SupplierId.Trim()))
{
newcomers.Add(item.SupplierId.Trim());
}
}
}
for (int i = 0; i < newcomers.Count; i++)
{
var supplierId = newcomers[i];
AppendStatus($"Новый поставщик {i + 1}/{newcomers.Count}: {supplierId}");
var chunk = await GetPriceSummaryWithAuthRetryAsync(
client,
supplierId: supplierId,
regionId: regionId,
limit: supplierPageLimit,
offset: 0,
transferProgress: transferProgress);
int got = chunk?.Summary?.Count ?? 0;
if (got > 0)
{
combined.Summary.AddRange(chunk.Summary);
}
AppendStatus($"Новый поставщик: +{got}, всего {combined.Summary.Count}");
}
}
catch (Exception ex)
{
AppendStatus($"Проверка новых поставщиков пропущена: {ex.Message}");
AppDebugLog.Error("Download", "Ошибка discovery новых поставщиков", ex);
}
return combined;
}
private async Task<PriceSummaryResponse> GetPriceSummaryWithAuthRetryAsync(
ApiClient client,
string supplierId,
string regionId,
int limit,
int offset,
IProgress<HttpTransferProgress> transferProgress)
{
try
{
return await client.GetPriceSummaryAsync(
supplierId: supplierId,
regionId: regionId,
limit: limit,
offset: offset,
transferProgress: transferProgress);
}
@ -181,34 +336,13 @@ namespace Электронная_Фармация.HelpForms
Settings.Default.Save();
client.SetToken(null);
await LoginAndSaveTokenAsync(client);
SetOverallProgress(3, "Повторная загрузка прайса...");
chunk = await client.GetPriceSummaryAsync(
supplierId: null,
return await client.GetPriceSummaryAsync(
supplierId: supplierId,
regionId: regionId,
limit: pageSize,
limit: limit,
offset: offset,
transferProgress: transferProgress);
}
int got = chunk?.Summary?.Count ?? 0;
if (page == 0 && chunk?.MarkupPercent.HasValue == true)
{
combined.MarkupPercent = chunk.MarkupPercent;
}
if (got > 0)
{
combined.Summary.AddRange(chunk.Summary);
AppendStatus($"Загружено {combined.Summary.Count} позиций...");
}
if (got < pageSize)
{
break;
}
}
return combined;
}
private static void ApplyClientMarkupSettings(PriceSummaryResponse response)
@ -379,29 +513,47 @@ VALUES
}
using (var transaction = sqliteCon.BeginTransaction())
{
int saved = 0;
foreach (DataRow row in tablePrice.Rows)
{
using (var cmd = new SQLiteCommand(commandToInsert, sqliteCon, transaction))
{
cmd.Parameters.AddWithValue("@guid_es", row["guid_es"]);
cmd.Parameters.AddWithValue("@es_code", row["es_code"]);
cmd.Parameters.AddWithValue("@supplier_id", row["supplier_id"]);
cmd.Parameters.AddWithValue("@DrugName", row["DrugName"]);
cmd.Parameters.AddWithValue("@SupplierName", row["SupplierName"]);
cmd.Parameters.AddWithValue("@Price", row["Price"]);
cmd.Parameters.AddWithValue("@Quantity", row["Quantity"]);
cmd.Parameters.AddWithValue("@ExpiryPeriod", row["ExpiryPeriod"]);
cmd.Parameters.AddWithValue("@Description", row["Description"]);
cmd.Parameters.AddWithValue("@Zakaz", row["Zakaz"]);
cmd.Parameters.AddWithValue("@SummaZakaza", row["SummaZakaza"]);
cmd.Parameters.AddWithValue("@supplier_price_id", row["supplier_price_id"]);
cmd.Parameters.AddWithValue("@TradeName", row.Table.Columns.Contains("TradeName") ? row["TradeName"] : DBNull.Value);
cmd.Parameters.AddWithValue("@Dosage", row.Table.Columns.Contains("Dosage") ? row["Dosage"] : DBNull.Value);
cmd.Parameters.AddWithValue("@MarkupPercent", row.Table.Columns.Contains("MarkupPercent") ? row["MarkupPercent"] : DBNull.Value);
var pGuidEs = cmd.Parameters.Add("@guid_es", DbType.String);
var pEsCode = cmd.Parameters.Add("@es_code", DbType.String);
var pSupplierId = cmd.Parameters.Add("@supplier_id", DbType.String);
var pDrugName = cmd.Parameters.Add("@DrugName", DbType.String);
var pSupplierName = cmd.Parameters.Add("@SupplierName", DbType.String);
var pPrice = cmd.Parameters.Add("@Price", DbType.String);
var pQuantity = cmd.Parameters.Add("@Quantity", DbType.String);
var pExpiry = cmd.Parameters.Add("@ExpiryPeriod", DbType.String);
var pDescription = cmd.Parameters.Add("@Description", DbType.String);
var pZakaz = cmd.Parameters.Add("@Zakaz", DbType.String);
var pSumma = cmd.Parameters.Add("@SummaZakaza", DbType.String);
var pSupplierPriceId = cmd.Parameters.Add("@supplier_price_id", DbType.String);
var pTradeName = cmd.Parameters.Add("@TradeName", DbType.String);
var pDosage = cmd.Parameters.Add("@Dosage", DbType.String);
var pMarkup = cmd.Parameters.Add("@MarkupPercent", DbType.Object);
int saved = 0;
bool hasTradeName = tablePrice.Columns.Contains("TradeName");
bool hasDosage = tablePrice.Columns.Contains("Dosage");
bool hasMarkup = tablePrice.Columns.Contains("MarkupPercent");
foreach (DataRow row in tablePrice.Rows)
{
pGuidEs.Value = row["guid_es"] ?? DBNull.Value;
pEsCode.Value = row["es_code"] ?? DBNull.Value;
pSupplierId.Value = row["supplier_id"] ?? DBNull.Value;
pDrugName.Value = row["DrugName"] ?? DBNull.Value;
pSupplierName.Value = row["SupplierName"] ?? DBNull.Value;
pPrice.Value = row["Price"] ?? DBNull.Value;
pQuantity.Value = row["Quantity"] ?? DBNull.Value;
pExpiry.Value = row["ExpiryPeriod"] ?? DBNull.Value;
pDescription.Value = row["Description"] ?? DBNull.Value;
pZakaz.Value = row["Zakaz"] ?? DBNull.Value;
pSumma.Value = row["SummaZakaza"] ?? DBNull.Value;
pSupplierPriceId.Value = row["supplier_price_id"] ?? DBNull.Value;
pTradeName.Value = hasTradeName ? (row["TradeName"] ?? DBNull.Value) : DBNull.Value;
pDosage.Value = hasDosage ? (row["Dosage"] ?? DBNull.Value) : DBNull.Value;
pMarkup.Value = hasMarkup ? (row["MarkupPercent"] ?? DBNull.Value) : DBNull.Value;
cmd.ExecuteNonQuery();
}
saved++;
if (progress != null && (saved % 250 == 0 || saved == total))

View File

@ -16,10 +16,6 @@ namespace Электронная_Фармация.HelpForms
Shown += (_, __) => WindowChromeHelper.ApplyTitleBarTheme(this, ThemeManager.Colors);
}
string login = Settings.Default.stringLogin.ToString();
string password = Settings.Default.stringPassword.ToString();
string doWeHaveAToken = Settings.Default.stringToken.ToString();
private void btnCloseThis_Click(object sender, EventArgs e)
{
Close();
@ -30,8 +26,8 @@ namespace Электронная_Фармация.HelpForms
btnConfirmRegistration.Enabled = false;
btnCloseThis.Enabled = false;
UseWaitCursor = true;
string strLogin = txtLogin.Text;
string strPassword = txtPassword.Text;
string strLogin = (txtLogin.Text ?? string.Empty).Trim();
string strPassword = txtPassword.Text ?? string.Empty;
if (strLogin.Length > 0 && strPassword.Length > 0)
{
@ -40,7 +36,7 @@ namespace Электронная_Фармация.HelpForms
: Settings.Default.ApiBaseUrl;
LoginRequest loginRequest = new LoginRequest();
loginRequest.Username = strLogin.Trim();
loginRequest.Username = strLogin;
loginRequest.Password = strPassword.Trim();
var client = new ApiClient(AppConfig.NormalizeApiBaseUrl(apiUrl));
@ -50,8 +46,9 @@ namespace Электронная_Фармация.HelpForms
{
string token = await client.LoginAsync(loginRequest.Username, loginRequest.Password);
// Сохраняем только логин и токен; пароль в настройках не храним.
Settings.Default.stringLogin = strLogin;
Settings.Default.stringPassword = strPassword;
Settings.Default.stringPassword = string.Empty;
Settings.Default.stringToken = token;
Settings.Default.ApiBaseUrl = client.BaseUrl;
Settings.Default.Save();
@ -59,14 +56,25 @@ namespace Электронная_Фармация.HelpForms
AppDebugLog.Info("Auth", $"Авторизация успешна. token={AppDebugLog.MaskToken(token)}");
ToastNotification.ShowSuccess("Авторизация успешно выполнена");
// После входа обязательно запрашиваем Location ID для отправки заказов.
if (!HF_LocationId.PromptAndSave(this))
// Fallback Location ID (на аптеку задаётся у грузополучателя).
if (string.IsNullOrWhiteSpace(AppConfig.LocationId))
{
if (HF_LocationId.PromptAndSave(this))
{
ConsigneeHelper.ApplyLocationIdToEmptyConsignees(AppConfig.LocationId);
}
else
{
ToastNotification.ShowCustom(
"Location ID не указан. Отправка заказов будет недоступна, пока его не заполните.",
"Location ID не указан. Задайте его у грузополучателя или в настройках.",
System.Drawing.Color.DarkOrange,
System.Drawing.Color.White);
}
}
else
{
ConsigneeHelper.ApplyLocationIdToEmptyConsignees(AppConfig.LocationId);
}
DialogResult = DialogResult.OK;
Close();
@ -97,9 +105,10 @@ namespace Электронная_Фармация.HelpForms
{
UiThemeHelper.ApplyToControlTree(this);
WindowChromeHelper.ApplyDialogChrome(this, panel1, label3);
txtLogin.Text = login;
txtPassword.Text = password;
checkTokenGained.Checked = doWeHaveAToken.Length > 0;
// Только последний успешный логин; пароль пользователь вводит каждый раз.
txtLogin.Text = Settings.Default.stringLogin ?? string.Empty;
txtPassword.Text = string.Empty;
checkTokenGained.Checked = !string.IsNullOrWhiteSpace(Settings.Default.stringToken);
}
}
}

View File

@ -59,7 +59,7 @@ namespace Электронная_Фармация.HelpForms
this.lblHint.Name = "lblHint";
this.lblHint.Size = new System.Drawing.Size(512, 46);
this.lblHint.TabIndex = 1;
this.lblHint.Text = "Здесь можно задать торговую точку, адрес API и папку для экспорта накладных.";
this.lblHint.Text = "Location ID ниже — запасной. Для каждой аптеки задайте свой ID в «Грузополучатели». Также укажите API и папку накладных.";
//
// lblLocationId
//