|
@@ -0,0 +1,751 @@
|
|
|
|
|
+using System.Data;
|
|
|
|
|
+using System.Text;
|
|
|
|
|
+using ComputerSalesApp.Data;
|
|
|
|
|
+using ComputerSalesApp.Models;
|
|
|
|
|
+using ComputerSalesApp.Services;
|
|
|
|
|
+
|
|
|
|
|
+namespace ComputerSalesApp.UI;
|
|
|
|
|
+
|
|
|
|
|
+internal sealed class MainForm : Form
|
|
|
|
|
+{
|
|
|
|
|
+ private readonly UserSession _session;
|
|
|
|
|
+ private readonly TableRepository _repository = new();
|
|
|
|
|
+ private readonly List<TableDefinition> _tables;
|
|
|
|
|
+
|
|
|
|
|
+ private readonly ListBox _tableList = new();
|
|
|
|
|
+ private readonly DataGridView _dataGrid = new();
|
|
|
|
|
+ private readonly Button _refreshButton = new();
|
|
|
|
|
+ private readonly Button _addButton = new();
|
|
|
|
|
+ private readonly Button _editButton = new();
|
|
|
|
|
+ private readonly Button _deleteButton = new();
|
|
|
|
|
+ private readonly Button _logoutButton = new();
|
|
|
|
|
+ private readonly Label _currentTableLabel = new();
|
|
|
|
|
+ private readonly Label _userLabel = new();
|
|
|
|
|
+
|
|
|
|
|
+ private readonly ComboBox _reportCustomerCombo = new();
|
|
|
|
|
+ private readonly DateTimePicker _reportDatePicker = new();
|
|
|
|
|
+ private readonly ComboBox _reportSortCombo = new();
|
|
|
|
|
+ private readonly Button _reportFindButton = new();
|
|
|
|
|
+ private readonly Button _reportExportButton = new();
|
|
|
|
|
+ private readonly DataGridView _reportGrid = new();
|
|
|
|
|
+ private readonly Label _reportHintLabel = new();
|
|
|
|
|
+
|
|
|
|
|
+ private TableDefinition? CurrentTable => _tableList.SelectedItem as TableDefinition;
|
|
|
|
|
+
|
|
|
|
|
+ public bool LogoutRequested { get; private set; }
|
|
|
|
|
+
|
|
|
|
|
+ public MainForm(UserSession session)
|
|
|
|
|
+ {
|
|
|
|
|
+ _session = session;
|
|
|
|
|
+ _tables = TableCatalog.CreateTables(session)
|
|
|
|
|
+ .Where(t => !t.AdminOnly || session.IsAdmin)
|
|
|
|
|
+ .ToList();
|
|
|
|
|
+
|
|
|
|
|
+ Text = "Продажа компьютерной техники";
|
|
|
|
|
+ StartPosition = FormStartPosition.CenterScreen;
|
|
|
|
|
+ MinimumSize = new Size(1120, 730);
|
|
|
|
|
+ BackColor = AppTheme.Background;
|
|
|
|
|
+ BuildUi();
|
|
|
|
|
+ Load += MainForm_Load;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void BuildUi()
|
|
|
|
|
+ {
|
|
|
|
|
+ var root = new TableLayoutPanel
|
|
|
|
|
+ {
|
|
|
|
|
+ Dock = DockStyle.Fill,
|
|
|
|
|
+ RowCount = 2,
|
|
|
|
|
+ ColumnCount = 1,
|
|
|
|
|
+ BackColor = AppTheme.Background
|
|
|
|
|
+ };
|
|
|
|
|
+ root.RowStyles.Add(new RowStyle(SizeType.Absolute, 82));
|
|
|
|
|
+ root.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
|
|
|
|
|
+ Controls.Add(root);
|
|
|
|
|
+
|
|
|
|
|
+ var header = new Panel
|
|
|
|
|
+ {
|
|
|
|
|
+ Dock = DockStyle.Fill,
|
|
|
|
|
+ BackColor = AppTheme.Header,
|
|
|
|
|
+ Padding = new Padding(24, 12, 18, 8)
|
|
|
|
|
+ };
|
|
|
|
|
+ root.Controls.Add(header, 0, 0);
|
|
|
|
|
+
|
|
|
|
|
+ var title = new Label
|
|
|
|
|
+ {
|
|
|
|
|
+ Text = "Продажа компьютерной техники",
|
|
|
|
|
+ ForeColor = Color.White,
|
|
|
|
|
+ Font = AppTheme.Bold(17f),
|
|
|
|
|
+ AutoSize = true,
|
|
|
|
|
+ Location = new Point(24, 14)
|
|
|
|
|
+ };
|
|
|
|
|
+ header.Controls.Add(title);
|
|
|
|
|
+
|
|
|
|
|
+ string roleText = _session.IsAdmin ? "администратор" : "покупатель";
|
|
|
|
|
+ _userLabel.Text = $"Пользователь: {_session.Login} | Роль: {roleText}";
|
|
|
|
|
+ _userLabel.ForeColor = Color.WhiteSmoke;
|
|
|
|
|
+ _userLabel.Font = AppTheme.Regular(10f);
|
|
|
|
|
+ _userLabel.AutoSize = true;
|
|
|
|
|
+ _userLabel.Location = new Point(26, 48);
|
|
|
|
|
+ header.Controls.Add(_userLabel);
|
|
|
|
|
+
|
|
|
|
|
+ _logoutButton.Text = "Выйти";
|
|
|
|
|
+ _logoutButton.Width = 120;
|
|
|
|
|
+ _logoutButton.Height = 36;
|
|
|
|
|
+ _logoutButton.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
|
|
|
|
+ _logoutButton.Location = new Point(header.Width - _logoutButton.Width - 18, 23);
|
|
|
|
|
+ AppTheme.StyleSecondaryButton(_logoutButton);
|
|
|
|
|
+ _logoutButton.Click += (_, _) =>
|
|
|
|
|
+ {
|
|
|
|
|
+ LogoutRequested = true;
|
|
|
|
|
+ Close();
|
|
|
|
|
+ };
|
|
|
|
|
+ header.Controls.Add(_logoutButton);
|
|
|
|
|
+ header.Resize += (_, _) => _logoutButton.Location = new Point(header.Width - _logoutButton.Width - 18, 23);
|
|
|
|
|
+
|
|
|
|
|
+ var tabs = new TabControl
|
|
|
|
|
+ {
|
|
|
|
|
+ Dock = DockStyle.Fill,
|
|
|
|
|
+ Font = AppTheme.Regular(10f)
|
|
|
|
|
+ };
|
|
|
|
|
+ root.Controls.Add(tabs, 0, 1);
|
|
|
|
|
+
|
|
|
|
|
+ var dataTab = new TabPage("Данные") { BackColor = AppTheme.Background };
|
|
|
|
|
+ var reportTab = new TabPage("Заказы покупателя по дате") { BackColor = AppTheme.Background };
|
|
|
|
|
+ tabs.TabPages.Add(dataTab);
|
|
|
|
|
+ tabs.TabPages.Add(reportTab);
|
|
|
|
|
+
|
|
|
|
|
+ BuildDataTab(dataTab);
|
|
|
|
|
+ BuildReportTab(reportTab);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void BuildDataTab(TabPage tab)
|
|
|
|
|
+ {
|
|
|
|
|
+ var layout = new TableLayoutPanel
|
|
|
|
|
+ {
|
|
|
|
|
+ Dock = DockStyle.Fill,
|
|
|
|
|
+ RowCount = 1,
|
|
|
|
|
+ ColumnCount = 2,
|
|
|
|
|
+ BackColor = AppTheme.Background,
|
|
|
|
|
+ Padding = new Padding(0)
|
|
|
|
|
+ };
|
|
|
|
|
+ layout.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
|
|
|
|
|
+ layout.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 290));
|
|
|
|
|
+ layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
|
|
|
|
|
+ tab.Controls.Add(layout);
|
|
|
|
|
+
|
|
|
|
|
+ var sidebar = new Panel
|
|
|
|
|
+ {
|
|
|
|
|
+ Dock = DockStyle.Fill,
|
|
|
|
|
+ BackColor = AppTheme.Surface,
|
|
|
|
|
+ Padding = new Padding(18, 16, 18, 14),
|
|
|
|
|
+ MinimumSize = new Size(290, 0)
|
|
|
|
|
+ };
|
|
|
|
|
+ layout.Controls.Add(sidebar, 0, 0);
|
|
|
|
|
+
|
|
|
|
|
+ var left = new TableLayoutPanel
|
|
|
|
|
+ {
|
|
|
|
|
+ Dock = DockStyle.Fill,
|
|
|
|
|
+ RowCount = 2,
|
|
|
|
|
+ ColumnCount = 1,
|
|
|
|
|
+ Padding = new Padding(0),
|
|
|
|
|
+ BackColor = AppTheme.Surface
|
|
|
|
|
+ };
|
|
|
|
|
+ left.RowStyles.Add(new RowStyle(SizeType.Absolute, 42));
|
|
|
|
|
+ left.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
|
|
|
|
|
+ sidebar.Controls.Add(left);
|
|
|
|
|
+
|
|
|
|
|
+ left.Controls.Add(new Label
|
|
|
|
|
+ {
|
|
|
|
|
+ Text = "Разделы",
|
|
|
|
|
+ Dock = DockStyle.Fill,
|
|
|
|
|
+ Font = AppTheme.Bold(12.5f),
|
|
|
|
|
+ ForeColor = AppTheme.Text,
|
|
|
|
|
+ TextAlign = ContentAlignment.MiddleLeft
|
|
|
|
|
+ }, 0, 0);
|
|
|
|
|
+
|
|
|
|
|
+ _tableList.Dock = DockStyle.Fill;
|
|
|
|
|
+ _tableList.Font = AppTheme.Regular(10.5f);
|
|
|
|
|
+ _tableList.ItemHeight = 24;
|
|
|
|
|
+ _tableList.BorderStyle = BorderStyle.FixedSingle;
|
|
|
|
|
+ _tableList.BackColor = Color.White;
|
|
|
|
|
+ _tableList.ForeColor = AppTheme.Text;
|
|
|
|
|
+ _tableList.HorizontalScrollbar = false;
|
|
|
|
|
+ _tableList.IntegralHeight = false;
|
|
|
|
|
+ _tableList.SelectedIndexChanged += async (_, _) => await LoadCurrentTableAsync();
|
|
|
|
|
+ left.Controls.Add(_tableList, 0, 1);
|
|
|
|
|
+
|
|
|
|
|
+ var right = new TableLayoutPanel
|
|
|
|
|
+ {
|
|
|
|
|
+ Dock = DockStyle.Fill,
|
|
|
|
|
+ RowCount = 3,
|
|
|
|
|
+ ColumnCount = 1,
|
|
|
|
|
+ Padding = new Padding(18, 16, 18, 14),
|
|
|
|
|
+ BackColor = AppTheme.Background
|
|
|
|
|
+ };
|
|
|
|
|
+ right.RowStyles.Add(new RowStyle(SizeType.Absolute, 44));
|
|
|
|
|
+ right.RowStyles.Add(new RowStyle(SizeType.Absolute, 52));
|
|
|
|
|
+ right.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
|
|
|
|
|
+ layout.Controls.Add(right, 1, 0);
|
|
|
|
|
+
|
|
|
|
|
+ _currentTableLabel.Text = "Выберите раздел";
|
|
|
|
|
+ _currentTableLabel.Dock = DockStyle.Fill;
|
|
|
|
|
+ _currentTableLabel.Font = AppTheme.Bold(14f);
|
|
|
|
|
+ _currentTableLabel.ForeColor = AppTheme.Text;
|
|
|
|
|
+ _currentTableLabel.TextAlign = ContentAlignment.MiddleLeft;
|
|
|
|
|
+ right.Controls.Add(_currentTableLabel, 0, 0);
|
|
|
|
|
+
|
|
|
|
|
+ var buttons = new FlowLayoutPanel
|
|
|
|
|
+ {
|
|
|
|
|
+ Dock = DockStyle.Fill,
|
|
|
|
|
+ FlowDirection = FlowDirection.LeftToRight,
|
|
|
|
|
+ BackColor = AppTheme.Background,
|
|
|
|
|
+ Padding = new Padding(0, 2, 0, 2)
|
|
|
|
|
+ };
|
|
|
|
|
+ right.Controls.Add(buttons, 0, 1);
|
|
|
|
|
+
|
|
|
|
|
+ ConfigureButton(_refreshButton, "Обновить", async (_, _) => await LoadCurrentTableAsync(), primary: true);
|
|
|
|
|
+ ConfigureButton(_addButton, "Добавить", async (_, _) => await AddRecordAsync());
|
|
|
|
|
+ ConfigureButton(_editButton, "Изменить", async (_, _) => await EditRecordAsync());
|
|
|
|
|
+ ConfigureButton(_deleteButton, "Удалить", async (_, _) => await DeleteRecordAsync());
|
|
|
|
|
+
|
|
|
|
|
+ buttons.Controls.AddRange(new Control[] { _refreshButton, _addButton, _editButton, _deleteButton });
|
|
|
|
|
+
|
|
|
|
|
+ if (!_session.IsAdmin)
|
|
|
|
|
+ {
|
|
|
|
|
+ _addButton.Visible = false;
|
|
|
|
|
+ _editButton.Visible = false;
|
|
|
|
|
+ _deleteButton.Visible = false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ _dataGrid.Dock = DockStyle.Fill;
|
|
|
|
|
+ _dataGrid.ReadOnly = true;
|
|
|
|
|
+ _dataGrid.AllowUserToAddRows = false;
|
|
|
|
|
+ _dataGrid.AllowUserToDeleteRows = false;
|
|
|
|
|
+ _dataGrid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
|
|
|
|
+ _dataGrid.MultiSelect = false;
|
|
|
|
|
+ AppTheme.StyleGrid(_dataGrid);
|
|
|
|
|
+ right.Controls.Add(_dataGrid, 0, 2);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void BuildReportTab(TabPage tab)
|
|
|
|
|
+ {
|
|
|
|
|
+ var root = new TableLayoutPanel
|
|
|
|
|
+ {
|
|
|
|
|
+ Dock = DockStyle.Fill,
|
|
|
|
|
+ RowCount = 3,
|
|
|
|
|
+ ColumnCount = 1,
|
|
|
|
|
+ Padding = new Padding(18),
|
|
|
|
|
+ BackColor = AppTheme.Background
|
|
|
|
|
+ };
|
|
|
|
|
+ root.RowStyles.Add(new RowStyle(SizeType.Absolute, 24));
|
|
|
|
|
+ root.RowStyles.Add(new RowStyle(SizeType.Absolute, 88));
|
|
|
|
|
+ root.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
|
|
|
|
|
+ tab.Controls.Add(root);
|
|
|
|
|
+
|
|
|
|
|
+ _reportHintLabel.Dock = DockStyle.Fill;
|
|
|
|
|
+ _reportHintLabel.ForeColor = AppTheme.Text;
|
|
|
|
|
+ _reportHintLabel.Font = AppTheme.Regular(9.5f);
|
|
|
|
|
+ _reportHintLabel.Text = _session.IsAdmin
|
|
|
|
|
+ ? "Администратор видит заказы любого покупателя."
|
|
|
|
|
+ : "Покупатель видит только свои заказы.";
|
|
|
|
|
+ root.Controls.Add(_reportHintLabel, 0, 0);
|
|
|
|
|
+
|
|
|
|
|
+ var filters = new TableLayoutPanel
|
|
|
|
|
+ {
|
|
|
|
|
+ Dock = DockStyle.Fill,
|
|
|
|
|
+ RowCount = 2,
|
|
|
|
|
+ ColumnCount = 5,
|
|
|
|
|
+ BackColor = AppTheme.Background
|
|
|
|
|
+ };
|
|
|
|
|
+ filters.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 40));
|
|
|
|
|
+ filters.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 180));
|
|
|
|
|
+ filters.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 270));
|
|
|
|
|
+ filters.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 160));
|
|
|
|
|
+ filters.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 180));
|
|
|
|
|
+ root.Controls.Add(filters, 0, 1);
|
|
|
|
|
+
|
|
|
|
|
+ AddFilterLabel(filters, "Покупатель", 0);
|
|
|
|
|
+ AddFilterLabel(filters, "Дата заказа", 1);
|
|
|
|
|
+ AddFilterLabel(filters, "Сортировка", 2);
|
|
|
|
|
+
|
|
|
|
|
+ _reportCustomerCombo.DropDownStyle = ComboBoxStyle.DropDownList;
|
|
|
|
|
+ _reportCustomerCombo.Dock = DockStyle.Top;
|
|
|
|
|
+ _reportCustomerCombo.Font = AppTheme.Regular(10f);
|
|
|
|
|
+ _reportCustomerCombo.Enabled = _session.IsAdmin;
|
|
|
|
|
+ filters.Controls.Add(_reportCustomerCombo, 0, 1);
|
|
|
|
|
+
|
|
|
|
|
+ _reportDatePicker.Format = DateTimePickerFormat.Short;
|
|
|
|
|
+ _reportDatePicker.Dock = DockStyle.Top;
|
|
|
|
|
+ _reportDatePicker.Font = AppTheme.Regular(10f);
|
|
|
|
|
+ filters.Controls.Add(_reportDatePicker, 1, 1);
|
|
|
|
|
+
|
|
|
|
|
+ _reportSortCombo.DropDownStyle = ComboBoxStyle.DropDownList;
|
|
|
|
|
+ _reportSortCombo.Items.AddRange(new object[] { "По возрастанию количества", "По убыванию количества" });
|
|
|
|
|
+ _reportSortCombo.SelectedIndex = 0;
|
|
|
|
|
+ _reportSortCombo.Dock = DockStyle.Top;
|
|
|
|
|
+ _reportSortCombo.Font = AppTheme.Regular(10f);
|
|
|
|
|
+ filters.Controls.Add(_reportSortCombo, 2, 1);
|
|
|
|
|
+
|
|
|
|
|
+ _reportFindButton.Text = "Найти";
|
|
|
|
|
+ _reportFindButton.Width = 140;
|
|
|
|
|
+ _reportFindButton.Height = 32;
|
|
|
|
|
+ _reportFindButton.Margin = new Padding(8, 0, 0, 0);
|
|
|
|
|
+ AppTheme.StylePrimaryButton(_reportFindButton);
|
|
|
|
|
+ _reportFindButton.Click += async (_, _) => await LoadReportAsync();
|
|
|
|
|
+ filters.Controls.Add(_reportFindButton, 3, 1);
|
|
|
|
|
+
|
|
|
|
|
+ _reportExportButton.Text = "Сохранить отчёт";
|
|
|
|
|
+ _reportExportButton.Width = 160;
|
|
|
|
|
+ _reportExportButton.Height = 32;
|
|
|
|
|
+ _reportExportButton.Margin = new Padding(8, 0, 0, 0);
|
|
|
|
|
+ _reportExportButton.Enabled = false;
|
|
|
|
|
+ AppTheme.StyleSecondaryButton(_reportExportButton);
|
|
|
|
|
+ _reportExportButton.Click += (_, _) => ExportReport();
|
|
|
|
|
+ filters.Controls.Add(_reportExportButton, 4, 1);
|
|
|
|
|
+
|
|
|
|
|
+ _reportGrid.Dock = DockStyle.Fill;
|
|
|
|
|
+ _reportGrid.ReadOnly = true;
|
|
|
|
|
+ _reportGrid.AllowUserToAddRows = false;
|
|
|
|
|
+ _reportGrid.AllowUserToDeleteRows = false;
|
|
|
|
|
+ _reportGrid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
|
|
|
|
+ _reportGrid.MultiSelect = false;
|
|
|
|
|
+ AppTheme.StyleGrid(_reportGrid);
|
|
|
|
|
+ root.Controls.Add(_reportGrid, 0, 2);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void AddFilterLabel(TableLayoutPanel filters, string text, int column)
|
|
|
|
|
+ {
|
|
|
|
|
+ filters.Controls.Add(new Label
|
|
|
|
|
+ {
|
|
|
|
|
+ Text = text,
|
|
|
|
|
+ Dock = DockStyle.Fill,
|
|
|
|
|
+ TextAlign = ContentAlignment.BottomLeft,
|
|
|
|
|
+ ForeColor = AppTheme.Text,
|
|
|
|
|
+ Font = AppTheme.Regular(10f)
|
|
|
|
|
+ }, column, 0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void ConfigureButton(Button button, string text, EventHandler onClick, bool primary = false)
|
|
|
|
|
+ {
|
|
|
|
|
+ button.Text = text;
|
|
|
|
|
+ button.Width = 135;
|
|
|
|
|
+ button.Height = 36;
|
|
|
|
|
+ button.Margin = new Padding(0, 5, 10, 5);
|
|
|
|
|
+ if (primary)
|
|
|
|
|
+ {
|
|
|
|
|
+ AppTheme.StylePrimaryButton(button);
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ AppTheme.StyleSecondaryButton(button);
|
|
|
|
|
+ }
|
|
|
|
|
+ button.Click += onClick;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private async void MainForm_Load(object? sender, EventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ _tableList.DataSource = _tables;
|
|
|
|
|
+ if (_tables.Count > 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ _tableList.SelectedIndex = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ await LoadReportCustomersAsync();
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ ErrorHandler.Show("Ошибка запуска", "Не удалось загрузить начальные данные приложения.", ex);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private async Task LoadCurrentTableAsync()
|
|
|
|
|
+ {
|
|
|
|
|
+ TableDefinition? table = CurrentTable;
|
|
|
|
|
+ if (table == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ SetBusy(true);
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ _currentTableLabel.Text = table.DisplayName;
|
|
|
|
|
+ DataTable data = await _repository.LoadTableAsync(table);
|
|
|
|
|
+ _dataGrid.DataSource = data;
|
|
|
|
|
+ ApplyGridSettings(_dataGrid, table.Headers, table.HiddenColumns);
|
|
|
|
|
+ UpdateActionButtons();
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ ErrorHandler.Show("Ошибка загрузки", $"Не удалось загрузить раздел \"{table.DisplayName}\".", ex);
|
|
|
|
|
+ }
|
|
|
|
|
+ finally
|
|
|
|
|
+ {
|
|
|
|
|
+ SetBusy(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private async Task AddRecordAsync()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!EnsureAdmin()) return;
|
|
|
|
|
+ TableDefinition? table = CurrentTable;
|
|
|
|
|
+ if (table == null || table.ReadOnly) return;
|
|
|
|
|
+
|
|
|
|
|
+ using var form = new EditRecordForm(table, null);
|
|
|
|
|
+ if (form.ShowDialog(this) != DialogResult.OK)
|
|
|
|
|
+ {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ SetBusy(true);
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ await _repository.InsertAsync(table, form.Values);
|
|
|
|
|
+ await LoadCurrentTableAsync();
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ ErrorHandler.Show("Ошибка добавления", $"Не удалось добавить запись в раздел \"{table.DisplayName}\".", ex);
|
|
|
|
|
+ }
|
|
|
|
|
+ finally
|
|
|
|
|
+ {
|
|
|
|
|
+ SetBusy(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private async Task EditRecordAsync()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!EnsureAdmin()) return;
|
|
|
|
|
+ TableDefinition? table = CurrentTable;
|
|
|
|
|
+ if (table == null || table.ReadOnly) return;
|
|
|
|
|
+ DataRowView? row = GetSelectedRow();
|
|
|
|
|
+ if (table == null || row == null) return;
|
|
|
|
|
+
|
|
|
|
|
+ using var form = new EditRecordForm(table, row);
|
|
|
|
|
+ if (form.ShowDialog(this) != DialogResult.OK)
|
|
|
|
|
+ {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ SetBusy(true);
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ int id = Convert.ToInt32(row[table.IdColumn]);
|
|
|
|
|
+ await _repository.UpdateAsync(table, id, form.Values);
|
|
|
|
|
+ await LoadCurrentTableAsync();
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ ErrorHandler.Show("Ошибка изменения", $"Не удалось изменить запись в разделе \"{table.DisplayName}\".", ex);
|
|
|
|
|
+ }
|
|
|
|
|
+ finally
|
|
|
|
|
+ {
|
|
|
|
|
+ SetBusy(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private async Task DeleteRecordAsync()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!EnsureAdmin()) return;
|
|
|
|
|
+ TableDefinition? table = CurrentTable;
|
|
|
|
|
+ if (table == null || table.ReadOnly) return;
|
|
|
|
|
+ DataRowView? row = GetSelectedRow();
|
|
|
|
|
+ if (table == null || row == null) return;
|
|
|
|
|
+
|
|
|
|
|
+ if (!ErrorHandler.Confirm("Удалить выбранную запись? Если она связана с другими таблицами, база данных не позволит удалить её."))
|
|
|
|
|
+ {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ SetBusy(true);
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ int id = Convert.ToInt32(row[table.IdColumn]);
|
|
|
|
|
+ await _repository.DeleteAsync(table, id);
|
|
|
|
|
+ await LoadCurrentTableAsync();
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ ErrorHandler.Show("Ошибка удаления", $"Не удалось удалить запись из раздела \"{table.DisplayName}\".", ex);
|
|
|
|
|
+ }
|
|
|
|
|
+ finally
|
|
|
|
|
+ {
|
|
|
|
|
+ SetBusy(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private async Task LoadReportCustomersAsync()
|
|
|
|
|
+ {
|
|
|
|
|
+ DataTable customers = await _repository.LoadCustomersForReportAsync(_session);
|
|
|
|
|
+ var items = customers.Rows.Cast<DataRow>()
|
|
|
|
|
+ .Where(row => row.Table.Columns.Contains("id") && row["id"] != DBNull.Value)
|
|
|
|
|
+ .Select(row => new ComboItem
|
|
|
|
|
+ {
|
|
|
|
|
+ Id = Convert.ToInt32(row["id"]),
|
|
|
|
|
+ Text = Convert.ToString(row["text"]) ?? string.Empty
|
|
|
|
|
+ })
|
|
|
|
|
+ .ToList();
|
|
|
|
|
+
|
|
|
|
|
+ _reportCustomerCombo.ValueMember = nameof(ComboItem.Id);
|
|
|
|
|
+ _reportCustomerCombo.DisplayMember = nameof(ComboItem.Text);
|
|
|
|
|
+ _reportCustomerCombo.DataSource = items;
|
|
|
|
|
+
|
|
|
|
|
+ if (items.Count == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ _reportHintLabel.Text = _session.IsAdmin
|
|
|
|
|
+ ? "В таблице покупателей нет записей. Добавьте покупателя, чтобы искать заказы."
|
|
|
|
|
+ : "Для этого пользователя не указан покупатель. Проверьте поле customer_id в app_users.";
|
|
|
|
|
+ _reportGrid.DataSource = new DataTable();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ _reportCustomerCombo.SelectedIndex = 0;
|
|
|
|
|
+ await SetLatestReportDateAsync();
|
|
|
|
|
+
|
|
|
|
|
+ // Отчёт не запускается автоматически при старте, чтобы случайная ошибка
|
|
|
|
|
+ // в представлении или пустая дата не мешала работать с основными таблицами.
|
|
|
|
|
+ // Пользователь сам нажимает кнопку "Найти".
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private async Task SetLatestReportDateAsync()
|
|
|
|
|
+ {
|
|
|
|
|
+ int? customerId = GetSelectedReportCustomerId(showMessage: false);
|
|
|
|
|
+ if (!customerId.HasValue)
|
|
|
|
|
+ {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ DateTime? latest = await _repository.LoadLatestOrderDateAsync(customerId.Value);
|
|
|
|
|
+ if (latest.HasValue)
|
|
|
|
|
+ {
|
|
|
|
|
+ _reportDatePicker.Value = latest.Value;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int? GetSelectedReportCustomerId(bool showMessage)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_reportCustomerCombo.SelectedItem is ComboItem selectedItem && selectedItem.Id.HasValue)
|
|
|
|
|
+ {
|
|
|
|
|
+ return selectedItem.Id.Value;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (_reportCustomerCombo.SelectedValue is int selectedValue)
|
|
|
|
|
+ {
|
|
|
|
|
+ return selectedValue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (_reportCustomerCombo.SelectedValue != null &&
|
|
|
|
|
+ int.TryParse(Convert.ToString(_reportCustomerCombo.SelectedValue), out int parsedValue))
|
|
|
|
|
+ {
|
|
|
|
|
+ return parsedValue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (showMessage)
|
|
|
|
|
+ {
|
|
|
|
|
+ MessageBox.Show("Выберите покупателя.", "Поиск заказов", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private async Task LoadReportAsync()
|
|
|
|
|
+ {
|
|
|
|
|
+ int? customerId = GetSelectedReportCustomerId(showMessage: true);
|
|
|
|
|
+ if (!customerId.HasValue)
|
|
|
|
|
+ {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ SetBusy(true);
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ bool descending = _reportSortCombo.SelectedIndex == 1;
|
|
|
|
|
+ DataTable data = await _repository.LoadCustomerOrderItemsAsync(_session, customerId.Value, _reportDatePicker.Value.Date, descending);
|
|
|
|
|
+ _reportGrid.DataSource = data;
|
|
|
|
|
+ _reportExportButton.Enabled = data.Rows.Count > 0;
|
|
|
|
|
+
|
|
|
|
|
+ ApplyGridSettings(_reportGrid, new Dictionary<string, string>
|
|
|
|
|
+ {
|
|
|
|
|
+ ["order_id"] = "№ заказа",
|
|
|
|
|
+ ["order_date"] = "Дата заказа",
|
|
|
|
|
+ ["customer_full_name"] = "Покупатель",
|
|
|
|
|
+ ["employee_full_name"] = "Сотрудник",
|
|
|
|
|
+ ["product_name"] = "Товар",
|
|
|
|
|
+ ["manufacturer"] = "Изготовитель",
|
|
|
|
|
+ ["quantity"] = "Количество",
|
|
|
|
|
+ ["unit_price"] = "Цена за единицу",
|
|
|
|
|
+ ["item_total"] = "Сумма",
|
|
|
|
|
+ ["status"] = "Статус"
|
|
|
|
|
+ }, new HashSet<string>());
|
|
|
|
|
+
|
|
|
|
|
+ if (data.Rows.Count == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ MessageBox.Show("За выбранную дату заказов не найдено.", "Поиск заказов", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ ErrorHandler.Show("Ошибка поиска", "Не удалось получить сведения о заказах покупателя за указанную дату.", ex);
|
|
|
|
|
+ }
|
|
|
|
|
+ finally
|
|
|
|
|
+ {
|
|
|
|
|
+ SetBusy(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void ExportReport()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_reportGrid.DataSource is not DataTable data || data.Rows.Count == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ MessageBox.Show("Сначала сформируйте отчёт.", "Сохранение отчёта", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ using var dialog = new SaveFileDialog
|
|
|
|
|
+ {
|
|
|
|
|
+ Title = "Сохранить отчёт",
|
|
|
|
|
+ Filter = "CSV-файл (*.csv)|*.csv",
|
|
|
|
|
+ FileName = $"orders_report_{DateTime.Now:yyyyMMdd_HHmm}.csv"
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ if (dialog.ShowDialog(this) != DialogResult.OK)
|
|
|
|
|
+ {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ var visibleColumns = _reportGrid.Columns
|
|
|
|
|
+ .Cast<DataGridViewColumn>()
|
|
|
|
|
+ .Where(c => c.Visible)
|
|
|
|
|
+ .OrderBy(c => c.DisplayIndex)
|
|
|
|
|
+ .ToList();
|
|
|
|
|
+
|
|
|
|
|
+ var lines = new List<string>();
|
|
|
|
|
+ lines.Add(string.Join(";", visibleColumns.Select(c => EscapeCsv(c.HeaderText))));
|
|
|
|
|
+
|
|
|
|
|
+ foreach (DataRow row in data.Rows)
|
|
|
|
|
+ {
|
|
|
|
|
+ var values = visibleColumns.Select(c =>
|
|
|
|
|
+ {
|
|
|
|
|
+ object value = row.Table.Columns.Contains(c.Name) ? row[c.Name] : string.Empty;
|
|
|
|
|
+ return EscapeCsv(value == DBNull.Value ? string.Empty : Convert.ToString(value) ?? string.Empty);
|
|
|
|
|
+ });
|
|
|
|
|
+ lines.Add(string.Join(";", values));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ File.WriteAllLines(dialog.FileName, lines, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true));
|
|
|
|
|
+ MessageBox.Show("Отчёт сохранён.", "Сохранение отчёта", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ ErrorHandler.Show("Ошибка сохранения отчёта", "Не удалось сохранить отчётный CSV-файл.", ex);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static string EscapeCsv(string value)
|
|
|
|
|
+ {
|
|
|
|
|
+ string escaped = value.Replace("\"", "\"\"");
|
|
|
|
|
+ return $"\"{escaped}\"";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private DataRowView? GetSelectedRow()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_dataGrid.CurrentRow?.DataBoundItem is DataRowView row)
|
|
|
|
|
+ {
|
|
|
|
|
+ return row;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ MessageBox.Show("Выберите запись в таблице.", "Выбор записи", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private bool EnsureAdmin()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_session.IsAdmin)
|
|
|
|
|
+ {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ MessageBox.Show("Изменять данные может только администратор.", "Ограничение доступа", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void ApplyGridSettings(DataGridView grid, Dictionary<string, string>? headers, HashSet<string>? hiddenColumns)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (grid == null || grid.Columns.Count == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ headers ??= new Dictionary<string, string>();
|
|
|
|
|
+ hiddenColumns ??= new HashSet<string>();
|
|
|
|
|
+
|
|
|
|
|
+ grid.AutoGenerateColumns = true;
|
|
|
|
|
+ grid.AllowUserToAddRows = false;
|
|
|
|
|
+ grid.AllowUserToDeleteRows = false;
|
|
|
|
|
+ grid.ReadOnly = true;
|
|
|
|
|
+ grid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
|
|
|
|
+ grid.MultiSelect = false;
|
|
|
|
|
+ AppTheme.StyleGrid(grid);
|
|
|
|
|
+
|
|
|
|
|
+ foreach (DataGridViewColumn column in grid.Columns)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (column == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ string columnName = column.Name ?? string.Empty;
|
|
|
|
|
+ if (headers.TryGetValue(columnName, out string? header) && !string.IsNullOrWhiteSpace(header))
|
|
|
|
|
+ {
|
|
|
|
|
+ column.HeaderText = header;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ bool hide = hiddenColumns.Contains(columnName);
|
|
|
|
|
+ column.Visible = !hide;
|
|
|
|
|
+
|
|
|
|
|
+ if (!hide)
|
|
|
|
|
+ {
|
|
|
|
|
+ string headerText = string.IsNullOrWhiteSpace(column.HeaderText) ? columnName : column.HeaderText;
|
|
|
|
|
+ column.MinimumWidth = Math.Max(95, headerText.Length * 8);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void UpdateActionButtons()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!_session.IsAdmin)
|
|
|
|
|
+ {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ bool canEdit = CurrentTable is { ReadOnly: false };
|
|
|
|
|
+ _addButton.Visible = canEdit;
|
|
|
|
|
+ _editButton.Visible = canEdit;
|
|
|
|
|
+ _deleteButton.Visible = canEdit;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void SetBusy(bool busy)
|
|
|
|
|
+ {
|
|
|
|
|
+ Cursor = busy ? Cursors.WaitCursor : Cursors.Default;
|
|
|
|
|
+ _refreshButton.Enabled = !busy;
|
|
|
|
|
+ _tableList.Enabled = !busy;
|
|
|
|
|
+ _reportFindButton.Enabled = !busy;
|
|
|
|
|
+ _reportExportButton.Enabled = !busy && _reportGrid.DataSource is DataTable reportData && reportData.Rows.Count > 0;
|
|
|
|
|
+ _reportCustomerCombo.Enabled = !busy && _session.IsAdmin && _reportCustomerCombo.Items.Count > 0;
|
|
|
|
|
+ _reportDatePicker.Enabled = !busy;
|
|
|
|
|
+ _reportSortCombo.Enabled = !busy;
|
|
|
|
|
+ _logoutButton.Enabled = !busy;
|
|
|
|
|
+
|
|
|
|
|
+ if (_session.IsAdmin)
|
|
|
|
|
+ {
|
|
|
|
|
+ bool canEdit = !busy && CurrentTable is { ReadOnly: false };
|
|
|
|
|
+ _addButton.Enabled = canEdit;
|
|
|
|
|
+ _editButton.Enabled = canEdit;
|
|
|
|
|
+ _deleteButton.Enabled = canEdit;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|