elfisa-pharmacy/src/ElectronicPharmacy/Form1.UiShell.cs
Magomed fd5bd18c66 Add per-pharmacy carts and client-side summed discount pricing.
Apply available buyer/price-list/region discounts to summary.price on download, keep consignee carts separate, and harden SQLite startup/migrations.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 14:07:47 +03:00

470 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using Elfisa.UI.Controls;
using Elfisa.UI.Helpers;
using Elfisa.UI.Loading;
using Elfisa.UI.Notifications;
using Elfisa.UI.Theming;
using Электроннаяармация.Classes;
using Электроннаяармация.Properties;
using Электроннаяармация.Forms;
using Электроннаяармация.HelpForms;
using Электроннаяармация.UserControls;
namespace Электроннаяармация
{
public partial class ElectroPharmacy
{
private readonly Dictionary<string, Control> _documentContents = new Dictionary<string, Control>();
private readonly ThemeService _themeService = new ThemeService();
private ToastService _toastService;
private LoadingOverlay _loadingOverlay;
private ModernAppHeader _appHeader;
private ModernDocumentTabStrip _documentTabStrip;
private Panel _documentHost;
private Panel _statusFooter;
private Label _lblCurrentUser;
private ModernButton _btnSettings;
private ContextMenuStrip _directoryMenu;
private ContextMenuStrip _documentTabMenu;
private ToolStripMenuItem _documentCloseTabMenuItem;
private ToolStripMenuItem _documentCloseAllTabsMenuItem;
partial void InitializeModernShell()
{
Font = new Font("Segoe UI", 12f);
AutoScaleMode = AutoScaleMode.Dpi;
KeyPreview = true;
MinimumSize = new Size(900, 620);
bindControl.Visible = false;
tabContent.Visible = false;
_toastService = new ToastService(this);
AppServices.Initialize(_toastService);
_loadingOverlay = new LoadingOverlay { Name = "loadingOverlay", Dock = DockStyle.Fill };
_appHeader = new ModernAppHeader { Name = "appHeader", Dock = DockStyle.Top };
_appHeader.SetItems(new[]
{
new SidebarItem { Key = "price", Text = "ПРАЙС-ЛИСТ", IconGlyph = "\uE8FD" },
new SidebarItem { Key = "orders", Text = "ЗАКАЗЫ", IconGlyph = "\uE719" },
new SidebarItem { Key = "invoices", Text = "НАКЛАДНЫЕ", IconGlyph = "\uE8A5" },
new SidebarItem { Key = "upload", Text = "ЗАГРУЗИТЬ", IconGlyph = "\uE896" },
new SidebarItem { Key = "send", Text = "ОТПРАВИТЬ", IconGlyph = "\uE725" },
new SidebarItem { Key = "refusals", Text = "ОТКАЗЫ", IconGlyph = "\uE711" },
new SidebarItem { Key = "directory", Text = "СПРАВОЧНИК", IconGlyph = "\uE8FD" },
new SidebarItem { Key = "auth", Text = "АВТОРИЗАЦИЯ", IconGlyph = "\uE192" }
});
_documentTabStrip = new ModernDocumentTabStrip { Name = "documentTabStrip", Dock = DockStyle.Top };
_documentHost = new Panel { Name = "documentHost", Dock = DockStyle.Fill, Tag = "chrome-child" };
_statusFooter = new Panel
{
Name = "statusFooter",
Dock = DockStyle.Bottom,
Height = 28,
Tag = "status-footer",
Padding = new Padding(10, 0, 4, 0)
};
_statusFooter.Paint += (_, e) =>
{
var border = ThemeManager.Colors.Border;
using (var pen = new Pen(border))
{
e.Graphics.DrawLine(pen, 0, 0, _statusFooter.Width, 0);
}
};
_lblCurrentUser = new Label
{
Name = "lblCurrentUser",
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleLeft,
AutoEllipsis = true,
Tag = "muted",
Padding = new Padding(0, 0, 4, 0)
};
_btnSettings = new ModernButton
{
Name = "btnSettings",
Dock = DockStyle.Right,
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();
// Сначала Fill (пользователь), потом Right (кнопка) — кнопка справа, текст не перекрывается.
_statusFooter.Controls.Add(_lblCurrentUser);
_statusFooter.Controls.Add(_btnSettings);
// Клик по строке "аптека" открывает выбор грузополучателя без перезапуска приложения.
// Т.к. метка у нас одна (Dock=Fill), обработчик навешиваем на весь Label.
_lblCurrentUser.Cursor = Cursors.Hand;
_lblCurrentUser.Click += (_, __) => SwitchConsignee();
Controls.Add(_loadingOverlay);
Controls.Add(_documentHost);
Controls.Add(_statusFooter);
Controls.Add(_documentTabStrip);
Controls.Add(_appHeader);
_directoryMenu = new ContextMenuStrip();
var contractorsItem = new ToolStripMenuItem("Поставщики");
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("Отладка");
debugItem.Click += (_, __) => tsDebug_Click(_, __);
_directoryMenu.Items.AddRange(new ToolStripItem[]
{
contractorsItem,
consigneesItem,
switchConsigneeItem,
invoicesItem,
new ToolStripSeparator(),
debugItem
});
_appHeader.ItemSelected += OnAppHeaderItemSelected;
_documentTabStrip.ThemeToggleClicked += OnThemeToggleClicked;
_documentTabStrip.TabSelected += OnDocumentTabSelected;
_documentTabStrip.TabClosed += OnDocumentTabClosed;
_documentTabStrip.TabCloseAllRequested += OnDocumentTabCloseAll;
_documentCloseTabMenuItem = new ToolStripMenuItem("Закрыть вкладку");
_documentCloseTabMenuItem.Click += (_, __) =>
{
var tabId = _documentTabStrip.ActiveTabId;
if (!string.IsNullOrEmpty(tabId))
{
CloseDocumentTab(tabId);
}
};
_documentCloseAllTabsMenuItem = new ToolStripMenuItem("Закрыть все вкладки");
_documentCloseAllTabsMenuItem.Click += (_, __) => ClearDocumentTabs();
_documentTabMenu = new ContextMenuStrip();
_documentTabMenu.Items.Add(_documentCloseTabMenuItem);
_documentTabMenu.Items.Add(_documentCloseAllTabsMenuItem);
ThemeManager.ThemeChanged += (_, __) =>
{
UiThemeHelper.ApplyToControlTree(this);
UpdateThemeToggleState();
WindowChromeHelper.ApplyTitleBarTheme(this, ThemeManager.Colors);
};
HandleCreated += (_, __) => WindowChromeHelper.ApplyTitleBarTheme(this, ThemeManager.Colors);
}
private void UpdateThemeToggleState()
{
if (_documentTabStrip != null)
{
_documentTabStrip.IsDarkTheme = ThemeManager.IsDarkProductivityActive;
}
}
private void OnThemeToggleClicked(object sender, EventArgs e)
{
ThemeManager.ToggleProductivityTheme();
Settings.Default.UseDarkTheme = ThemeManager.IsDarkProductivityActive;
Settings.Default.Save();
UpdateThemeToggleState();
}
partial void FinalizeModernShell()
{
ThemeManager.CurrentVariant = Settings.Default.UseDarkTheme
? ThemeVariant.DarkProductivity
: ThemeVariant.PharmaTrust;
_themeService.Initialize(this);
UpdateThemeToggleState();
UiThemeHelper.ApplyToControlTree(this);
WindowChromeHelper.ApplyTitleBarTheme(this, ThemeManager.Colors);
RefreshCurrentUserStatus();
}
private void RefreshCurrentUserStatus()
{
if (_lblCurrentUser == null)
{
return;
}
var login = Settings.Default.stringLogin?.Trim();
var hasToken = !string.IsNullOrWhiteSpace(Settings.Default.stringToken);
var pharmacy = ConsigneeName?.Trim();
if (hasToken &&
(string.IsNullOrWhiteSpace(pharmacy) || pharmacy == "Прайс-Лист"))
{
pharmacy = ConsigneeHelper.GetDefaultConsigneeName();
if (!string.IsNullOrWhiteSpace(pharmacy) &&
(string.IsNullOrWhiteSpace(ConsigneeName) || ConsigneeName == "Прайс-Лист"))
{
ConsigneeName = pharmacy;
}
}
string userPart;
if (!string.IsNullOrWhiteSpace(login) && hasToken)
{
userPart = $"Пользователь: {login}";
}
else if (!string.IsNullOrWhiteSpace(login))
{
userPart = $"Пользователь: {login} (не авторизован)";
}
else
{
userPart = "Пользователь не авторизован";
}
if (hasToken && !string.IsNullOrWhiteSpace(pharmacy) && pharmacy != "Прайс-Лист")
{
_lblCurrentUser.Text = $"{userPart} | Аптека: {pharmacy}";
}
else
{
_lblCurrentUser.Text = $"{userPart} | Аптека: не выбрана";
}
}
private void OnAppHeaderItemSelected(object sender, string key)
{
switch (key)
{
case "price":
loadDefaultPriceList();
break;
case "orders":
OpenOrdersTab();
break;
case "invoices":
OpenInvoicesTab();
break;
case "upload":
tsBtnDownloadData_Click(this, EventArgs.Empty);
_appHeader.SetActive("upload");
break;
case "send":
tsBtnSentData_Click(this, EventArgs.Empty);
_appHeader.SetActive("send");
break;
case "refusals":
OpenRefusalsTab();
break;
case "directory":
_directoryMenu.Show(_appHeader, new Point(_appHeader.Width - 220, _appHeader.Height - 4));
break;
case "auth":
tsBtnAuth_Click(this, EventArgs.Empty);
break;
}
}
private void OnDocumentTabSelected(object sender, string tabId)
{
ShowDocumentTab(tabId);
var tab = _documentTabStrip.Tabs.FirstOrDefault(item => item.Id == tabId);
if (tab != null)
{
_appHeader.SetActive(tab.NavKey);
}
}
private string OpenDocumentTab(string title, string navKey, Control content, bool allowDuplicate)
{
if (!allowDuplicate)
{
var existing = _documentTabStrip.FindByNavKey(navKey);
if (existing != null)
{
_documentTabStrip.SelectTab(existing.Id);
ShowDocumentTab(existing.Id);
_appHeader.SetActive(navKey);
return existing.Id;
}
}
var tabId = _documentTabStrip.AddTab(title, navKey);
content.Dock = DockStyle.Fill;
content.Tag = "chrome-child";
content.Visible = false;
_documentContents[tabId] = content;
_documentHost.Controls.Add(content);
UiThemeHelper.ApplyToControlTree(content);
WireDocumentContextMenu(content);
_documentTabStrip.SelectTab(tabId);
ShowDocumentTab(tabId);
_appHeader.SetActive(navKey);
return tabId;
}
private void ShowDocumentTab(string tabId)
{
foreach (var pair in _documentContents)
{
pair.Value.Visible = pair.Key == tabId;
}
}
private void OnDocumentTabClosed(object sender, string tabId)
{
CloseDocumentTab(tabId);
}
private void OnDocumentTabCloseAll(object sender, EventArgs e)
{
ClearDocumentTabs();
}
private void WireDocumentContextMenu(Control control)
{
control.MouseUp -= OnDocumentContentMouseUp;
control.MouseUp += OnDocumentContentMouseUp;
foreach (Control child in control.Controls)
{
WireDocumentContextMenu(child);
}
}
private void OnDocumentContentMouseUp(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Right || _documentTabStrip.Tabs.Count == 0)
{
return;
}
if (sender is Control clicked && HasDedicatedContextMenu(clicked))
{
return;
}
_documentCloseTabMenuItem.Enabled = !string.IsNullOrEmpty(_documentTabStrip.ActiveTabId);
_documentCloseAllTabsMenuItem.Enabled = _documentTabStrip.Tabs.Count > 0;
var screenPoint = (sender as Control)?.PointToScreen(e.Location) ?? Cursor.Position;
_documentTabMenu.Show(screenPoint);
}
private static bool HasDedicatedContextMenu(Control control)
{
for (var current = control; current != null; current = current.Parent)
{
if (current.ContextMenuStrip != null)
{
return true;
}
}
return false;
}
private void CloseDocumentTab(string tabId)
{
if (_documentContents.TryGetValue(tabId, out var control))
{
_documentHost.Controls.Remove(control);
control.Dispose();
_documentContents.Remove(tabId);
}
_documentTabStrip.RemoveTab(tabId);
}
private void ClearDocumentTabs()
{
foreach (var control in _documentContents.Values)
{
_documentHost.Controls.Remove(control);
control.Dispose();
}
_documentContents.Clear();
_documentTabStrip.ClearTabs();
}
private void OpenOrdersTab()
{
var orders = new FOrders
{
Dock = DockStyle.Fill,
TopLevel = false,
TopMost = true
};
OpenDocumentTab("Заказы", "orders", orders, allowDuplicate: true);
orders.Show();
}
public void OpenInvoicesTab(string sourceOrderId = null)
{
var existing = _documentTabStrip.FindByNavKey("invoices");
if (existing != null &&
_documentContents.TryGetValue(existing.Id, out var existingControl) &&
existingControl is UCInvoices existingInvoices)
{
existingInvoices.SetSourceOrderFilter(sourceOrderId);
_documentTabStrip.SelectTab(existing.Id);
ShowDocumentTab(existing.Id);
_appHeader.SetActive("invoices");
return;
}
var invoices = new UCInvoices(sourceOrderId);
OpenDocumentTab("Накладные", "invoices", invoices, allowDuplicate: false);
}
private void OpenRefusalsTab()
{
var refusals = new UCRefusals();
OpenDocumentTab("Отказы", "refusals", refusals, allowDuplicate: false);
}
partial void ShowShellLoading(string message)
{
_loadingOverlay?.Show(message);
_loadingOverlay?.BringToFront();
}
partial void HideShellLoading()
{
_loadingOverlay?.HideOverlay();
}
private bool HasDocumentTabs()
{
return _documentContents.Count > 0;
}
partial void RefreshOpenPriceLists()
{
foreach (var control in _documentContents.Values.OfType<UCPriceList>())
{
control.RefreshFromDatabase();
}
if (!_documentContents.Values.OfType<UCPriceList>().Any())
{
loadDefaultPriceList();
}
}
}
}