|
@@ -0,0 +1,799 @@
|
|
|
|
|
+using System;
|
|
|
|
|
+using System.Collections.Generic;
|
|
|
|
|
+using System.ComponentModel;
|
|
|
|
|
+using System.Data;
|
|
|
|
|
+using System.Drawing;
|
|
|
|
|
+using System.IO;
|
|
|
|
|
+using System.Text;
|
|
|
|
|
+using System.Windows.Forms;
|
|
|
|
|
+using Npgsql;
|
|
|
|
|
+using NpgsqlTypes;
|
|
|
|
|
+
|
|
|
|
|
+namespace SimpleDBViewer
|
|
|
|
|
+{
|
|
|
|
|
+ public partial class MainForm : Form
|
|
|
|
|
+ {
|
|
|
|
|
+ public const string DbSchema = "akulova_vs";
|
|
|
|
|
+
|
|
|
|
|
+ private readonly List<TableInfo> tableInfos = new List<TableInfo>();
|
|
|
|
|
+ private TableInfo currentTable;
|
|
|
|
|
+ private DataTable currentTableData;
|
|
|
|
|
+ private DataTable currentSearchData;
|
|
|
|
|
+
|
|
|
|
|
+ public MainForm()
|
|
|
|
|
+ {
|
|
|
|
|
+ bool isDesigner = LicenseManager.UsageMode == LicenseUsageMode.Designtime;
|
|
|
|
|
+
|
|
|
|
|
+ if (!isDesigner && !UserSession.IsLoggedIn)
|
|
|
|
|
+ {
|
|
|
|
|
+ MessageBox.Show("Сначала выполните вход в систему.", "Доступ запрещён",
|
|
|
|
|
+ MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
|
|
|
+ Close();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ InitializeComponent();
|
|
|
|
|
+
|
|
|
|
|
+ if (isDesigner)
|
|
|
|
|
+ return;
|
|
|
|
|
+
|
|
|
|
|
+ BuildTableList();
|
|
|
|
|
+ ApplyPermissions();
|
|
|
|
|
+ LoadInitialData();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void BuildTableList()
|
|
|
|
|
+ {
|
|
|
|
|
+ tableInfos.Clear();
|
|
|
|
|
+
|
|
|
|
|
+ if (!UserSession.IsAdmin)
|
|
|
|
|
+ {
|
|
|
|
|
+ tableInfos.Add(new TableInfo("personal_profile", "Мои данные", "driver_id"));
|
|
|
|
|
+ tableInfos.Add(new TableInfo("personal_exams", "Мои экзамены", "exam_id"));
|
|
|
|
|
+ tableInfos.Add(new TableInfo("personal_license", "Моё удостоверение", "license_id"));
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ tableInfos.Add(new TableInfo("driving_schools", "Автошколы", "school_id"));
|
|
|
|
|
+ tableInfos.Add(new TableInfo("instructors", "Инструкторы", "instructor_id"));
|
|
|
|
|
+ tableInfos.Add(new TableInfo("drivers", "Водители", "driver_id"));
|
|
|
|
|
+ tableInfos.Add(new TableInfo("exam_routes", "Маршруты экзамена", "route_id"));
|
|
|
|
|
+ tableInfos.Add(new TableInfo("traffic_inspectors", "Инспекторы", "inspector_id"));
|
|
|
|
|
+ tableInfos.Add(new TableInfo("traffic_officers", "Сотрудники, выдающие права", "officer_id"));
|
|
|
|
|
+ tableInfos.Add(new TableInfo("exams", "Экзамены", "exam_id"));
|
|
|
|
|
+ tableInfos.Add(new TableInfo("licenses", "Водительские удостоверения", "license_id"));
|
|
|
|
|
+ tableInfos.Add(new TableInfo("app_users", "Пользователи приложения", "user_id"));
|
|
|
|
|
+ tableInfos.Add(new TableInfo("audit_log", "Журнал операций", "log_id", true));
|
|
|
|
|
+ }
|
|
|
|
|
+ private void ApplyPermissions()
|
|
|
|
|
+ {
|
|
|
|
|
+ lblUser.Text = UserSession.CurrentUser.Login + " · " + UserSession.CurrentUser.Role;
|
|
|
|
|
+
|
|
|
|
|
+ bool isAdmin = UserSession.IsAdmin;
|
|
|
|
|
+ btnAdd.Enabled = isAdmin;
|
|
|
|
|
+ btnEdit.Enabled = isAdmin;
|
|
|
|
|
+ btnDelete.Enabled = isAdmin;
|
|
|
|
|
+
|
|
|
|
|
+ if (!isAdmin)
|
|
|
|
|
+ {
|
|
|
|
|
+ lblTitle.Text = "Личный кабинет автошколы";
|
|
|
|
|
+ lblTableCaption.Text = "Раздел:";
|
|
|
|
|
+ tabTables.Text = "Личный кабинет";
|
|
|
|
|
+
|
|
|
|
|
+ btnAdd.Visible = false;
|
|
|
|
|
+ btnEdit.Visible = false;
|
|
|
|
|
+ btnDelete.Visible = false;
|
|
|
|
|
+ btnExportTable.Location = btnAdd.Location;
|
|
|
|
|
+
|
|
|
|
|
+ if (tabControl.TabPages.Contains(tabSearch))
|
|
|
|
|
+ tabControl.TabPages.Remove(tabSearch);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ UpdateActionButtons();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void UpdateActionButtons()
|
|
|
|
|
+ {
|
|
|
|
|
+ bool canModify = UserSession.IsAdmin && currentTable != null && !currentTable.IsReadOnly;
|
|
|
|
|
+ if (btnAdd != null) btnAdd.Enabled = canModify;
|
|
|
|
|
+ if (btnEdit != null) btnEdit.Enabled = canModify;
|
|
|
|
|
+ if (btnDelete != null) btnDelete.Enabled = canModify;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void LoadInitialData()
|
|
|
|
|
+ {
|
|
|
|
|
+ cmbTables.Items.Clear();
|
|
|
|
|
+ foreach (TableInfo table in tableInfos)
|
|
|
|
|
+ {
|
|
|
|
|
+ cmbTables.Items.Add(table);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (cmbTables.Items.Count > 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ cmbTables.SelectedIndex = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (UserSession.IsAdmin)
|
|
|
|
|
+ LoadExamSearch();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void CmbTables_SelectedIndexChanged(object sender, EventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ currentTable = cmbTables.SelectedItem as TableInfo;
|
|
|
|
|
+ UpdateActionButtons();
|
|
|
|
|
+ LoadCurrentTable();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void LoadCurrentTable()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (currentTable == null)
|
|
|
|
|
+ return;
|
|
|
|
|
+
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ using (NpgsqlConnection connection = new NpgsqlConnection(DbSettings.ConnectionString))
|
|
|
|
|
+ {
|
|
|
|
|
+ connection.Open();
|
|
|
|
|
+ string sql = GetSelectSql(currentTable);
|
|
|
|
|
+ using (NpgsqlCommand command = new NpgsqlCommand(sql, connection))
|
|
|
|
|
+ {
|
|
|
|
|
+ AddCurrentTableParameters(command);
|
|
|
|
|
+ using (NpgsqlDataAdapter adapter = new NpgsqlDataAdapter(command))
|
|
|
|
|
+ {
|
|
|
|
|
+ currentTableData = new DataTable();
|
|
|
|
|
+ adapter.Fill(currentTableData);
|
|
|
|
|
+ dgvTables.DataSource = currentTableData;
|
|
|
|
|
+ ApplyGridPresentation(dgvTables);
|
|
|
|
|
+ lblTableStatus.Text = currentTable.DisplayName + ": записей — " + currentTableData.Rows.Count;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ MessageBox.Show(AppError.ToUserMessage(ex, "Не удалось загрузить таблицу."),
|
|
|
|
|
+ "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void AddCurrentTableParameters(NpgsqlCommand command)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (currentTable == null)
|
|
|
|
|
+ return;
|
|
|
|
|
+
|
|
|
|
|
+ if (currentTable.Name == "personal_profile" || currentTable.Name == "personal_exams" || currentTable.Name == "personal_license")
|
|
|
|
|
+ {
|
|
|
|
|
+ int driverId = UserSession.CurrentUser != null && UserSession.CurrentUser.DriverId.HasValue
|
|
|
|
|
+ ? UserSession.CurrentUser.DriverId.Value
|
|
|
|
|
+ : -1;
|
|
|
|
|
+ command.Parameters.AddWithValue("@current_driver_id", driverId);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private string GetSelectSql(TableInfo table)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (table.Name == "personal_profile")
|
|
|
|
|
+ {
|
|
|
|
|
+ if (GetCurrentDriverCondition() == null)
|
|
|
|
|
+ return "select 'Учётная запись не привязана к водителю. Обратитесь к администратору.' as message";
|
|
|
|
|
+
|
|
|
|
|
+ return @"select d.driver_id,
|
|
|
|
|
+ concat_ws(' ', d.last_name, d.first_name, d.middle_name) as driver_display,
|
|
|
|
|
+ d.birth_date,
|
|
|
|
|
+ d.phone,
|
|
|
|
|
+ ds.school_name
|
|
|
|
|
+ from akulova_vs.drivers d
|
|
|
|
|
+ join akulova_vs.driving_schools ds on ds.school_id = d.school_id
|
|
|
|
|
+ where d.driver_id = @current_driver_id";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (table.Name == "personal_exams")
|
|
|
|
|
+ {
|
|
|
|
|
+ if (GetCurrentDriverCondition() == null)
|
|
|
|
|
+ return "select 'Учётная запись не привязана к водителю. Обратитесь к администратору.' as message";
|
|
|
|
|
+
|
|
|
|
|
+ return @"select exam_id,
|
|
|
|
|
+ exam_date,
|
|
|
|
|
+ result,
|
|
|
|
|
+ route_name,
|
|
|
|
|
+ inspector_full_name,
|
|
|
|
|
+ attempt_number,
|
|
|
|
|
+ driver_age
|
|
|
|
|
+ from (
|
|
|
|
|
+ select e.exam_id,
|
|
|
|
|
+ e.exam_date,
|
|
|
|
|
+ e.result,
|
|
|
|
|
+ r.route_name,
|
|
|
|
|
+ concat_ws(' ', ti.last_name, ti.first_name, ti.middle_name) as inspector_full_name,
|
|
|
|
|
+ count(*) over (
|
|
|
|
|
+ partition by e.driver_id
|
|
|
|
|
+ order by e.exam_date, e.exam_id
|
|
|
|
|
+ rows between unbounded preceding and current row
|
|
|
|
|
+ )::integer as attempt_number,
|
|
|
|
|
+ extract(year from age(e.exam_date, d.birth_date))::integer as driver_age
|
|
|
|
|
+ from akulova_vs.exams e
|
|
|
|
|
+ join akulova_vs.drivers d on d.driver_id = e.driver_id
|
|
|
|
|
+ join akulova_vs.exam_routes r on r.route_id = e.route_id
|
|
|
|
|
+ join akulova_vs.traffic_inspectors ti on ti.inspector_id = e.inspector_id
|
|
|
|
|
+ where e.driver_id = @current_driver_id
|
|
|
|
|
+ ) personal_exams
|
|
|
|
|
+ order by exam_date desc, exam_id desc";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (table.Name == "personal_license")
|
|
|
|
|
+ {
|
|
|
|
|
+ if (GetCurrentDriverCondition() == null)
|
|
|
|
|
+ return "select 'Учётная запись не привязана к водителю. Обратитесь к администратору.' as message";
|
|
|
|
|
+
|
|
|
|
|
+ return @"select l.license_id,
|
|
|
|
|
+ l.issue_date,
|
|
|
|
|
+ l.category,
|
|
|
|
|
+ l.license_number,
|
|
|
|
|
+ concat_ws(' ', o.last_name, o.first_name, o.middle_name) as officer_display
|
|
|
|
|
+ from akulova_vs.licenses l
|
|
|
|
|
+ join akulova_vs.traffic_officers o on o.officer_id = l.officer_id
|
|
|
|
|
+ where l.driver_id = @current_driver_id
|
|
|
|
|
+ order by l.issue_date desc, l.license_id desc";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (table.Name == "driving_schools")
|
|
|
|
|
+ {
|
|
|
|
|
+ return @"select school_id, school_name, address, phone
|
|
|
|
|
+ from akulova_vs.driving_schools
|
|
|
|
|
+ order by school_id";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (table.Name == "instructors")
|
|
|
|
|
+ {
|
|
|
|
|
+ return @"select i.instructor_id, i.school_id, ds.school_name as school_display,
|
|
|
|
|
+ i.last_name, i.first_name, i.middle_name, i.phone, i.license_number
|
|
|
|
|
+ from akulova_vs.instructors i
|
|
|
|
|
+ join akulova_vs.driving_schools ds on ds.school_id = i.school_id
|
|
|
|
|
+ order by i.instructor_id";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (table.Name == "drivers")
|
|
|
|
|
+ {
|
|
|
|
|
+ return @"select d.driver_id, d.school_id, ds.school_name as school_display,
|
|
|
|
|
+ d.last_name, d.first_name, d.middle_name, d.birth_date, d.phone
|
|
|
|
|
+ from akulova_vs.drivers d
|
|
|
|
|
+ join akulova_vs.driving_schools ds on ds.school_id = d.school_id
|
|
|
|
|
+ order by d.driver_id";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (table.Name == "exam_routes")
|
|
|
|
|
+ {
|
|
|
|
|
+ return @"select route_id, route_name, start_address, end_address, difficulty
|
|
|
|
|
+ from akulova_vs.exam_routes
|
|
|
|
|
+ order by route_id";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (table.Name == "traffic_inspectors")
|
|
|
|
|
+ {
|
|
|
|
|
+ return @"select inspector_id, last_name, first_name, middle_name, rank_name
|
|
|
|
|
+ from akulova_vs.traffic_inspectors
|
|
|
|
|
+ order by inspector_id";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (table.Name == "traffic_officers")
|
|
|
|
|
+ {
|
|
|
|
|
+ return @"select officer_id, last_name, first_name, middle_name, position_name
|
|
|
|
|
+ from akulova_vs.traffic_officers
|
|
|
|
|
+ order by officer_id";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (table.Name == "exams")
|
|
|
|
|
+ {
|
|
|
|
|
+ return @"select e.exam_id,
|
|
|
|
|
+ e.driver_id, concat_ws(' ', d.last_name, d.first_name, d.middle_name) as driver_display,
|
|
|
|
|
+ e.route_id, r.route_name as route_display,
|
|
|
|
|
+ e.inspector_id, concat_ws(' ', ti.last_name, ti.first_name, ti.middle_name) as inspector_display,
|
|
|
|
|
+ e.exam_date, e.result
|
|
|
|
|
+ from akulova_vs.exams e
|
|
|
|
|
+ join akulova_vs.drivers d on d.driver_id = e.driver_id
|
|
|
|
|
+ join akulova_vs.exam_routes r on r.route_id = e.route_id
|
|
|
|
|
+ join akulova_vs.traffic_inspectors ti on ti.inspector_id = e.inspector_id
|
|
|
|
|
+ order by e.exam_id";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (table.Name == "licenses")
|
|
|
|
|
+ {
|
|
|
|
|
+ return @"select l.license_id,
|
|
|
|
|
+ l.driver_id, concat_ws(' ', d.last_name, d.first_name, d.middle_name) as driver_display,
|
|
|
|
|
+ l.officer_id, concat_ws(' ', o.last_name, o.first_name, o.middle_name) as officer_display,
|
|
|
|
|
+ l.issue_date, l.category, l.license_number
|
|
|
|
|
+ from akulova_vs.licenses l
|
|
|
|
|
+ join akulova_vs.drivers d on d.driver_id = l.driver_id
|
|
|
|
|
+ join akulova_vs.traffic_officers o on o.officer_id = l.officer_id
|
|
|
|
|
+ order by l.license_id";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (table.Name == "app_users")
|
|
|
|
|
+ {
|
|
|
|
|
+ return @"select u.user_id, u.login, u.role::text as role, u.is_active,
|
|
|
|
|
+ u.instructor_id, concat_ws(' ', i.last_name, i.first_name, i.middle_name) as instructor_display,
|
|
|
|
|
+ u.driver_id, concat_ws(' ', d.last_name, d.first_name, d.middle_name) as driver_display
|
|
|
|
|
+ from akulova_vs.app_users u
|
|
|
|
|
+ left join akulova_vs.instructors i on i.instructor_id = u.instructor_id
|
|
|
|
|
+ left join akulova_vs.drivers d on d.driver_id = u.driver_id
|
|
|
|
|
+ order by u.user_id";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (table.Name == "audit_log")
|
|
|
|
|
+ {
|
|
|
|
|
+ return @"select log_id,
|
|
|
|
|
+ table_name,
|
|
|
|
|
+ operation,
|
|
|
|
|
+ record_id,
|
|
|
|
|
+ changed_at,
|
|
|
|
|
+ coalesce(nullif(changed_by, ''), 'system') as changed_by
|
|
|
|
|
+ from akulova_vs.audit_log
|
|
|
|
|
+ order by changed_at desc, log_id desc
|
|
|
|
|
+ limit 500";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return "select * from " + FullName(table.Name) + " order by " + Quote(table.PrimaryKey);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private string GetCurrentDriverCondition()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (UserSession.CurrentUser == null || !UserSession.CurrentUser.DriverId.HasValue)
|
|
|
|
|
+ return null;
|
|
|
|
|
+
|
|
|
|
|
+ return UserSession.CurrentUser.DriverId.Value.ToString();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void ApplyGridPresentation(DataGridView grid)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (grid == null)
|
|
|
|
|
+ return;
|
|
|
|
|
+
|
|
|
|
|
+ foreach (DataGridViewColumn column in grid.Columns)
|
|
|
|
|
+ {
|
|
|
|
|
+ string name = column.Name;
|
|
|
|
|
+ column.Visible = !IsTechnicalColumn(name);
|
|
|
|
|
+ column.HeaderText = GetColumnCaption(name);
|
|
|
|
|
+
|
|
|
|
|
+ if (name.EndsWith("_date", StringComparison.OrdinalIgnoreCase) || name == "birth_date" || name == "exam_date" || name == "issue_date")
|
|
|
|
|
+ column.DefaultCellStyle.Format = "dd.MM.yyyy";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ grid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
|
|
|
|
|
+ grid.AutoResizeColumns();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private bool IsTechnicalColumn(string columnName)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (string.IsNullOrEmpty(columnName))
|
|
|
|
|
+ return false;
|
|
|
|
|
+
|
|
|
|
|
+ if (columnName == "password_hash")
|
|
|
|
|
+ return true;
|
|
|
|
|
+
|
|
|
|
|
+ if (columnName == "record_id")
|
|
|
|
|
+ return false;
|
|
|
|
|
+
|
|
|
|
|
+ if (columnName.EndsWith("_id", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
+ return true;
|
|
|
|
|
+
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private string GetColumnCaption(string columnName)
|
|
|
|
|
+ {
|
|
|
|
|
+ Dictionary<string, string> captions = new Dictionary<string, string>
|
|
|
|
|
+ {
|
|
|
|
|
+ { "school_name", "Название автошколы" },
|
|
|
|
|
+ { "address", "Адрес" },
|
|
|
|
|
+ { "phone", "Телефон" },
|
|
|
|
|
+ { "school_display", "Автошкола" },
|
|
|
|
|
+ { "last_name", "Фамилия" },
|
|
|
|
|
+ { "first_name", "Имя" },
|
|
|
|
|
+ { "middle_name", "Отчество" },
|
|
|
|
|
+ { "license_number", "Номер удостоверения" },
|
|
|
|
|
+ { "birth_date", "Дата рождения" },
|
|
|
|
|
+ { "route_name", "Название маршрута" },
|
|
|
|
|
+ { "route_display", "Маршрут" },
|
|
|
|
|
+ { "start_address", "Начальный адрес" },
|
|
|
|
|
+ { "end_address", "Конечный адрес" },
|
|
|
|
|
+ { "difficulty", "Сложность" },
|
|
|
|
|
+ { "rank_name", "Звание" },
|
|
|
|
|
+ { "position_name", "Должность" },
|
|
|
|
|
+ { "driver_display", "Водитель" },
|
|
|
|
|
+ { "message", "Сообщение" },
|
|
|
|
|
+ { "inspector_display", "Инспектор" },
|
|
|
|
|
+ { "officer_display", "Сотрудник" },
|
|
|
|
|
+ { "exam_date", "Дата экзамена" },
|
|
|
|
|
+ { "result", "Результат" },
|
|
|
|
|
+ { "issue_date", "Дата выдачи" },
|
|
|
|
|
+ { "category", "Категория" },
|
|
|
|
|
+ { "login", "Логин" },
|
|
|
|
|
+ { "role", "Роль" },
|
|
|
|
|
+ { "is_active", "Активен" },
|
|
|
|
|
+ { "instructor_display", "Инструктор" },
|
|
|
|
|
+ { "driver_full_name", "Водитель" },
|
|
|
|
|
+ { "inspector_full_name", "Инспектор" },
|
|
|
|
|
+ { "attempt_number", "Попытка №" },
|
|
|
|
|
+ { "driver_age", "Возраст водителя" },
|
|
|
|
|
+ { "table_name", "Таблица" },
|
|
|
|
|
+ { "operation", "Операция" },
|
|
|
|
|
+ { "record_id", "ID записи" },
|
|
|
|
|
+ { "changed_at", "Дата и время" },
|
|
|
|
|
+ { "changed_by", "Пользователь" }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return captions.ContainsKey(columnName) ? captions[columnName] : columnName;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void BtnRefresh_Click(object sender, EventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ LoadCurrentTable();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void BtnAdd_Click(object sender, EventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!UserSession.IsAdmin)
|
|
|
|
|
+ {
|
|
|
|
|
+ ShowReadonlyMessage();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (currentTable == null)
|
|
|
|
|
+ return;
|
|
|
|
|
+
|
|
|
|
|
+ if (currentTable.IsReadOnly)
|
|
|
|
|
+ {
|
|
|
|
|
+ MessageBox.Show("Этот раздел предназначен только для просмотра.", "Информация",
|
|
|
|
|
+ MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ using (RecordEditForm form = new RecordEditForm(DbSettings.ConnectionString, currentTable, null))
|
|
|
|
|
+ {
|
|
|
|
|
+ if (form.ShowDialog(this) == DialogResult.OK)
|
|
|
|
|
+ {
|
|
|
|
|
+ LoadCurrentTable();
|
|
|
|
|
+ LoadExamSearch();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void BtnEdit_Click(object sender, EventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!UserSession.IsAdmin)
|
|
|
|
|
+ {
|
|
|
|
|
+ ShowReadonlyMessage();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (currentTable != null && currentTable.IsReadOnly)
|
|
|
|
|
+ {
|
|
|
|
|
+ MessageBox.Show("Этот раздел предназначен только для просмотра.", "Информация",
|
|
|
|
|
+ MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (currentTable == null || dgvTables.SelectedRows.Count == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ MessageBox.Show("Выберите запись для изменения.", "Информация",
|
|
|
|
|
+ MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ DataRowView rowView = dgvTables.SelectedRows[0].DataBoundItem as DataRowView;
|
|
|
|
|
+ if (rowView == null)
|
|
|
|
|
+ return;
|
|
|
|
|
+
|
|
|
|
|
+ using (RecordEditForm form = new RecordEditForm(DbSettings.ConnectionString, currentTable, rowView.Row))
|
|
|
|
|
+ {
|
|
|
|
|
+ if (form.ShowDialog(this) == DialogResult.OK)
|
|
|
|
|
+ {
|
|
|
|
|
+ LoadCurrentTable();
|
|
|
|
|
+ LoadExamSearch();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void BtnDelete_Click(object sender, EventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!UserSession.IsAdmin)
|
|
|
|
|
+ {
|
|
|
|
|
+ ShowReadonlyMessage();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (currentTable != null && currentTable.IsReadOnly)
|
|
|
|
|
+ {
|
|
|
|
|
+ MessageBox.Show("Этот раздел предназначен только для просмотра.", "Информация",
|
|
|
|
|
+ MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (currentTable == null || dgvTables.SelectedRows.Count == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ MessageBox.Show("Выберите запись для удаления.", "Информация",
|
|
|
|
|
+ MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ DataRowView rowView = dgvTables.SelectedRows[0].DataBoundItem as DataRowView;
|
|
|
|
|
+ if (rowView == null)
|
|
|
|
|
+ return;
|
|
|
|
|
+
|
|
|
|
|
+ object pkValue = rowView.Row[currentTable.PrimaryKey];
|
|
|
|
|
+
|
|
|
|
|
+ if (currentTable.Name == "app_users" && Convert.ToInt32(pkValue) == UserSession.CurrentUser.UserId)
|
|
|
|
|
+ {
|
|
|
|
|
+ MessageBox.Show("Нельзя удалить текущую учётную запись, под которой выполнен вход.",
|
|
|
|
|
+ "Удаление запрещено", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ DialogResult result = MessageBox.Show("Удалить выбранную запись?",
|
|
|
|
|
+ "Подтверждение", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
|
|
|
|
|
+
|
|
|
|
|
+ if (result != DialogResult.Yes)
|
|
|
|
|
+ return;
|
|
|
|
|
+
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ using (NpgsqlConnection connection = new NpgsqlConnection(DbSettings.ConnectionString))
|
|
|
|
|
+ {
|
|
|
|
|
+ connection.Open();
|
|
|
|
|
+ DbAppContext.Apply(connection);
|
|
|
|
|
+ string sql = "delete from " + FullName(currentTable.Name) + " where " + Quote(currentTable.PrimaryKey) + " = @id";
|
|
|
|
|
+ using (NpgsqlCommand command = new NpgsqlCommand(sql, connection))
|
|
|
|
|
+ {
|
|
|
|
|
+ command.Parameters.AddWithValue("@id", pkValue);
|
|
|
|
|
+ command.ExecuteNonQuery();
|
|
|
|
|
+ DbAppContext.MarkLatestAuditLogin(connection, currentTable.Name, pkValue);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ LoadCurrentTable();
|
|
|
|
|
+ LoadExamSearch();
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ MessageBox.Show(AppError.ToUserMessage(ex, "Не удалось удалить запись."),
|
|
|
|
|
+ "Ошибка удаления", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void ShowReadonlyMessage()
|
|
|
|
|
+ {
|
|
|
|
|
+ MessageBox.Show("Обычный пользователь работает только с личным кабинетом. Изменение данных доступно только роли admin.",
|
|
|
|
|
+ "Недостаточно прав", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void BtnExportTable_Click(object sender, EventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ ExportTable(dgvTables, currentTable == null ? "table" : currentTable.Name);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void BtnSearch_Click(object sender, EventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ LoadExamSearch();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void BtnReset_Click(object sender, EventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ txtDriverFilter.Clear();
|
|
|
|
|
+ txtSchoolFilter.Clear();
|
|
|
|
|
+ cmbResultFilter.SelectedIndex = 0;
|
|
|
|
|
+ dtFrom.Checked = false;
|
|
|
|
|
+ dtTo.Checked = false;
|
|
|
|
|
+ LoadExamSearch();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void BtnExportSearch_Click(object sender, EventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ ExportTable(dgvSearch, "exam_search");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void LoadExamSearch()
|
|
|
|
|
+ {
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ using (NpgsqlConnection connection = new NpgsqlConnection(DbSettings.ConnectionString))
|
|
|
|
|
+ {
|
|
|
|
|
+ connection.Open();
|
|
|
|
|
+
|
|
|
|
|
+ const string sql = @"
|
|
|
|
|
+ select exam_id, exam_date, result, driver_full_name, school_name, route_name, inspector_full_name, attempt_number, driver_age
|
|
|
|
|
+ from akulova_vs.v_exam_search
|
|
|
|
|
+ where (cast(@driver as text) = '' or driver_full_name ilike '%' || cast(@driver as text) || '%')
|
|
|
|
|
+ and (cast(@school as text) = '' or school_name ilike '%' || cast(@school as text) || '%')
|
|
|
|
|
+ and (cast(@result as text) = '' or result = cast(@result as text))
|
|
|
|
|
+ and (cast(@date_from as date) is null or exam_date >= cast(@date_from as date))
|
|
|
|
|
+ and (cast(@date_to as date) is null or exam_date <= cast(@date_to as date))
|
|
|
|
|
+ order by exam_date desc, exam_id desc;";
|
|
|
|
|
+
|
|
|
|
|
+ using (NpgsqlCommand command = new NpgsqlCommand(sql, connection))
|
|
|
|
|
+ {
|
|
|
|
|
+ command.Parameters.AddWithValue("@driver", txtDriverFilter == null ? "" : txtDriverFilter.Text.Trim());
|
|
|
|
|
+ command.Parameters.AddWithValue("@school", txtSchoolFilter == null ? "" : txtSchoolFilter.Text.Trim());
|
|
|
|
|
+ string selectedResult = cmbResultFilter == null || cmbResultFilter.SelectedIndex <= 0
|
|
|
|
|
+ ? ""
|
|
|
|
|
+ : cmbResultFilter.SelectedItem.ToString();
|
|
|
|
|
+ command.Parameters.AddWithValue("@result", selectedResult);
|
|
|
|
|
+
|
|
|
|
|
+ Npgsql.NpgsqlParameter dateFromParameter = command.Parameters.Add("@date_from", NpgsqlTypes.NpgsqlDbType.Date);
|
|
|
|
|
+ dateFromParameter.Value = dtFrom != null && dtFrom.Checked ? (object)dtFrom.Value.Date : DBNull.Value;
|
|
|
|
|
+
|
|
|
|
|
+ Npgsql.NpgsqlParameter dateToParameter = command.Parameters.Add("@date_to", NpgsqlTypes.NpgsqlDbType.Date);
|
|
|
|
|
+ dateToParameter.Value = dtTo != null && dtTo.Checked ? (object)dtTo.Value.Date : DBNull.Value;
|
|
|
|
|
+
|
|
|
|
|
+ using (NpgsqlDataAdapter adapter = new NpgsqlDataAdapter(command))
|
|
|
|
|
+ {
|
|
|
|
|
+ currentSearchData = new DataTable();
|
|
|
|
|
+ adapter.Fill(currentSearchData);
|
|
|
|
|
+ if (dgvSearch != null)
|
|
|
|
|
+ {
|
|
|
|
|
+ dgvSearch.DataSource = currentSearchData;
|
|
|
|
|
+ ApplyGridPresentation(dgvSearch);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (dgvSearch != null)
|
|
|
|
|
+ {
|
|
|
|
|
+ MessageBox.Show(AppError.ToUserMessage(ex, "Не удалось выполнить поиск по экзаменам."),
|
|
|
|
|
+ "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void ExportTable(DataGridView grid, string filePrefix)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (grid == null || grid.DataSource == null || grid.Rows.Count == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ MessageBox.Show("Нет данных для экспорта.", "Информация",
|
|
|
|
|
+ MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ using (SaveFileDialog dialog = new SaveFileDialog())
|
|
|
|
|
+ {
|
|
|
|
|
+ dialog.Filter = "CSV файл (*.csv)|*.csv";
|
|
|
|
|
+ dialog.FileName = filePrefix + "_" + DateTime.Now.ToString("yyyyMMdd_HHmm") + ".csv";
|
|
|
|
|
+
|
|
|
|
|
+ if (dialog.ShowDialog(this) != DialogResult.OK)
|
|
|
|
|
+ return;
|
|
|
|
|
+
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ WriteCsv(grid, dialog.FileName);
|
|
|
|
|
+ MessageBox.Show("CSV-файл сохранён:\n" + dialog.FileName,
|
|
|
|
|
+ "Экспорт выполнен", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ MessageBox.Show("Не удалось сохранить CSV.\n\n" + ex.Message,
|
|
|
|
|
+ "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void WriteCsv(DataGridView grid, string path)
|
|
|
|
|
+ {
|
|
|
|
|
+ List<DataGridViewColumn> visibleColumns = new List<DataGridViewColumn>();
|
|
|
|
|
+ foreach (DataGridViewColumn column in grid.Columns)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (column.Visible)
|
|
|
|
|
+ visibleColumns.Add(column);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ using (StreamWriter writer = new StreamWriter(path, false, new UTF8Encoding(true)))
|
|
|
|
|
+ {
|
|
|
|
|
+ for (int i = 0; i < visibleColumns.Count; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (i > 0) writer.Write(';');
|
|
|
|
|
+ writer.Write(EscapeCsv(visibleColumns[i].HeaderText));
|
|
|
|
|
+ }
|
|
|
|
|
+ writer.WriteLine();
|
|
|
|
|
+
|
|
|
|
|
+ foreach (DataGridViewRow row in grid.Rows)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (row.IsNewRow)
|
|
|
|
|
+ continue;
|
|
|
|
|
+
|
|
|
|
|
+ for (int i = 0; i < visibleColumns.Count; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (i > 0) writer.Write(';');
|
|
|
|
|
+ object value = row.Cells[visibleColumns[i].Name].Value;
|
|
|
|
|
+ writer.Write(EscapeCsv(value == null || value == DBNull.Value ? "" : value.ToString()));
|
|
|
|
|
+ }
|
|
|
|
|
+ writer.WriteLine();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private string EscapeCsv(string value)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (value == null) return "";
|
|
|
|
|
+ bool needQuotes = value.Contains(";") || value.Contains("\"") || value.Contains("\n") || value.Contains("\r");
|
|
|
|
|
+ value = value.Replace("\"", "\"\"");
|
|
|
|
|
+ return needQuotes ? "\"" + value + "\"" : value;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void BtnLogout_Click(object sender, EventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ UserSession.Logout();
|
|
|
|
|
+ Close();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static string FullName(string identifier)
|
|
|
|
|
+ {
|
|
|
|
|
+ return Quote(DbSchema) + "." + Quote(identifier);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static string Quote(string identifier)
|
|
|
|
|
+ {
|
|
|
|
|
+ return "\"" + identifier.Replace("\"", "\"\"") + "\"";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public class TableInfo
|
|
|
|
|
+ {
|
|
|
|
|
+ public string Name { get; private set; }
|
|
|
|
|
+ public string DisplayName { get; private set; }
|
|
|
|
|
+ public string PrimaryKey { get; private set; }
|
|
|
|
|
+ public bool IsReadOnly { get; private set; }
|
|
|
|
|
+
|
|
|
|
|
+ public TableInfo(string name, string displayName, string primaryKey)
|
|
|
|
|
+ : this(name, displayName, primaryKey, false)
|
|
|
|
|
+ {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public TableInfo(string name, string displayName, string primaryKey, bool isReadOnly)
|
|
|
|
|
+ {
|
|
|
|
|
+ Name = name;
|
|
|
|
|
+ DisplayName = displayName;
|
|
|
|
|
+ PrimaryKey = primaryKey;
|
|
|
|
|
+ IsReadOnly = isReadOnly;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public override string ToString()
|
|
|
|
|
+ {
|
|
|
|
|
+ return DisplayName;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public class ColumnInfo
|
|
|
|
|
+ {
|
|
|
|
|
+ public string Name { get; set; }
|
|
|
|
|
+ public string DataType { get; set; }
|
|
|
|
|
+ public string UdtName { get; set; }
|
|
|
|
|
+ public bool IsNullable { get; set; }
|
|
|
|
|
+ public bool IsIdentity { get; set; }
|
|
|
|
|
+ public bool IsPrimaryKey { get; set; }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public class ComboItem
|
|
|
|
|
+ {
|
|
|
|
|
+ public int? Id { get; private set; }
|
|
|
|
|
+ public string Text { get; private set; }
|
|
|
|
|
+
|
|
|
|
|
+ public ComboItem(int? id, string text)
|
|
|
|
|
+ {
|
|
|
|
|
+ Id = id;
|
|
|
|
|
+ Text = text;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public override string ToString()
|
|
|
|
|
+ {
|
|
|
|
|
+ return Text;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public class FieldBinding
|
|
|
|
|
+ {
|
|
|
|
|
+ public ColumnInfo Column { get; set; }
|
|
|
|
|
+ public Control Control { get; set; }
|
|
|
|
|
+ public bool IsPasswordField { get; set; }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|