From 7ad5a4d6c53a10172a2357ee333a9daa33b6af3b Mon Sep 17 00:00:00 2001 From: Professional Date: Mon, 14 Apr 2025 22:09:03 +0700 Subject: [PATCH] 1.0.0 --- Form1.Designer.cs | 13 +- Form1.cs | 1236 +++++++++++++++++++++++++++++++-------- WindowsFormsApp1.csproj | 41 ++ 3 files changed, 1028 insertions(+), 262 deletions(-) diff --git a/Form1.Designer.cs b/Form1.Designer.cs index a393283..61dc1d8 100644 --- a/Form1.Designer.cs +++ b/Form1.Designer.cs @@ -35,22 +35,23 @@ // // buttonLogin // - this.buttonLogin.Location = new System.Drawing.Point(778, 428); + this.buttonLogin.Location = new System.Drawing.Point(125, 130); this.buttonLogin.Name = "buttonLogin"; - this.buttonLogin.Size = new System.Drawing.Size(10, 10); + this.buttonLogin.Size = new System.Drawing.Size(100, 30); this.buttonLogin.TabIndex = 0; - this.buttonLogin.Text = "button1"; + this.buttonLogin.Text = "Войти"; this.buttonLogin.UseVisualStyleBackColor = true; + // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(800, 450); + this.ClientSize = new System.Drawing.Size(350, 200); this.Controls.Add(this.buttonLogin); this.Name = "Form1"; - this.Text = "Form1"; - this.ResumeLayout(false); + this.Text = "Авторизация"; + } diff --git a/Form1.cs b/Form1.cs index aae0781..866310d 100644 --- a/Form1.cs +++ b/Form1.cs @@ -13,6 +13,7 @@ using System.IO; namespace WindowsFormsApp1 { + // --- Форма авторизации (без изменений) --- public partial class Form1 : Form { // Путь к файлу SQLite @@ -32,6 +33,21 @@ namespace WindowsFormsApp1 // Создаем базу данных, если она не существует CreateDatabaseIfNotExists(); + + this.Load += Form1_Load; + } + + private void Form1_Load(object sender, EventArgs e) + { + // Используем BeginInvoke для гарантированной установки фокуса после загрузки формы + this.BeginInvoke(new Action(() => { + TextBox txtLogin = (TextBox)this.Controls["txtLogin"]; + if (txtLogin != null) // Добавлена проверка на null + { + txtLogin.BringToFront(); + txtLogin.Focus(); + } + })); } private void CreateDatabaseIfNotExists() @@ -48,20 +64,29 @@ namespace WindowsFormsApp1 // Создаем таблицу пользователей string createUsersTable = @"CREATE TABLE IF NOT EXISTS Users ( UserID INTEGER PRIMARY KEY AUTOINCREMENT, - Login TEXT NOT NULL, - Password TEXT NOT NULL)"; + Login TEXT NOT NULL UNIQUE, + Password TEXT NOT NULL)"; // Добавлено UNIQUE для Login using (SQLiteCommand command = new SQLiteCommand(createUsersTable, connection)) { command.ExecuteNonQuery(); } - // Создаем admin пользователя - string insertAdmin = @"INSERT INTO Users (Login, Password) VALUES ('admin', 'admin')"; - using (SQLiteCommand command = new SQLiteCommand(insertAdmin, connection)) + // Создаем admin пользователя (если его еще нет) + string checkAdminExists = "SELECT COUNT(*) FROM Users WHERE Login = 'admin'"; + using (SQLiteCommand checkCmd = new SQLiteCommand(checkAdminExists, connection)) { - command.ExecuteNonQuery(); + int adminCount = Convert.ToInt32(checkCmd.ExecuteScalar()); + if (adminCount == 0) + { + string insertAdmin = @"INSERT INTO Users (Login, Password) VALUES ('admin', 'admin')"; + using (SQLiteCommand command = new SQLiteCommand(insertAdmin, connection)) + { + command.ExecuteNonQuery(); + } + } } + // Создаем таблицу сотрудников string createEmployeesTable = @"CREATE TABLE IF NOT EXISTS Employees ( EmployeeID INTEGER PRIMARY KEY AUTOINCREMENT, @@ -81,12 +106,33 @@ namespace WindowsFormsApp1 EmployeeID INTEGER NOT NULL, ArrivalTime TEXT NOT NULL, DepartureTime TEXT, - FOREIGN KEY (EmployeeID) REFERENCES Employees(EmployeeID))"; + FOREIGN KEY (EmployeeID) REFERENCES Employees(EmployeeID) ON DELETE CASCADE)"; // Добавлено ON DELETE CASCADE using (SQLiteCommand command = new SQLiteCommand(createAttendanceTable, connection)) { command.ExecuteNonQuery(); } } + // Включить поддержку внешних ключей для текущего соединения + using (SQLiteConnection connection = new SQLiteConnection(connectionString)) + { + connection.Open(); + using (SQLiteCommand command = new SQLiteCommand("PRAGMA foreign_keys = ON;", connection)) + { + command.ExecuteNonQuery(); + } + } + } + else + { + // Включить поддержку внешних ключей при каждом запуске, если БД уже существует + using (SQLiteConnection connection = new SQLiteConnection(connectionString)) + { + connection.Open(); + using (SQLiteCommand command = new SQLiteCommand("PRAGMA foreign_keys = ON;", connection)) + { + command.ExecuteNonQuery(); + } + } } } catch (Exception ex) @@ -95,6 +141,7 @@ namespace WindowsFormsApp1 } } + private void InitializeLoginComponents() { // Заголовок @@ -102,52 +149,65 @@ namespace WindowsFormsApp1 { Text = "Вход в систему учета посещаемости", Font = new Font("Arial", 14, FontStyle.Bold), - Width = 350, - TextAlign = ContentAlignment.MiddleCenter, - Location = new Point(70, 20) + AutoSize = true, + TextAlign = ContentAlignment.MiddleCenter }; // Поле логина Label lblLogin = new Label { Text = "Логин:", - Location = new Point(70, 70), - Width = 100 + AutoSize = true }; TextBox txtLogin = new TextBox { Name = "txtLogin", - Location = new Point(170, 70), - Width = 200 + Width = 250 // Увеличиваем ширину }; + txtLogin.KeyDown += TxtLogin_KeyDown; // Поле пароля Label lblPassword = new Label { Text = "Пароль:", - Location = new Point(70, 100), - Width = 100 + AutoSize = true }; TextBox txtPassword = new TextBox { Name = "txtPassword", - Location = new Point(170, 100), - Width = 200, + Width = 250, // Увеличиваем ширину PasswordChar = '*' }; + txtPassword.KeyDown += TxtPassword_KeyDown; // Кнопка входа - Button buttonLogin = new Button - { - Name = "buttonLogin", - Text = "Войти", - Location = new Point(200, 150), - Width = 100 - }; + buttonLogin.Text = "Войти"; + buttonLogin.Width = 120; // Увеличиваем ширину кнопки + buttonLogin.Height = 35; // Увеличиваем высоту кнопки buttonLogin.Click += ButtonLogin_Click; + // Устанавливаем размеры формы (увеличиваем ширину) + this.ClientSize = new Size(500, 250); + + // Позиционирование заголовка + lblHeader.Location = new Point( + (this.ClientSize.Width - lblHeader.PreferredWidth) / 2, // Центрируем по ширине + 20 // Отступ сверху + ); + + lblLogin.Location = new Point(50, 80); // Сдвигаем метку логина + txtLogin.Location = new Point(lblLogin.Right + 20, lblLogin.Top - 3); // Располагаем текстбокс справа от метки + + lblPassword.Location = new Point(50, lblLogin.Bottom + 20); // Сдвигаем метку пароля + txtPassword.Location = new Point(lblPassword.Right + 20, lblPassword.Top - 3); // Располагаем текстбокс справа от метки + + buttonLogin.Location = new Point( + (this.ClientSize.Width - buttonLogin.Width) / 2, // Центрируем кнопку по ширине + txtPassword.Bottom + 30 // Отступ от текстбокса пароля + ); + // Добавляем все компоненты на форму this.Controls.Add(lblHeader); this.Controls.Add(lblLogin); @@ -156,38 +216,80 @@ namespace WindowsFormsApp1 this.Controls.Add(txtPassword); this.Controls.Add(buttonLogin); - // Устанавливаем размеры формы - this.Width = 480; - this.Height = 250; + // Устанавливаем кнопку входа как кнопку по умолчанию для формы + this.AcceptButton = buttonLogin; + } + + + + // Обработчик нажатия клавиш для поля логина + private void TxtLogin_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Enter) + { + // Предотвращаем звуковой сигнал при нажатии Enter + e.SuppressKeyPress = true; + + // Перемещаем фокус на поле пароля + Control txtPassword = this.Controls["txtPassword"]; + if (txtPassword != null) + txtPassword.Focus(); + } + } + + // Обработчик нажатия клавиш для поля пароля + private void TxtPassword_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Enter) + { + // Предотвращаем звуковой сигнал при нажатии Enter + e.SuppressKeyPress = true; + + // Вызываем метод входа в систему + ButtonLogin_Click(this, EventArgs.Empty); + } } private void ButtonLogin_Click(object sender, EventArgs e) { - string login = this.Controls["txtLogin"].Text; - string password = this.Controls["txtPassword"].Text; + string login = ""; + string password = ""; + + Control txtLoginCtrl = this.Controls["txtLogin"]; + Control txtPasswordCtrl = this.Controls["txtPassword"]; + + if (txtLoginCtrl != null) + login = txtLoginCtrl.Text; + if (txtPasswordCtrl != null) + password = txtPasswordCtrl.Text; + + if (string.IsNullOrWhiteSpace(login) || string.IsNullOrWhiteSpace(password)) + { + MessageBox.Show("Логин и пароль не могут быть пустыми!", "Предупреждение", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + try { using (SQLiteConnection connection = new SQLiteConnection(connectionString)) { connection.Open(); - string query = "SELECT COUNT(*) FROM Users WHERE Login = @Login AND Password = @Password"; + string query = "SELECT COUNT(*) FROM Users WHERE Login = @Login AND Password = @Password COLLATE NOCASE"; // Добавлено COLLATE NOCASE для регистронезависимого сравнения using (SQLiteCommand command = new SQLiteCommand(query, connection)) { command.Parameters.AddWithValue("@Login", login); - command.Parameters.AddWithValue("@Password", password); + command.Parameters.AddWithValue("@Password", password); // Пароль обычно чувствителен к регистру int count = Convert.ToInt32(command.ExecuteScalar()); if (count > 0) { - MessageBox.Show("Вход выполнен успешно!", "Успех", MessageBoxButtons.OK, MessageBoxIcon.Information); - // Скрываем текущую форму this.Hide(); // Открываем главную форму приложения MainForm mainForm = new MainForm(connectionString); - mainForm.FormClosed += (s, args) => this.Close(); + mainForm.FormClosed += (s, args) => this.Close(); // Закрыть приложение при закрытии главной формы mainForm.Show(); } else @@ -204,7 +306,7 @@ namespace WindowsFormsApp1 } } - // Форма для работы с таблицами + // --- Главная форма (с изменениями) --- public class MainForm : Form { private string connectionString; @@ -214,6 +316,11 @@ namespace WindowsFormsApp1 private DataGridView dgvEmployees; private DataGridView dgvAttendance; + // Кнопки для сотрудников (доступны через поля класса для легкого доступа) + private Button btnAddEmployee; + private Button btnEditEmployee; + private Button btnDeleteEmployee; + public MainForm(string connString) { connectionString = connString; @@ -221,6 +328,12 @@ namespace WindowsFormsApp1 InitializeComponent(); LoadEmployeeData(); LoadAttendanceData(); + + // *** ИЗМЕНЕНИЕ: Установить вкладку "Посещаемость" по умолчанию *** + if (tabControl.TabPages.Contains(tabAttendance)) + { + tabControl.SelectedTab = tabAttendance; + } } private void InitializeComponent() @@ -229,6 +342,7 @@ namespace WindowsFormsApp1 this.Width = 800; this.Height = 600; this.StartPosition = FormStartPosition.CenterScreen; + this.MinimumSize = new Size(600, 400); // Добавим минимальный размер // Создаем вкладки tabControl = new TabControl @@ -248,39 +362,48 @@ namespace WindowsFormsApp1 Text = "Посещаемость" }; + // --- Вкладка Сотрудники --- + Panel panelEmployees = new Panel { Dock = DockStyle.Fill }; // Панель для содержимого вкладки + tabEmployees.Controls.Add(panelEmployees); + // Панель для кнопок сотрудников Panel panelEmployeesButtons = new Panel { Dock = DockStyle.Top, - Height = 50 + Height = 50, + Padding = new Padding(10) // Отступы для кнопок }; + panelEmployees.Controls.Add(panelEmployeesButtons); // Добавляем на панель содержимого // Кнопки для управления сотрудниками - Button btnAddEmployee = new Button + btnAddEmployee = new Button // Присваиваем полю класса { Text = "Добавить сотрудника", Width = 150, - Location = new Point(10, 10) + Location = new Point(10, 10), + Anchor = AnchorStyles.Left | AnchorStyles.Top // Привязка к верху и левому краю }; - btnAddEmployee.Click += BtnAddEmployee_Click; + btnAddEmployee.Click += BtnAddEmployee_Click; // Обработчик будет содержать проверку пароля - Button btnEditEmployee = new Button + btnEditEmployee = new Button // Присваиваем полю класса { Text = "Редактировать", Width = 150, - Location = new Point(170, 10) + Location = new Point(btnAddEmployee.Right + 10, 10), + Anchor = AnchorStyles.Left | AnchorStyles.Top }; - btnEditEmployee.Click += BtnEditEmployee_Click; + btnEditEmployee.Click += BtnEditEmployee_Click; // Обработчик будет содержать проверку пароля - Button btnDeleteEmployee = new Button + btnDeleteEmployee = new Button // Присваиваем полю класса { Text = "Удалить", Width = 150, - Location = new Point(330, 10) + Location = new Point(btnEditEmployee.Right + 10, 10), + Anchor = AnchorStyles.Left | AnchorStyles.Top }; - btnDeleteEmployee.Click += BtnDeleteEmployee_Click; + btnDeleteEmployee.Click += BtnDeleteEmployee_Click; // Обработчик будет содержать проверку пароля - // Добавляем кнопки на панель + // Добавляем кнопки на панель кнопок сотрудников panelEmployeesButtons.Controls.Add(btnAddEmployee); panelEmployeesButtons.Controls.Add(btnEditEmployee); panelEmployeesButtons.Controls.Add(btnDeleteEmployee); @@ -293,27 +416,32 @@ namespace WindowsFormsApp1 ReadOnly = true, AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill, SelectionMode = DataGridViewSelectionMode.FullRowSelect, - Dock = DockStyle.Fill + Dock = DockStyle.Fill, // Занимает оставшееся место на панели содержимого + MultiSelect = false // Запретим множественный выбор }; + panelEmployees.Controls.Add(dgvEmployees); // Добавляем на панель содержимого + dgvEmployees.BringToFront(); // Убедимся, что таблица поверх панели кнопок - // ВАЖНО: сначала добавляем DataGridView, затем панель кнопок - // Это обеспечивает правильное расположение элементов сверху вниз - tabEmployees.Controls.Add(dgvEmployees); - tabEmployees.Controls.Add(panelEmployeesButtons); + // --- Вкладка Посещаемость --- + Panel panelAttendance = new Panel { Dock = DockStyle.Fill }; // Панель для содержимого вкладки + tabAttendance.Controls.Add(panelAttendance); // Панель для кнопок посещаемости Panel panelAttendanceButtons = new Panel { Dock = DockStyle.Top, - Height = 50 + Height = 50, + Padding = new Padding(10) // Отступы для кнопок }; + panelAttendance.Controls.Add(panelAttendanceButtons); // Добавляем на панель содержимого // Кнопки для управления посещаемостью Button btnArrival = new Button { Text = "Отметить прибытие", Width = 150, - Location = new Point(10, 10) + Location = new Point(10, 10), + Anchor = AnchorStyles.Left | AnchorStyles.Top }; btnArrival.Click += BtnArrival_Click; @@ -321,13 +449,35 @@ namespace WindowsFormsApp1 { Text = "Отметить убытие", Width = 150, - Location = new Point(170, 10) + Location = new Point(btnArrival.Right + 10, 10), + Anchor = AnchorStyles.Left | AnchorStyles.Top }; btnDeparture.Click += BtnDeparture_Click; - // Добавляем кнопки на панель + // Добавляем две новые кнопки для удаления и редактирования записей + Button btnDeleteAttendance = new Button + { + Text = "Удалить запись", + Width = 150, + Location = new Point(btnDeparture.Right + 10, 10), + Anchor = AnchorStyles.Left | AnchorStyles.Top + }; + btnDeleteAttendance.Click += BtnDeleteAttendance_Click; // В этом методе уже есть проверка пароля + + Button btnEditAttendance = new Button + { + Text = "Редактировать запись", + Width = 150, + Location = new Point(btnDeleteAttendance.Right + 10, 10), + Anchor = AnchorStyles.Left | AnchorStyles.Top + }; + btnEditAttendance.Click += BtnEditAttendance_Click; // В этом методе уже есть проверка пароля + + // Добавляем кнопки на панель кнопок посещаемости panelAttendanceButtons.Controls.Add(btnArrival); panelAttendanceButtons.Controls.Add(btnDeparture); + panelAttendanceButtons.Controls.Add(btnDeleteAttendance); + panelAttendanceButtons.Controls.Add(btnEditAttendance); // Таблица для посещаемости dgvAttendance = new DataGridView @@ -337,12 +487,11 @@ namespace WindowsFormsApp1 ReadOnly = true, AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill, SelectionMode = DataGridViewSelectionMode.FullRowSelect, - Dock = DockStyle.Fill + Dock = DockStyle.Fill, // Занимает оставшееся место на панели содержимого + MultiSelect = false // Запретим множественный выбор }; - - // ВАЖНО: сначала добавляем DataGridView, затем панель кнопок - tabAttendance.Controls.Add(dgvAttendance); - tabAttendance.Controls.Add(panelAttendanceButtons); + panelAttendance.Controls.Add(dgvAttendance); // Добавляем на панель содержимого + dgvAttendance.BringToFront(); // Убедимся, что таблица поверх панели кнопок // Добавляем вкладки в контрол tabControl.TabPages.Add(tabEmployees); @@ -352,7 +501,6 @@ namespace WindowsFormsApp1 this.Controls.Add(tabControl); } - private void LoadEmployeeData() { try @@ -360,14 +508,14 @@ namespace WindowsFormsApp1 using (SQLiteConnection connection = new SQLiteConnection(connectionString)) { connection.Open(); - string query = "SELECT EmployeeID, LastName, FirstName, MiddleName, Position, Department FROM Employees"; + string query = "SELECT EmployeeID, LastName, FirstName, MiddleName, Position, Department FROM Employees ORDER BY LastName, FirstName"; // Добавил сортировку SQLiteDataAdapter adapter = new SQLiteDataAdapter(query, connection); DataTable dt = new DataTable(); adapter.Fill(dt); dgvEmployees.DataSource = dt; // Переименовываем колонки для отображения - if (dt.Rows.Count > 0) + if (dgvEmployees.Columns.Count > 0) // Проверяем наличие колонок { dgvEmployees.Columns["EmployeeID"].HeaderText = "ID"; dgvEmployees.Columns["LastName"].HeaderText = "Фамилия"; @@ -375,6 +523,9 @@ namespace WindowsFormsApp1 dgvEmployees.Columns["MiddleName"].HeaderText = "Отчество"; dgvEmployees.Columns["Position"].HeaderText = "Должность"; dgvEmployees.Columns["Department"].HeaderText = "Отдел"; + + // Скрыть ID, если не нужен пользователю + dgvEmployees.Columns["EmployeeID"].Visible = false; } } } @@ -392,25 +543,34 @@ namespace WindowsFormsApp1 { connection.Open(); // Изменен синтаксис конкатенации для SQLite - string query = @"SELECT a.AttendanceID, e.EmployeeID, - e.LastName || ' ' || e.FirstName || ' ' || COALESCE(e.MiddleName, '') as FullName, - a.ArrivalTime, a.DepartureTime + string query = @"SELECT a.AttendanceID, e.EmployeeID, + e.LastName || ' ' || e.FirstName || ' ' || COALESCE(e.MiddleName, '') as FullName, + strftime('%Y-%m-%d %H:%M:%S', a.ArrivalTime) as ArrivalTimeFormatted, -- Форматируем дату/время + strftime('%Y-%m-%d %H:%M:%S', a.DepartureTime) as DepartureTimeFormatted -- Форматируем дату/время FROM Attendance a JOIN Employees e ON a.EmployeeID = e.EmployeeID - ORDER BY a.ArrivalTime DESC"; + ORDER BY a.ArrivalTime DESC"; // Сортировка по времени прибытия SQLiteDataAdapter adapter = new SQLiteDataAdapter(query, connection); DataTable dt = new DataTable(); adapter.Fill(dt); dgvAttendance.DataSource = dt; // Переименовываем колонки для отображения - if (dt.Rows.Count > 0) + if (dgvAttendance.Columns.Count > 0) // Проверяем наличие колонок { dgvAttendance.Columns["AttendanceID"].HeaderText = "ID записи"; dgvAttendance.Columns["EmployeeID"].HeaderText = "ID сотрудника"; dgvAttendance.Columns["FullName"].HeaderText = "ФИО"; - dgvAttendance.Columns["ArrivalTime"].HeaderText = "Время прибытия"; - dgvAttendance.Columns["DepartureTime"].HeaderText = "Время убытия"; + dgvAttendance.Columns["ArrivalTimeFormatted"].HeaderText = "Время прибытия"; + dgvAttendance.Columns["DepartureTimeFormatted"].HeaderText = "Время убытия"; + + // Скрыть ID, если не нужны пользователю + dgvAttendance.Columns["AttendanceID"].Visible = false; + dgvAttendance.Columns["EmployeeID"].Visible = false; + + // Настроить ширину колонки ФИО + dgvAttendance.Columns["FullName"].AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells; + dgvAttendance.Columns["FullName"].MinimumWidth = 150; } } } @@ -420,13 +580,18 @@ namespace WindowsFormsApp1 } } - // Обработчики событий для кнопок + // --- Обработчики событий для кнопок СОТРУДНИКОВ (с проверкой пароля) --- private void BtnAddEmployee_Click(object sender, EventArgs e) { - EmployeeForm form = new EmployeeForm(connectionString, 0); - if (form.ShowDialog() == DialogResult.OK) + // *** ИЗМЕНЕНИЕ: Проверка мастер-пароля *** + if (VerifyMasterPassword()) { - LoadEmployeeData(); + EmployeeForm form = new EmployeeForm(connectionString, 0); + if (form.ShowDialog() == DialogResult.OK) + { + LoadEmployeeData(); // Обновляем сотрудников + // Обновлять посещаемость не обязательно при добавлении нового сотрудника + } } } @@ -434,11 +599,23 @@ namespace WindowsFormsApp1 { if (dgvEmployees.SelectedRows.Count > 0) { - int employeeId = Convert.ToInt32(dgvEmployees.SelectedRows[0].Cells["EmployeeID"].Value); - EmployeeForm form = new EmployeeForm(connectionString, employeeId); - if (form.ShowDialog() == DialogResult.OK) + // *** ИЗМЕНЕНИЕ: Проверка мастер-пароля *** + if (VerifyMasterPassword()) { - LoadEmployeeData(); + try + { + int employeeId = Convert.ToInt32(dgvEmployees.SelectedRows[0].Cells["EmployeeID"].Value); + EmployeeForm form = new EmployeeForm(connectionString, employeeId); + if (form.ShowDialog() == DialogResult.OK) + { + LoadEmployeeData(); // Обновляем сотрудников + LoadAttendanceData(); // Обновляем посещаемость (ФИО могло измениться) + } + } + catch (Exception ex) + { + MessageBox.Show("Ошибка получения ID сотрудника: " + ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); + } } } else @@ -451,60 +628,58 @@ namespace WindowsFormsApp1 { if (dgvEmployees.SelectedRows.Count > 0) { - int employeeId = Convert.ToInt32(dgvEmployees.SelectedRows[0].Cells["EmployeeID"].Value); - string employeeName = dgvEmployees.SelectedRows[0].Cells["LastName"].Value.ToString() + " " + - dgvEmployees.SelectedRows[0].Cells["FirstName"].Value.ToString(); - - DialogResult result = MessageBox.Show($"Вы уверены, что хотите удалить сотрудника {employeeName}?", - "Подтверждение", MessageBoxButtons.YesNo, MessageBoxIcon.Question); - if (result == DialogResult.Yes) + // *** ИЗМЕНЕНИЕ: Проверка мастер-пароля *** + if (VerifyMasterPassword()) { try { - using (SQLiteConnection connection = new SQLiteConnection(connectionString)) + int employeeId = Convert.ToInt32(dgvEmployees.SelectedRows[0].Cells["EmployeeID"].Value); + string employeeName = dgvEmployees.SelectedRows[0].Cells["LastName"].Value.ToString() + " " + + dgvEmployees.SelectedRows[0].Cells["FirstName"].Value.ToString(); + + DialogResult result = MessageBox.Show($"Вы уверены, что хотите удалить сотрудника {employeeName}?\nВсе связанные записи посещаемости также будут удалены!", + "Подтверждение удаления", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); // Изменил иконку на Warning + if (result == DialogResult.Yes) { - connection.Open(); - // Начинаем транзакцию - using (SQLiteTransaction transaction = connection.BeginTransaction()) + try { - try + using (SQLiteConnection connection = new SQLiteConnection(connectionString)) { - // Сначала удаляем записи посещаемости - string deleteAttendance = "DELETE FROM Attendance WHERE EmployeeID = @EmployeeID"; - using (SQLiteCommand command = new SQLiteCommand(deleteAttendance, connection, transaction)) - { - command.Parameters.AddWithValue("@EmployeeID", employeeId); - command.ExecuteNonQuery(); - } + connection.Open(); + // Используем PRAGMA foreign_keys = ON; при создании/открытии БД + // и ON DELETE CASCADE в определении внешнего ключа. + // Удаление записей посещаемости произойдет автоматически. - // Затем удаляем сотрудника + // Удаляем сотрудника string deleteEmployee = "DELETE FROM Employees WHERE EmployeeID = @EmployeeID"; - using (SQLiteCommand command = new SQLiteCommand(deleteEmployee, connection, transaction)) + using (SQLiteCommand command = new SQLiteCommand(deleteEmployee, connection)) { command.Parameters.AddWithValue("@EmployeeID", employeeId); - command.ExecuteNonQuery(); + int deletedRows = command.ExecuteNonQuery(); + if (deletedRows > 0) + { + MessageBox.Show("Сотрудник и связанные записи посещаемости успешно удалены", "Успех", MessageBoxButtons.OK, MessageBoxIcon.Information); + LoadEmployeeData(); + LoadAttendanceData(); + } + else + { + MessageBox.Show("Не удалось удалить сотрудника.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); + } } - - // Фиксируем изменения - transaction.Commit(); - MessageBox.Show("Сотрудник успешно удален", "Успех", MessageBoxButtons.OK, MessageBoxIcon.Information); - LoadEmployeeData(); - LoadAttendanceData(); - } - catch (Exception ex) - { - // Откатываем изменения при ошибке - transaction.Rollback(); - throw ex; } } + catch (Exception ex) + { + MessageBox.Show("Ошибка при удалении сотрудника: " + ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); + } } } catch (Exception ex) { - MessageBox.Show("Ошибка при удалении сотрудника: " + ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); + MessageBox.Show("Ошибка получения ID сотрудника: " + ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); } - } + } // конец блока проверки пароля } else { @@ -512,6 +687,8 @@ namespace WindowsFormsApp1 } } + + // --- Обработчики событий для кнопок ПОСЕЩАЕМОСТИ (без изменений, т.к. уже имели проверку пароля где надо) --- private void BtnArrival_Click(object sender, EventArgs e) { AttendanceForm form = new AttendanceForm(connectionString, true); @@ -529,9 +706,104 @@ namespace WindowsFormsApp1 LoadAttendanceData(); } } + + // Добавляем метод для проверки мастер-пароля + private bool VerifyMasterPassword() + { + using (PasswordVerificationForm passwordForm = new PasswordVerificationForm()) + { + // Возвращаем true только если пользователь нажал OK и пароль верен + return passwordForm.ShowDialog() == DialogResult.OK; + } + // Если форма закрыта или нажата отмена, возвращаем false + // return false; // Это не нужно, т.к. ShowDialog вернет не OK + } + + // Обработчик для кнопки "Удалить запись" (уже содержит проверку пароля) + private void BtnDeleteAttendance_Click(object sender, EventArgs e) + { + // Проверяем, выбрана ли запись для удаления + if (dgvAttendance.SelectedRows.Count == 0) + { + MessageBox.Show("Выберите запись для удаления", "Информация", MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + // Запрашиваем мастер-пароль + if (VerifyMasterPassword()) + { + try + { + int attendanceId = Convert.ToInt32(dgvAttendance.SelectedRows[0].Cells["AttendanceID"].Value); + + DialogResult confirmResult = MessageBox.Show("Вы уверены, что хотите удалить выбранную запись?", + "Подтверждение удаления", MessageBoxButtons.YesNo, MessageBoxIcon.Question); + + if (confirmResult == DialogResult.Yes) + { + using (SQLiteConnection connection = new SQLiteConnection(connectionString)) + { + connection.Open(); + string deleteQuery = "DELETE FROM Attendance WHERE AttendanceID = @AttendanceID"; + + using (SQLiteCommand command = new SQLiteCommand(deleteQuery, connection)) + { + command.Parameters.AddWithValue("@AttendanceID", attendanceId); + int rowsAffected = command.ExecuteNonQuery(); // Проверяем, удалилось ли что-то + if (rowsAffected > 0) + { + LoadAttendanceData(); // Обновляем таблицу + MessageBox.Show("Запись успешно удалена.", "Успех", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + else + { + MessageBox.Show("Не удалось удалить запись.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } + } + } + catch (Exception ex) + { + MessageBox.Show("Ошибка при удалении записи: " + ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } + + // Обработчик для кнопки "Редактировать запись" (уже содержит проверку пароля) + private void BtnEditAttendance_Click(object sender, EventArgs e) + { + // Проверяем, выбрана ли запись для редактирования + if (dgvAttendance.SelectedRows.Count == 0) + { + MessageBox.Show("Выберите запись для редактирования", "Информация", MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + // Запрашиваем мастер-пароль + if (VerifyMasterPassword()) + { + try + { + int attendanceId = Convert.ToInt32(dgvAttendance.SelectedRows[0].Cells["AttendanceID"].Value); + int employeeId = Convert.ToInt32(dgvAttendance.SelectedRows[0].Cells["EmployeeID"].Value); + + // Открываем форму редактирования записи + AttendanceEditForm editForm = new AttendanceEditForm(connectionString, attendanceId, employeeId); + if (editForm.ShowDialog() == DialogResult.OK) + { + LoadAttendanceData(); // Обновляем таблицу после редактирования + } + } + catch (Exception ex) + { + MessageBox.Show("Ошибка при получении данных для редактирования: " + ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } } - // Форма для добавления/редактирования сотрудника + // --- Форма EmployeeForm (без существенных изменений) --- public class EmployeeForm : Form { private string connectionString; @@ -570,88 +842,55 @@ namespace WindowsFormsApp1 this.MinimizeBox = false; // Поля для ввода данных - Label lblLastName = new Label - { - Text = "Фамилия:", - Location = new Point(30, 20), - Width = 100 + Label lblLastName = new Label { Text = "Фамилия:", Location = new Point(30, 20), Width = 100, AutoSize = true }; + txtLastName = new TextBox { Location = new Point(150, 20), Width = 200, Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right }; + + Label lblFirstName = new Label { Text = "Имя:", Location = new Point(30, 50), Width = 100, AutoSize = true }; + txtFirstName = new TextBox { Location = new Point(150, 50), Width = 200, Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right }; + + Label lblMiddleName = new Label { Text = "Отчество:", Location = new Point(30, 80), Width = 100, AutoSize = true }; + txtMiddleName = new TextBox { Location = new Point(150, 80), Width = 200, Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right }; + + Label lblPosition = new Label { Text = "Должность:", Location = new Point(30, 110), Width = 100, AutoSize = true }; + txtPosition = new TextBox { Location = new Point(150, 110), Width = 200, Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right }; + + Label lblDepartment = new Label { Text = "Отдел:", Location = new Point(30, 140), Width = 100, AutoSize = true }; + txtDepartment = new TextBox { Location = new Point(150, 140), Width = 200, Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right }; + + // Позиционирование полей ввода относительно меток + Action alignTextBox = (lbl, txt) => { + txt.Location = new Point(lbl.Right + 10, lbl.Top - (txt.Height - lbl.Height) / 2); + txt.Width = this.ClientSize.Width - txt.Left - 30; // Растягиваем до правого края с отступом }; - txtLastName = new TextBox - { - Location = new Point(150, 20), - Width = 200 - }; - - Label lblFirstName = new Label - { - Text = "Имя:", - Location = new Point(30, 50), - Width = 100 - }; - - txtFirstName = new TextBox - { - Location = new Point(150, 50), - Width = 200 - }; - - Label lblMiddleName = new Label - { - Text = "Отчество:", - Location = new Point(30, 80), - Width = 100 - }; - - txtMiddleName = new TextBox - { - Location = new Point(150, 80), - Width = 200 - }; - - Label lblPosition = new Label - { - Text = "Должность:", - Location = new Point(30, 110), - Width = 100 - }; - - txtPosition = new TextBox - { - Location = new Point(150, 110), - Width = 200 - }; - - Label lblDepartment = new Label - { - Text = "Отдел:", - Location = new Point(30, 140), - Width = 100 - }; - - txtDepartment = new TextBox - { - Location = new Point(150, 140), - Width = 200 - }; + alignTextBox(lblLastName, txtLastName); + alignTextBox(lblFirstName, txtFirstName); + alignTextBox(lblMiddleName, txtMiddleName); + alignTextBox(lblPosition, txtPosition); + alignTextBox(lblDepartment, txtDepartment); // Кнопки Button btnSave = new Button { Text = "Сохранить", - DialogResult = DialogResult.OK, + DialogResult = DialogResult.OK, // Устанавливаем сразу, обработчик может отменить закрытие Location = new Point(150, 180), - Width = 100 + Width = 100, + Anchor = AnchorStyles.Bottom | AnchorStyles.Right // Привязка к правому нижнему углу }; btnSave.Click += BtnSave_Click; Button btnCancel = new Button { Text = "Отмена", - DialogResult = DialogResult.Cancel, - Location = new Point(260, 180), - Width = 100 + DialogResult = DialogResult.Cancel, // Закроет форму с результатом Cancel + Location = new Point(btnSave.Left - 110, btnSave.Top), // Левее кнопки Сохранить + Width = 100, + Anchor = AnchorStyles.Bottom | AnchorStyles.Right }; + btnCancel.Location = new Point(this.ClientSize.Width - btnCancel.Width - 20, this.ClientSize.Height - btnCancel.Height - 20); + btnSave.Location = new Point(btnCancel.Left - btnSave.Width - 10, btnCancel.Top); + // Добавляем компоненты на форму this.Controls.Add(lblLastName); @@ -667,10 +906,14 @@ namespace WindowsFormsApp1 this.Controls.Add(btnSave); this.Controls.Add(btnCancel); + // Устанавливаем динамическую высоту формы + this.ClientSize = new Size(400, btnSave.Bottom + 20); + this.AcceptButton = btnSave; this.CancelButton = btnCancel; } + private void LoadEmployeeData() { try @@ -686,11 +929,16 @@ namespace WindowsFormsApp1 { if (reader.Read()) { - txtLastName.Text = reader["LastName"].ToString(); - txtFirstName.Text = reader["FirstName"].ToString(); - txtMiddleName.Text = reader["MiddleName"].ToString(); - txtPosition.Text = reader["Position"].ToString(); - txtDepartment.Text = reader["Department"].ToString(); + txtLastName.Text = reader["LastName"]?.ToString() ?? ""; // Безопасное получение строк + txtFirstName.Text = reader["FirstName"]?.ToString() ?? ""; + txtMiddleName.Text = reader["MiddleName"]?.ToString() ?? ""; + txtPosition.Text = reader["Position"]?.ToString() ?? ""; + txtDepartment.Text = reader["Department"]?.ToString() ?? ""; + } + else + { + MessageBox.Show("Сотрудник с указанным ID не найден.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); + this.Close(); // Закрыть форму, если данные не загружены } } } @@ -699,15 +947,25 @@ namespace WindowsFormsApp1 catch (Exception ex) { MessageBox.Show("Ошибка при загрузке данных сотрудника: " + ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); + this.Close(); // Закрыть форму при ошибке } } private void BtnSave_Click(object sender, EventArgs e) { - if (string.IsNullOrWhiteSpace(txtLastName.Text) || string.IsNullOrWhiteSpace(txtFirstName.Text)) + // Валидация + if (string.IsNullOrWhiteSpace(txtLastName.Text)) { - MessageBox.Show("Фамилия и имя обязательны для заполнения", "Предупреждение", MessageBoxButtons.OK, MessageBoxIcon.Warning); - this.DialogResult = DialogResult.None; + MessageBox.Show("Поле 'Фамилия' обязательно для заполнения.", "Предупреждение", MessageBoxButtons.OK, MessageBoxIcon.Warning); + this.DialogResult = DialogResult.None; // Отменяем закрытие формы + txtLastName.Focus(); + return; + } + if (string.IsNullOrWhiteSpace(txtFirstName.Text)) + { + MessageBox.Show("Поле 'Имя' обязательно для заполнения.", "Предупреждение", MessageBoxButtons.OK, MessageBoxIcon.Warning); + this.DialogResult = DialogResult.None; // Отменяем закрытие формы + txtFirstName.Focus(); return; } @@ -721,47 +979,59 @@ namespace WindowsFormsApp1 if (employeeId > 0) { // Обновление существующего сотрудника - query = @"UPDATE Employees SET - LastName = @LastName, - FirstName = @FirstName, - MiddleName = @MiddleName, - Position = @Position, - Department = @Department + query = @"UPDATE Employees SET + LastName = @LastName, + FirstName = @FirstName, + MiddleName = @MiddleName, + Position = @Position, + Department = @Department WHERE EmployeeID = @EmployeeID"; } else { // Добавление нового сотрудника - query = @"INSERT INTO Employees (LastName, FirstName, MiddleName, Position, Department) + query = @"INSERT INTO Employees (LastName, FirstName, MiddleName, Position, Department) VALUES (@LastName, @FirstName, @MiddleName, @Position, @Department)"; } using (SQLiteCommand command = new SQLiteCommand(query, connection)) { - command.Parameters.AddWithValue("@LastName", txtLastName.Text); - command.Parameters.AddWithValue("@FirstName", txtFirstName.Text); - command.Parameters.AddWithValue("@MiddleName", txtMiddleName.Text); - command.Parameters.AddWithValue("@Position", txtPosition.Text); - command.Parameters.AddWithValue("@Department", txtDepartment.Text); + // Используем DBNull.Value для пустых необязательных полей + command.Parameters.AddWithValue("@LastName", txtLastName.Text.Trim()); + command.Parameters.AddWithValue("@FirstName", txtFirstName.Text.Trim()); + command.Parameters.AddWithValue("@MiddleName", string.IsNullOrWhiteSpace(txtMiddleName.Text) ? (object)DBNull.Value : txtMiddleName.Text.Trim()); + command.Parameters.AddWithValue("@Position", string.IsNullOrWhiteSpace(txtPosition.Text) ? (object)DBNull.Value : txtPosition.Text.Trim()); + command.Parameters.AddWithValue("@Department", string.IsNullOrWhiteSpace(txtDepartment.Text) ? (object)DBNull.Value : txtDepartment.Text.Trim()); + if (employeeId > 0) { command.Parameters.AddWithValue("@EmployeeID", employeeId); } - command.ExecuteNonQuery(); + int rowsAffected = command.ExecuteNonQuery(); + + if (rowsAffected > 0) + { + // Успешно сохранено, DialogResult остается OK (установлен на кнопке) + } + else + { + MessageBox.Show("Не удалось сохранить данные. Возможно, сотрудник был удален.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); + this.DialogResult = DialogResult.None; // Отменяем закрытие + } } } } catch (Exception ex) { MessageBox.Show("Ошибка при сохранении данных: " + ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); - this.DialogResult = DialogResult.None; + this.DialogResult = DialogResult.None; // Отменяем закрытие формы при ошибке } } } - // Форма для отметки посещаемости + // --- Форма AttendanceForm (без существенных изменений) --- public class AttendanceForm : Form { private string connectionString; @@ -774,7 +1044,10 @@ namespace WindowsFormsApp1 isArrival = arrival; InitializeComponent(); - LoadEmployees(); + if (!LoadEmployees()) // Если сотрудники не загружены, закрываем форму + { + this.Load += (s, e) => this.Close(); // Запланировать закрытие после загрузки + } this.Text = isArrival ? "Отметка прибытия" : "Отметка убытия"; } @@ -793,44 +1066,53 @@ namespace WindowsFormsApp1 { Text = isArrival ? "Отметка прибытия сотрудника" : "Отметка убытия сотрудника", Font = new Font("Arial", 12, FontStyle.Bold), - Location = new Point(50, 20), - Width = 300, - TextAlign = ContentAlignment.MiddleCenter + Location = new Point(10, 20), // Сдвинул влево + Width = this.ClientSize.Width - 20, // На всю ширину с отступами + TextAlign = ContentAlignment.MiddleCenter, + Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right // Привязка }; // Выбор сотрудника Label lblEmployee = new Label { Text = "Сотрудник:", - Location = new Point(30, 60), - Width = 100 + Location = new Point(30, 65), // Немного ниже заголовка + AutoSize = true // Авторазмер }; cmbEmployees = new ComboBox { - Location = new Point(130, 60), - Width = 220, - DropDownStyle = ComboBoxStyle.DropDownList + Location = new Point(lblEmployee.Right + 10, lblEmployee.Top - 3), // Выравнивание по вертикали + Width = 250, // Немного шире + DropDownStyle = ComboBoxStyle.DropDownList, + Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right // Привязка }; + cmbEmployees.Width = this.ClientSize.Width - cmbEmployees.Left - 30; // Растягивание // Кнопки Button btnSave = new Button { Text = "Отметить", - DialogResult = DialogResult.OK, - Location = new Point(130, 100), - Width = 100 + DialogResult = DialogResult.OK, // Устанавливаем сразу + Location = new Point(130, 120), // Ниже комбобокса + Width = 100, + Anchor = AnchorStyles.Bottom | AnchorStyles.Right // Привязка }; btnSave.Click += BtnSave_Click; Button btnCancel = new Button { Text = "Отмена", - DialogResult = DialogResult.Cancel, - Location = new Point(250, 100), - Width = 100 + DialogResult = DialogResult.Cancel, // Закроет форму + Location = new Point(btnSave.Left + 110, btnSave.Top), + Width = 100, + Anchor = AnchorStyles.Bottom | AnchorStyles.Right }; + // Позиционирование кнопок от правого нижнего угла + btnCancel.Location = new Point(this.ClientSize.Width - btnCancel.Width - 20, this.ClientSize.Height - btnCancel.Height - 20); + btnSave.Location = new Point(btnCancel.Left - btnSave.Width - 10, btnCancel.Top); + // Добавляем компоненты на форму this.Controls.Add(lblHeader); this.Controls.Add(lblEmployee); @@ -838,92 +1120,534 @@ namespace WindowsFormsApp1 this.Controls.Add(btnSave); this.Controls.Add(btnCancel); + // Устанавливаем динамическую высоту + this.ClientSize = new Size(400, btnSave.Bottom + 20); + + this.AcceptButton = btnSave; this.CancelButton = btnCancel; } - private void LoadEmployees() + + private bool LoadEmployees() { try { using (SQLiteConnection connection = new SQLiteConnection(connectionString)) { connection.Open(); - string query = "SELECT EmployeeID, LastName || ' ' || FirstName || ' ' || COALESCE(MiddleName, '') as FullName FROM Employees ORDER BY LastName, FirstName"; + string query = "SELECT EmployeeID, LastName || ' ' || FirstName || COALESCE(' ' || MiddleName, '') as FullName FROM Employees ORDER BY LastName, FirstName"; SQLiteDataAdapter adapter = new SQLiteDataAdapter(query, connection); DataTable dt = new DataTable(); adapter.Fill(dt); - cmbEmployees.DataSource = dt; - cmbEmployees.DisplayMember = "FullName"; - cmbEmployees.ValueMember = "EmployeeID"; + if (dt.Rows.Count > 0) + { + cmbEmployees.DataSource = dt; + cmbEmployees.DisplayMember = "FullName"; + cmbEmployees.ValueMember = "EmployeeID"; + cmbEmployees.SelectedIndex = -1; // Сбросить выбор по умолчанию + return true; // Сотрудники загружены + } + else + { + MessageBox.Show("Нет сотрудников для выбора. Сначала добавьте сотрудников.", "Информация", MessageBoxButtons.OK, MessageBoxIcon.Information); + return false; // Сотрудников нет + } } } catch (Exception ex) { MessageBox.Show("Ошибка при загрузке списка сотрудников: " + ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); + return false; // Ошибка загрузки } } + private void BtnSave_Click(object sender, EventArgs e) { if (cmbEmployees.SelectedValue == null) { MessageBox.Show("Необходимо выбрать сотрудника", "Предупреждение", MessageBoxButtons.OK, MessageBoxIcon.Warning); - this.DialogResult = DialogResult.None; + this.DialogResult = DialogResult.None; // Отменяем закрытие + cmbEmployees.Focus(); return; } + object selectedEmployeeId = cmbEmployees.SelectedValue; + try { using (SQLiteConnection connection = new SQLiteConnection(connectionString)) { connection.Open(); + string currentTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); if (isArrival) { - // Отметка прибытия - создаем новую запись - string query = "INSERT INTO Attendance (EmployeeID, ArrivalTime) VALUES (@EmployeeID, @ArrivalTime)"; - using (SQLiteCommand command = new SQLiteCommand(query, connection)) + // Проверка, нет ли уже незакрытой отметки о прибытии для этого сотрудника сегодня + string checkQuery = @"SELECT COUNT(*) FROM Attendance + WHERE EmployeeID = @EmployeeID + AND date(ArrivalTime) = date('now', 'localtime') + AND DepartureTime IS NULL"; + using (SQLiteCommand checkCmd = new SQLiteCommand(checkQuery, connection)) { - command.Parameters.AddWithValue("@EmployeeID", cmbEmployees.SelectedValue); - command.Parameters.AddWithValue("@ArrivalTime", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); - command.ExecuteNonQuery(); - } - } - else - { - // Отметка убытия - ищем последнюю запись с прибытием для данного сотрудника - string query = @"UPDATE Attendance SET DepartureTime = @DepartureTime - WHERE EmployeeID = @EmployeeID AND DepartureTime IS NULL - AND AttendanceID = (SELECT AttendanceID FROM Attendance - WHERE EmployeeID = @EmployeeID AND DepartureTime IS NULL - ORDER BY ArrivalTime DESC LIMIT 1)"; - using (SQLiteCommand command = new SQLiteCommand(query, connection)) - { - command.Parameters.AddWithValue("@EmployeeID", cmbEmployees.SelectedValue); - command.Parameters.AddWithValue("@DepartureTime", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); - int rowsAffected = command.ExecuteNonQuery(); - - if (rowsAffected == 0) + checkCmd.Parameters.AddWithValue("@EmployeeID", selectedEmployeeId); + int openArrivals = Convert.ToInt32(checkCmd.ExecuteScalar()); + if (openArrivals > 0) { - MessageBox.Show("Для данного сотрудника нет открытых записей о прибытии", - "Информация", MessageBoxButtons.OK, MessageBoxIcon.Information); + MessageBox.Show("У этого сотрудника уже есть незавершенная отметка о прибытии за сегодня.", "Предупреждение", MessageBoxButtons.OK, MessageBoxIcon.Warning); this.DialogResult = DialogResult.None; return; } } - } - MessageBox.Show("Посещаемость успешно отмечена", "Успех", MessageBoxButtons.OK, MessageBoxIcon.Information); + // Отметка прибытия - создаем новую запись + string query = "INSERT INTO Attendance (EmployeeID, ArrivalTime) VALUES (@EmployeeID, @ArrivalTime)"; + using (SQLiteCommand command = new SQLiteCommand(query, connection)) + { + command.Parameters.AddWithValue("@EmployeeID", selectedEmployeeId); + command.Parameters.AddWithValue("@ArrivalTime", currentTime); + command.ExecuteNonQuery(); + } + } + else // Отметка убытия + { + // Отметка убытия - ищем последнюю запись с прибытием без убытия для данного сотрудника + string query = @"UPDATE Attendance SET DepartureTime = @DepartureTime + WHERE EmployeeID = @EmployeeID AND DepartureTime IS NULL + AND AttendanceID = (SELECT AttendanceID FROM Attendance + WHERE EmployeeID = @EmployeeID AND DepartureTime IS NULL + ORDER BY ArrivalTime DESC LIMIT 1)"; + using (SQLiteCommand command = new SQLiteCommand(query, connection)) + { + command.Parameters.AddWithValue("@EmployeeID", selectedEmployeeId); + command.Parameters.AddWithValue("@DepartureTime", currentTime); + int rowsAffected = command.ExecuteNonQuery(); + + if (rowsAffected == 0) + { + MessageBox.Show("Для данного сотрудника нет открытых записей о прибытии, для которых можно отметить убытие.", + "Информация", MessageBoxButtons.OK, MessageBoxIcon.Information); + this.DialogResult = DialogResult.None; // Отменяем закрытие + return; + } + } + } + // Если дошли сюда, значит все ок, DialogResult остается OK } } catch (Exception ex) { MessageBox.Show("Ошибка при сохранении данных: " + ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); - this.DialogResult = DialogResult.None; + this.DialogResult = DialogResult.None; // Отменяем закрытие + } + } + + } + + // --- Форма PasswordVerificationForm (без изменений) --- + public class PasswordVerificationForm : Form + { + private TextBox txtPassword; + + public PasswordVerificationForm() + { + InitializeComponent(); + // Установить фокус на поле пароля при открытии + this.Load += (s, e) => { txtPassword.Focus(); }; + } + + private void InitializeComponent() + { + this.Width = 330; + this.Height = 150; + this.FormBorderStyle = FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.StartPosition = FormStartPosition.CenterParent; + this.Text = "Проверка доступа"; + + Label lblPassword = new Label + { + Text = "Введите мастер-пароль:", + Location = new Point(20, 20), + AutoSize = true // Авторазмер + }; + + txtPassword = new TextBox + { + Location = new Point(20, lblPassword.Bottom + 5), // Позиция под меткой + Width = this.ClientSize.Width - 40, // Ширина с отступами + PasswordChar = '*', + Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right // Привязка + }; + + Button btnOK = new Button + { + Text = "ОК", + DialogResult = DialogResult.OK, // Устанавливаем заранее + Location = new Point(120, 80), + Width = 80, + Anchor = AnchorStyles.Bottom | AnchorStyles.Right // Привязка + }; + btnOK.Click += BtnOK_Click; // Проверка пароля в обработчике + + Button btnCancel = new Button + { + Text = "Отмена", + DialogResult = DialogResult.Cancel, // Закроет форму + Location = new Point(220, 80), + Width = 80, + Anchor = AnchorStyles.Bottom | AnchorStyles.Right + }; + + // Позиционирование кнопок + btnCancel.Location = new Point(this.ClientSize.Width - btnCancel.Width - 20, this.ClientSize.Height - btnCancel.Height - 20); + btnOK.Location = new Point(btnCancel.Left - btnOK.Width - 10, btnCancel.Top); + + this.Controls.Add(lblPassword); + this.Controls.Add(txtPassword); + this.Controls.Add(btnOK); + this.Controls.Add(btnCancel); + + // Устанавливаем высоту динамически + this.ClientSize = new Size(315, btnOK.Bottom + 20); + + + this.AcceptButton = btnOK; + this.CancelButton = btnCancel; + } + + // --- ИЗМЕНЕННЫЙ мастер-пароль (для примера) --- + private const string MasterPassword = "1234321"; // Лучше вынести в конфигурацию или другое безопасное место + + private void BtnOK_Click(object sender, EventArgs e) + { + if (txtPassword.Text == MasterPassword) // Сравнение с константой + { + // Пароль верный, DialogResult уже OK, форма закроется + } + else + { + MessageBox.Show("Неверный пароль!", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); + this.DialogResult = DialogResult.None; // Отменяем закрытие формы + txtPassword.Clear(); + txtPassword.Focus(); } } } -} + // --- Форма AttendanceEditForm (без существенных изменений) --- + public class AttendanceEditForm : Form + { + private string connectionString; + private int attendanceId; + private int initialEmployeeId; // Сохраняем ID сотрудника, который был изначально + private ComboBox cmbEmployees; + private DateTimePicker dtpArrivalDate; + private DateTimePicker dtpArrivalTime; + private DateTimePicker dtpDepartureDate; + private DateTimePicker dtpDepartureTime; + private CheckBox chkHasDeparture; + + public AttendanceEditForm(string connString, int attId, int empId) + { + connectionString = connString; + attendanceId = attId; + initialEmployeeId = empId; + + InitializeComponent(); + if (!LoadEmployees()) // Если сотрудники не загружены, закрываем форму + { + this.Load += (s, e) => this.Close(); + return; // Прерываем дальнейшую инициализацию + } + if (!LoadAttendanceData()) // Если запись не загружена, закрываем форму + { + this.Load += (s, e) => this.Close(); + } + } + + private void InitializeComponent() + { + this.Width = 450; + this.Height = 300; + this.StartPosition = FormStartPosition.CenterParent; + this.FormBorderStyle = FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Text = "Редактирование записи посещаемости"; + + int currentY = 20; + int labelWidth = 120; + int controlLeft = labelWidth + 30; + int controlWidth = this.ClientSize.Width - controlLeft - 20; + + // Сотрудник + Label lblEmployee = new Label { Text = "Сотрудник:", Location = new Point(20, currentY), Width = labelWidth, AutoSize = true }; + cmbEmployees = new ComboBox { Location = new Point(controlLeft, lblEmployee.Top - 3), Width = controlWidth, DropDownStyle = ComboBoxStyle.DropDownList, Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right }; + currentY = cmbEmployees.Bottom + 15; + + // Время прибытия + Label lblArrival = new Label { Text = "Время прибытия:", Location = new Point(20, currentY), Width = labelWidth, AutoSize = true }; + dtpArrivalDate = new DateTimePicker { Location = new Point(controlLeft, lblArrival.Top - 3), Width = 120, Format = DateTimePickerFormat.Short, Anchor = AnchorStyles.Top | AnchorStyles.Left }; + dtpArrivalTime = new DateTimePicker { Location = new Point(dtpArrivalDate.Right + 10, dtpArrivalDate.Top), Width = 120, Format = DateTimePickerFormat.Time, ShowUpDown = true, Anchor = AnchorStyles.Top | AnchorStyles.Left }; + currentY = dtpArrivalDate.Bottom + 15; + + // Время убытия + chkHasDeparture = new CheckBox { Text = "Есть время убытия", Location = new Point(20, currentY), Width = labelWidth + 50, AutoSize = true, Anchor = AnchorStyles.Top | AnchorStyles.Left }; + chkHasDeparture.CheckedChanged += ChkHasDeparture_CheckedChanged; + currentY = chkHasDeparture.Bottom + 5; + + dtpDepartureDate = new DateTimePicker { Location = new Point(controlLeft, currentY), Width = 120, Format = DateTimePickerFormat.Short, Enabled = false, Anchor = AnchorStyles.Top | AnchorStyles.Left }; + dtpDepartureTime = new DateTimePicker { Location = new Point(dtpDepartureDate.Right + 10, dtpDepartureDate.Top), Width = 120, Format = DateTimePickerFormat.Time, ShowUpDown = true, Enabled = false, Anchor = AnchorStyles.Top | AnchorStyles.Left }; + currentY = dtpDepartureDate.Bottom + 25; // Больше отступ перед кнопками + + // Кнопки + Button btnSave = new Button + { + Text = "Сохранить", + DialogResult = DialogResult.OK, // Устанавливаем сразу + Location = new Point(150, currentY), + Width = 100, + Anchor = AnchorStyles.Bottom | AnchorStyles.Right + }; + btnSave.Click += BtnSave_Click; + + Button btnCancel = new Button + { + Text = "Отмена", + DialogResult = DialogResult.Cancel, // Закроет форму + Location = new Point(btnSave.Left + 110, btnSave.Top), + Width = 100, + Anchor = AnchorStyles.Bottom | AnchorStyles.Right + }; + + // Позиционирование кнопок + btnCancel.Location = new Point(this.ClientSize.Width - btnCancel.Width - 20, this.ClientSize.Height - btnCancel.Height - 20); + btnSave.Location = new Point(btnCancel.Left - btnSave.Width - 10, btnCancel.Top); + + // Добавляем элементы на форму + this.Controls.Add(lblEmployee); + this.Controls.Add(cmbEmployees); + this.Controls.Add(lblArrival); + this.Controls.Add(dtpArrivalDate); + this.Controls.Add(dtpArrivalTime); + this.Controls.Add(chkHasDeparture); + this.Controls.Add(dtpDepartureDate); + this.Controls.Add(dtpDepartureTime); + this.Controls.Add(btnSave); + this.Controls.Add(btnCancel); + + // Устанавливаем высоту формы динамически + this.ClientSize = new Size(435, btnSave.Bottom + 20); + + this.AcceptButton = btnSave; + this.CancelButton = btnCancel; + } + + + private void ChkHasDeparture_CheckedChanged(object sender, EventArgs e) + { + dtpDepartureDate.Enabled = chkHasDeparture.Checked; + dtpDepartureTime.Enabled = chkHasDeparture.Checked; + // Если снимаем галочку, можно сбросить время убытия на время прибытия для удобства + if (!chkHasDeparture.Checked) + { + dtpDepartureDate.Value = dtpArrivalDate.Value; + dtpDepartureTime.Value = dtpArrivalTime.Value; + } + } + + private bool LoadEmployees() + { + try + { + using (SQLiteConnection connection = new SQLiteConnection(connectionString)) + { + connection.Open(); + string query = "SELECT EmployeeID, LastName || ' ' || FirstName || COALESCE(' ' || MiddleName, '') as FullName FROM Employees ORDER BY LastName, FirstName"; + SQLiteDataAdapter adapter = new SQLiteDataAdapter(query, connection); + DataTable dt = new DataTable(); + adapter.Fill(dt); + + if (dt.Rows.Count > 0) + { + cmbEmployees.DataSource = dt; + cmbEmployees.DisplayMember = "FullName"; + cmbEmployees.ValueMember = "EmployeeID"; + + // Устанавливаем текущего сотрудника + cmbEmployees.SelectedValue = initialEmployeeId; + // Проверка, что значение установилось + if (cmbEmployees.SelectedValue == null || (int)cmbEmployees.SelectedValue != initialEmployeeId) + { + MessageBox.Show("Не удалось выбрать исходного сотрудника в списке. Возможно, он был удален.", "Предупреждение", MessageBoxButtons.OK, MessageBoxIcon.Warning); + // Можно либо выбрать первого попавшегося, либо закрыть форму + if (dt.Rows.Count > 0) cmbEmployees.SelectedIndex = 0; + else return false; // Нет сотрудников вообще + } + return true; + } + else + { + MessageBox.Show("Нет сотрудников для выбора.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); + return false; + } + } + } + catch (Exception ex) + { + MessageBox.Show("Ошибка при загрузке списка сотрудников: " + ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); + return false; + } + } + + + private bool LoadAttendanceData() + { + try + { + using (SQLiteConnection connection = new SQLiteConnection(connectionString)) + { + connection.Open(); + string query = "SELECT ArrivalTime, DepartureTime FROM Attendance WHERE AttendanceID = @AttendanceID"; + using (SQLiteCommand command = new SQLiteCommand(query, connection)) + { + command.Parameters.AddWithValue("@AttendanceID", attendanceId); + using (SQLiteDataReader reader = command.ExecuteReader()) + { + if (reader.Read()) + { + // Устанавливаем время прибытия + if (DateTime.TryParse(reader["ArrivalTime"]?.ToString(), out DateTime arrivalTime)) + { + dtpArrivalDate.Value = arrivalTime.Date; + dtpArrivalTime.Value = arrivalTime; // Устанавливаем полное время + } + else + { + MessageBox.Show("Некорректный формат времени прибытия в базе данных.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); + return false; // Не можем загрузить данные + } + + + // Устанавливаем время убытия, если оно есть + if (!reader.IsDBNull(reader.GetOrdinal("DepartureTime"))) + { + if (DateTime.TryParse(reader["DepartureTime"]?.ToString(), out DateTime departureTime)) + { + dtpDepartureDate.Value = departureTime.Date; + dtpDepartureTime.Value = departureTime; // Устанавливаем полное время + chkHasDeparture.Checked = true; // Включаем чекбокс и связанные поля + } + else + { + // Не критично, просто не установим время убытия + chkHasDeparture.Checked = false; + } + } + else + { + // Если время убытия NULL, чекбокс не отмечен, поля деактивированы + chkHasDeparture.Checked = false; + // Установим время убытия равным времени прибытия по умолчанию + dtpDepartureDate.Value = dtpArrivalDate.Value; + dtpDepartureTime.Value = dtpArrivalTime.Value; + } + return true; // Данные загружены + } + else + { + MessageBox.Show("Запись посещаемости с указанным ID не найдена.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); + return false; // Запись не найдена + } + } + } + } + } + catch (Exception ex) + { + MessageBox.Show("Ошибка при загрузке данных записи: " + ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); + return false; // Ошибка загрузки + } + } + + + private void BtnSave_Click(object sender, EventArgs e) + { + if (cmbEmployees.SelectedValue == null) + { + MessageBox.Show("Необходимо выбрать сотрудника.", "Предупреждение", MessageBoxButtons.OK, MessageBoxIcon.Warning); + this.DialogResult = DialogResult.None; + cmbEmployees.Focus(); + return; + } + + try + { + object selectedEmployeeId = cmbEmployees.SelectedValue; + + // Комбинируем дату и время для получения полного времени прибытия + DateTime arrivalDateTime = dtpArrivalDate.Value.Date.Add(dtpArrivalTime.Value.TimeOfDay); + DateTime? departureDateTime = null; // nullable DateTime + + if (chkHasDeparture.Checked) + { + // Комбинируем дату и время убытия + departureDateTime = dtpDepartureDate.Value.Date.Add(dtpDepartureTime.Value.TimeOfDay); + + // Валидация: время убытия не может быть раньше времени прибытия + if (departureDateTime.Value < arrivalDateTime) + { + MessageBox.Show("Время убытия не может быть раньше времени прибытия.", "Ошибка валидации", MessageBoxButtons.OK, MessageBoxIcon.Warning); + this.DialogResult = DialogResult.None; // Отменяем закрытие + dtpDepartureTime.Focus(); + return; + } + } + + + using (SQLiteConnection connection = new SQLiteConnection(connectionString)) + { + connection.Open(); + + string query = @"UPDATE Attendance SET + EmployeeID = @EmployeeID, + ArrivalTime = @ArrivalTime, + DepartureTime = @DepartureTime + WHERE AttendanceID = @AttendanceID"; + + using (SQLiteCommand command = new SQLiteCommand(query, connection)) + { + command.Parameters.AddWithValue("@EmployeeID", selectedEmployeeId); + command.Parameters.AddWithValue("@ArrivalTime", arrivalDateTime.ToString("yyyy-MM-dd HH:mm:ss")); + + // Используем DBNull.Value если время убытия не установлено + command.Parameters.AddWithValue("@DepartureTime", departureDateTime.HasValue ? (object)departureDateTime.Value.ToString("yyyy-MM-dd HH:mm:ss") : DBNull.Value); + + command.Parameters.AddWithValue("@AttendanceID", attendanceId); + int rowsAffected = command.ExecuteNonQuery(); + + if (rowsAffected > 0) + { + // Успешно, DialogResult остается OK + } + else + { + MessageBox.Show("Не удалось обновить запись. Возможно, она была удалена.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); + this.DialogResult = DialogResult.None; // Отменяем закрытие + } + } + } + } + catch (Exception ex) + { + MessageBox.Show("Ошибка при сохранении данных: " + ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); + this.DialogResult = DialogResult.None; // Отменяем закрытие + } + } + + } +} \ No newline at end of file diff --git a/WindowsFormsApp1.csproj b/WindowsFormsApp1.csproj index 5756105..a44315c 100644 --- a/WindowsFormsApp1.csproj +++ b/WindowsFormsApp1.csproj @@ -15,6 +15,22 @@ true + false + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 1 + 1.0.0.%2a + false + true + true AnyCPU @@ -35,6 +51,18 @@ prompt 4 + + 27892327985E3CFAC70BBC1BF9768591AA583D3F + + + WindowsFormsApp1_TemporaryKey.pfx + + + true + + + true + packages\EntityFramework.6.4.4\lib\net45\EntityFramework.dll @@ -95,10 +123,23 @@ Settings.settings True + + + + False + Microsoft .NET Framework 4.7.2 %28x86 и x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + +