using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Data.SQLite; using System.Configuration; using System.IO; namespace WindowsFormsApp1 { // --- Форма авторизации (без изменений) --- public partial class Form1 : Form { // Путь к файлу SQLite private string connectionString = @"Data Source=EmployeeDB.db;Version=3;"; public Form1() { InitializeComponent(); this.AcceptButton = buttonLogin; this.Text = "Авторизация"; this.FormBorderStyle = FormBorderStyle.FixedDialog; this.MaximizeBox = false; this.StartPosition = FormStartPosition.CenterScreen; // Добавляем компоненты InitializeLoginComponents(); // Создаем базу данных, если она не существует 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() { try { if (!File.Exists("EmployeeDB.db")) { SQLiteConnection.CreateFile("EmployeeDB.db"); using (SQLiteConnection connection = new SQLiteConnection(connectionString)) { connection.Open(); // Создаем таблицу пользователей string createUsersTable = @"CREATE TABLE IF NOT EXISTS Users ( UserID INTEGER PRIMARY KEY AUTOINCREMENT, Login TEXT NOT NULL UNIQUE, Password TEXT NOT NULL)"; // Добавлено UNIQUE для Login using (SQLiteCommand command = new SQLiteCommand(createUsersTable, connection)) { command.ExecuteNonQuery(); } // Создаем admin пользователя (если его еще нет) string checkAdminExists = "SELECT COUNT(*) FROM Users WHERE Login = 'admin'"; using (SQLiteCommand checkCmd = new SQLiteCommand(checkAdminExists, connection)) { 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, LastName TEXT NOT NULL, FirstName TEXT NOT NULL, MiddleName TEXT, Position TEXT, Department TEXT)"; using (SQLiteCommand command = new SQLiteCommand(createEmployeesTable, connection)) { command.ExecuteNonQuery(); } // Создаем таблицу посещаемости string createAttendanceTable = @"CREATE TABLE IF NOT EXISTS Attendance ( AttendanceID INTEGER PRIMARY KEY AUTOINCREMENT, EmployeeID INTEGER NOT NULL, ArrivalTime TEXT NOT NULL, DepartureTime TEXT, 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) { MessageBox.Show("Ошибка инициализации базы данных: " + ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void InitializeLoginComponents() { // Заголовок Label lblHeader = new Label { Text = "Вход в систему учета посещаемости", Font = new Font("Arial", 14, FontStyle.Bold), AutoSize = true, TextAlign = ContentAlignment.MiddleCenter }; // Поле логина Label lblLogin = new Label { Text = "Логин:", AutoSize = true }; TextBox txtLogin = new TextBox { Name = "txtLogin", Width = 250 // Увеличиваем ширину }; txtLogin.KeyDown += TxtLogin_KeyDown; // Поле пароля Label lblPassword = new Label { Text = "Пароль:", AutoSize = true }; TextBox txtPassword = new TextBox { Name = "txtPassword", Width = 250, // Увеличиваем ширину PasswordChar = '*' }; txtPassword.KeyDown += TxtPassword_KeyDown; // Кнопка входа 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); this.Controls.Add(txtLogin); this.Controls.Add(lblPassword); this.Controls.Add(txtPassword); this.Controls.Add(buttonLogin); // Устанавливаем кнопку входа как кнопку по умолчанию для формы 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 = ""; 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 COLLATE NOCASE"; // Добавлено COLLATE NOCASE для регистронезависимого сравнения using (SQLiteCommand command = new SQLiteCommand(query, connection)) { command.Parameters.AddWithValue("@Login", login); command.Parameters.AddWithValue("@Password", password); // Пароль обычно чувствителен к регистру int count = Convert.ToInt32(command.ExecuteScalar()); if (count > 0) { // Скрываем текущую форму this.Hide(); // Открываем главную форму приложения MainForm mainForm = new MainForm(connectionString); mainForm.FormClosed += (s, args) => this.Close(); // Закрыть приложение при закрытии главной формы mainForm.Show(); } else { MessageBox.Show("Неверный логин или пароль!", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } } catch (Exception ex) { MessageBox.Show("Ошибка входа: " + ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } // --- Главная форма (с изменениями) --- public class MainForm : Form { private string connectionString; private TabControl tabControl; private TabPage tabEmployees; private TabPage tabAttendance; private DataGridView dgvEmployees; private DataGridView dgvAttendance; // Кнопки для сотрудников (доступны через поля класса для легкого доступа) private Button btnAddEmployee; private Button btnEditEmployee; private Button btnDeleteEmployee; public MainForm(string connString) { connectionString = connString; InitializeComponent(); LoadEmployeeData(); LoadAttendanceData(); // *** ИЗМЕНЕНИЕ: Установить вкладку "Посещаемость" по умолчанию *** if (tabControl.TabPages.Contains(tabAttendance)) { tabControl.SelectedTab = tabAttendance; } } private void InitializeComponent() { this.Text = "Система учета посещаемости"; this.Width = 800; this.Height = 600; this.StartPosition = FormStartPosition.CenterScreen; this.MinimumSize = new Size(600, 400); // Добавим минимальный размер // Создаем вкладки tabControl = new TabControl { Dock = DockStyle.Fill }; // Вкладка "Сотрудники" tabEmployees = new TabPage { Text = "Сотрудники" }; // Вкладка "Посещаемость" tabAttendance = new TabPage { Text = "Посещаемость" }; // --- Вкладка Сотрудники --- Panel panelEmployees = new Panel { Dock = DockStyle.Fill }; // Панель для содержимого вкладки tabEmployees.Controls.Add(panelEmployees); // Панель для кнопок сотрудников Panel panelEmployeesButtons = new Panel { Dock = DockStyle.Top, Height = 50, Padding = new Padding(10) // Отступы для кнопок }; panelEmployees.Controls.Add(panelEmployeesButtons); // Добавляем на панель содержимого // Кнопки для управления сотрудниками btnAddEmployee = new Button // Присваиваем полю класса { Text = "Добавить сотрудника", Width = 150, Location = new Point(10, 10), Anchor = AnchorStyles.Left | AnchorStyles.Top // Привязка к верху и левому краю }; btnAddEmployee.Click += BtnAddEmployee_Click; // Обработчик будет содержать проверку пароля btnEditEmployee = new Button // Присваиваем полю класса { Text = "Редактировать", Width = 150, Location = new Point(btnAddEmployee.Right + 10, 10), Anchor = AnchorStyles.Left | AnchorStyles.Top }; btnEditEmployee.Click += BtnEditEmployee_Click; // Обработчик будет содержать проверку пароля btnDeleteEmployee = new Button // Присваиваем полю класса { Text = "Удалить", Width = 150, Location = new Point(btnEditEmployee.Right + 10, 10), Anchor = AnchorStyles.Left | AnchorStyles.Top }; btnDeleteEmployee.Click += BtnDeleteEmployee_Click; // Обработчик будет содержать проверку пароля // Добавляем кнопки на панель кнопок сотрудников panelEmployeesButtons.Controls.Add(btnAddEmployee); panelEmployeesButtons.Controls.Add(btnEditEmployee); panelEmployeesButtons.Controls.Add(btnDeleteEmployee); // Таблица для сотрудников dgvEmployees = new DataGridView { AllowUserToAddRows = false, AllowUserToDeleteRows = false, ReadOnly = true, AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill, SelectionMode = DataGridViewSelectionMode.FullRowSelect, Dock = DockStyle.Fill, // Занимает оставшееся место на панели содержимого MultiSelect = false // Запретим множественный выбор }; panelEmployees.Controls.Add(dgvEmployees); // Добавляем на панель содержимого dgvEmployees.BringToFront(); // Убедимся, что таблица поверх панели кнопок // --- Вкладка Посещаемость --- Panel panelAttendance = new Panel { Dock = DockStyle.Fill }; // Панель для содержимого вкладки tabAttendance.Controls.Add(panelAttendance); // Панель для кнопок посещаемости Panel panelAttendanceButtons = new Panel { Dock = DockStyle.Top, Height = 50, Padding = new Padding(10) // Отступы для кнопок }; panelAttendance.Controls.Add(panelAttendanceButtons); // Добавляем на панель содержимого // Кнопки для управления посещаемостью Button btnArrival = new Button { Text = "Отметить прибытие", Width = 150, Location = new Point(10, 10), Anchor = AnchorStyles.Left | AnchorStyles.Top }; btnArrival.Click += BtnArrival_Click; Button btnDeparture = new Button { Text = "Отметить убытие", Width = 150, 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 { AllowUserToAddRows = false, AllowUserToDeleteRows = false, ReadOnly = true, AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill, SelectionMode = DataGridViewSelectionMode.FullRowSelect, Dock = DockStyle.Fill, // Занимает оставшееся место на панели содержимого MultiSelect = false // Запретим множественный выбор }; panelAttendance.Controls.Add(dgvAttendance); // Добавляем на панель содержимого dgvAttendance.BringToFront(); // Убедимся, что таблица поверх панели кнопок // Добавляем вкладки в контрол tabControl.TabPages.Add(tabEmployees); tabControl.TabPages.Add(tabAttendance); // Добавляем контрол вкладок на форму this.Controls.Add(tabControl); } private void LoadEmployeeData() { try { using (SQLiteConnection connection = new SQLiteConnection(connectionString)) { connection.Open(); 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 (dgvEmployees.Columns.Count > 0) // Проверяем наличие колонок { dgvEmployees.Columns["EmployeeID"].HeaderText = "ID"; dgvEmployees.Columns["LastName"].HeaderText = "Фамилия"; dgvEmployees.Columns["FirstName"].HeaderText = "Имя"; dgvEmployees.Columns["MiddleName"].HeaderText = "Отчество"; dgvEmployees.Columns["Position"].HeaderText = "Должность"; dgvEmployees.Columns["Department"].HeaderText = "Отдел"; // Скрыть ID, если не нужен пользователю dgvEmployees.Columns["EmployeeID"].Visible = false; } } } catch (Exception ex) { MessageBox.Show("Ошибка загрузки данных сотрудников: " + ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void LoadAttendanceData() { try { using (SQLiteConnection connection = new SQLiteConnection(connectionString)) { connection.Open(); // Изменен синтаксис конкатенации для SQLite 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"; // Сортировка по времени прибытия SQLiteDataAdapter adapter = new SQLiteDataAdapter(query, connection); DataTable dt = new DataTable(); adapter.Fill(dt); dgvAttendance.DataSource = dt; // Переименовываем колонки для отображения if (dgvAttendance.Columns.Count > 0) // Проверяем наличие колонок { dgvAttendance.Columns["AttendanceID"].HeaderText = "ID записи"; dgvAttendance.Columns["EmployeeID"].HeaderText = "ID сотрудника"; dgvAttendance.Columns["FullName"].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; } } } catch (Exception ex) { MessageBox.Show("Ошибка загрузки данных посещаемости: " + ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); } } // --- Обработчики событий для кнопок СОТРУДНИКОВ (с проверкой пароля) --- private void BtnAddEmployee_Click(object sender, EventArgs e) { // *** ИЗМЕНЕНИЕ: Проверка мастер-пароля *** if (VerifyMasterPassword()) { EmployeeForm form = new EmployeeForm(connectionString, 0); if (form.ShowDialog() == DialogResult.OK) { LoadEmployeeData(); // Обновляем сотрудников // Обновлять посещаемость не обязательно при добавлении нового сотрудника } } } private void BtnEditEmployee_Click(object sender, EventArgs e) { if (dgvEmployees.SelectedRows.Count > 0) { // *** ИЗМЕНЕНИЕ: Проверка мастер-пароля *** if (VerifyMasterPassword()) { 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 { MessageBox.Show("Выберите сотрудника для редактирования", "Информация", MessageBoxButtons.OK, MessageBoxIcon.Information); } } private void BtnDeleteEmployee_Click(object sender, EventArgs e) { if (dgvEmployees.SelectedRows.Count > 0) { // *** ИЗМЕНЕНИЕ: Проверка мастер-пароля *** if (VerifyMasterPassword()) { try { 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) { try { using (SQLiteConnection connection = new SQLiteConnection(connectionString)) { connection.Open(); // Используем PRAGMA foreign_keys = ON; при создании/открытии БД // и ON DELETE CASCADE в определении внешнего ключа. // Удаление записей посещаемости произойдет автоматически. // Удаляем сотрудника string deleteEmployee = "DELETE FROM Employees WHERE EmployeeID = @EmployeeID"; using (SQLiteCommand command = new SQLiteCommand(deleteEmployee, connection)) { command.Parameters.AddWithValue("@EmployeeID", employeeId); int deletedRows = command.ExecuteNonQuery(); if (deletedRows > 0) { MessageBox.Show("Сотрудник и связанные записи посещаемости успешно удалены", "Успех", MessageBoxButtons.OK, MessageBoxIcon.Information); LoadEmployeeData(); LoadAttendanceData(); } else { MessageBox.Show("Не удалось удалить сотрудника.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } } catch (Exception ex) { MessageBox.Show("Ошибка при удалении сотрудника: " + ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } catch (Exception ex) { MessageBox.Show("Ошибка получения ID сотрудника: " + ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); } } // конец блока проверки пароля } else { MessageBox.Show("Выберите сотрудника для удаления", "Информация", MessageBoxButtons.OK, MessageBoxIcon.Information); } } // --- Обработчики событий для кнопок ПОСЕЩАЕМОСТИ (без изменений, т.к. уже имели проверку пароля где надо) --- private void BtnArrival_Click(object sender, EventArgs e) { AttendanceForm form = new AttendanceForm(connectionString, true); if (form.ShowDialog() == DialogResult.OK) { LoadAttendanceData(); } } private void BtnDeparture_Click(object sender, EventArgs e) { AttendanceForm form = new AttendanceForm(connectionString, false); if (form.ShowDialog() == DialogResult.OK) { 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; private int employeeId; private TextBox txtLastName; private TextBox txtFirstName; private TextBox txtMiddleName; private TextBox txtPosition; private TextBox txtDepartment; public EmployeeForm(string connString, int id) { connectionString = connString; employeeId = id; InitializeComponent(); if (employeeId > 0) { this.Text = "Редактирование сотрудника"; LoadEmployeeData(); } else { this.Text = "Добавление нового сотрудника"; } } private void InitializeComponent() { this.Width = 400; this.Height = 300; this.StartPosition = FormStartPosition.CenterParent; this.FormBorderStyle = FormBorderStyle.FixedDialog; this.MaximizeBox = false; this.MinimizeBox = false; // Поля для ввода данных 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; // Растягиваем до правого края с отступом }; alignTextBox(lblLastName, txtLastName); alignTextBox(lblFirstName, txtFirstName); alignTextBox(lblMiddleName, txtMiddleName); alignTextBox(lblPosition, txtPosition); alignTextBox(lblDepartment, txtDepartment); // Кнопки Button btnSave = new Button { Text = "Сохранить", DialogResult = DialogResult.OK, // Устанавливаем сразу, обработчик может отменить закрытие Location = new Point(150, 180), Width = 100, Anchor = AnchorStyles.Bottom | AnchorStyles.Right // Привязка к правому нижнему углу }; btnSave.Click += BtnSave_Click; Button btnCancel = new Button { Text = "Отмена", 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); this.Controls.Add(txtLastName); this.Controls.Add(lblFirstName); this.Controls.Add(txtFirstName); this.Controls.Add(lblMiddleName); this.Controls.Add(txtMiddleName); this.Controls.Add(lblPosition); this.Controls.Add(txtPosition); this.Controls.Add(lblDepartment); this.Controls.Add(txtDepartment); 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 { using (SQLiteConnection connection = new SQLiteConnection(connectionString)) { connection.Open(); string query = "SELECT LastName, FirstName, MiddleName, Position, Department FROM Employees WHERE EmployeeID = @EmployeeID"; using (SQLiteCommand command = new SQLiteCommand(query, connection)) { command.Parameters.AddWithValue("@EmployeeID", employeeId); using (SQLiteDataReader reader = command.ExecuteReader()) { 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() ?? ""; } else { MessageBox.Show("Сотрудник с указанным ID не найден.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); this.Close(); // Закрыть форму, если данные не загружены } } } } } 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)) { 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; } try { using (SQLiteConnection connection = new SQLiteConnection(connectionString)) { connection.Open(); string query; if (employeeId > 0) { // Обновление существующего сотрудника 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) VALUES (@LastName, @FirstName, @MiddleName, @Position, @Department)"; } using (SQLiteCommand command = new SQLiteCommand(query, connection)) { // Используем 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); } 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; // Отменяем закрытие формы при ошибке } } } // --- Форма AttendanceForm (без существенных изменений) --- public class AttendanceForm : Form { private string connectionString; private bool isArrival; private ComboBox cmbEmployees; public AttendanceForm(string connString, bool arrival) { connectionString = connString; isArrival = arrival; InitializeComponent(); if (!LoadEmployees()) // Если сотрудники не загружены, закрываем форму { this.Load += (s, e) => this.Close(); // Запланировать закрытие после загрузки } this.Text = isArrival ? "Отметка прибытия" : "Отметка убытия"; } private void InitializeComponent() { this.Width = 400; this.Height = 200; this.StartPosition = FormStartPosition.CenterParent; this.FormBorderStyle = FormBorderStyle.FixedDialog; this.MaximizeBox = false; this.MinimizeBox = false; // Заголовок Label lblHeader = new Label { Text = isArrival ? "Отметка прибытия сотрудника" : "Отметка убытия сотрудника", Font = new Font("Arial", 12, FontStyle.Bold), 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, 65), // Немного ниже заголовка AutoSize = true // Авторазмер }; cmbEmployees = new ComboBox { 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, 120), // Ниже комбобокса 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(lblHeader); this.Controls.Add(lblEmployee); this.Controls.Add(cmbEmployees); this.Controls.Add(btnSave); this.Controls.Add(btnCancel); // Устанавливаем динамическую высоту this.ClientSize = new Size(400, btnSave.Bottom + 20); this.AcceptButton = btnSave; this.CancelButton = btnCancel; } 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.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; // Отменяем закрытие 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 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)) { checkCmd.Parameters.AddWithValue("@EmployeeID", selectedEmployeeId); int openArrivals = Convert.ToInt32(checkCmd.ExecuteScalar()); if (openArrivals > 0) { MessageBox.Show("У этого сотрудника уже есть незавершенная отметка о прибытии за сегодня.", "Предупреждение", MessageBoxButtons.OK, MessageBoxIcon.Warning); this.DialogResult = DialogResult.None; return; } } // Отметка прибытия - создаем новую запись 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; // Отменяем закрытие } } } // --- Форма 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; // Отменяем закрытие } } } }