Эх сурвалжийг харах

добавление файлов в репозиторий

jeon1x 1 долоо хоног өмнө
commit
ffb986a4a2

+ 3 - 0
.github/copilot-instructions.md

@@ -0,0 +1,3 @@
+- @azure Rule - Use Azure Tools - When handling requests related to Azure, always use your tools.
+- @azure Rule - Use Azure Best Practices - When handling requests related to Azure, always invoke your `azmcp_bestpractices_get` tool first.
+- @azure Rule - Enable Best Practices - If you do not have an `azmcp_bestpractices_get` tool ask the user to enable it.

+ 8 - 0
.gitignore

@@ -0,0 +1,8 @@
+# Visual Studio
+bin/
+obj/
+.vs/
+*.user
+*.suo
+packages/
+*.exe

+ 76 - 0
README.md

@@ -0,0 +1,76 @@
+# ЖКХ Управление
+
+Информационная система для автоматизации учёта квартир, собственников и платежей в сфере ЖКХ.
+
+## Введение
+
+Система предназначена для сотрудников управляющих компаний, ТСЖ и жильцов. Обеспечивает учёт квартир, формирование квитанций, приём платежей и контроль задолженности.
+
+### Основные возможности
+
+- Авторизация пользователей (администратор, оператор, житель);
+- Учёт квартир и собственников;
+- Формирование квитанций на оплату коммунальных услуг;
+- Приём платежей (онлайн и в кассе);
+- Контроль задолженности;
+- Формирование отчётов;
+- Журнал операций (аудит изменений).
+
+## Назначение и условия применения
+
+Система предназначена для использования в управляющих компаниях, ТСЖ и ЖСК.
+
+Требования:
+- Операционная система: Windows 10/11
+- Установленный .NET 6.0 Runtime
+- PostgreSQL 12 или выше (сервер)
+
+## Установка
+
+1. Скачайте установочный файл `ZhkhSetup.exe`
+2. Запустите его от имени администратора
+3. Следуйте инструкциям мастера установки
+4. Выберите папку для установки
+5. После завершения установки запустите приложение
+
+## Описание операций
+
+### Авторизация
+1. Введите логин и пароль
+2. Нажмите «Войти»
+3. Откроется главное окно в зависимости от роли
+
+### Работа с квартирами
+1. Перейдите в раздел «Квартиры»
+2. Нажмите «Добавить» для создания новой квартиры
+3. Заполните поля и нажмите «Сохранить»
+
+### Формирование квитанции
+1. Перейдите в раздел «Квитанции»
+2. Нажмите «Создать»
+3. Выберите квартиру, укажите период и сумму
+4. Нажмите «Сохранить»
+
+### Приём платежа
+1. Выберите квитанцию
+2. Нажмите «Оплатить»
+3. Укажите сумму и способ оплаты
+4. Подтвердите операцию
+
+## Термины и сокращения
+
+ЖКХ - Жилищно-коммунальное хозяйство
+ТСЖ - Товарищество собственников жилья
+ЖСК - Жилищно-строительный кооператив
+Квитанция - Документ для оплаты коммунальных услуг
+Пени - Штраф за просрочку платежа
+
+## Технологии
+
+- C# Windows Forms
+- PostgreSQL
+- Npgsql
+
+## Автор
+
+Григорьева В.А., группа 3992

+ 25 - 0
ZhkhApp.sln

@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.14.37216.2 d17.14
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZhkhApp", "ZhkhApp\ZhkhApp.csproj", "{0C8DCFC1-9A73-4A5F-A272-5FBBB83D0556}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{0C8DCFC1-9A73-4A5F-A272-5FBBB83D0556}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{0C8DCFC1-9A73-4A5F-A272-5FBBB83D0556}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{0C8DCFC1-9A73-4A5F-A272-5FBBB83D0556}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{0C8DCFC1-9A73-4A5F-A272-5FBBB83D0556}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {899C1C95-5BED-4A90-8234-E24EECC07B91}
+	EndGlobalSection
+EndGlobal

+ 186 - 0
ZhkhApp/AdminForm.Designer.cs

@@ -0,0 +1,186 @@
+using System.Windows.Forms;
+
+namespace ZhkhApp
+{
+    partial class AdminForm
+    {
+        private System.ComponentModel.IContainer components = null;
+        private DataGridView dgvList;
+        private ComboBox cbTables;
+        private Button btnLoad, btnAdd, btnEdit, btnDelete, btnRefresh, btnLogout, btnUsers, btnPayments, btnLog;
+        private Label lblTitle, lblUserInfo;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null)) components.Dispose();
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.dgvList = new System.Windows.Forms.DataGridView();
+            this.cbTables = new System.Windows.Forms.ComboBox();
+            this.btnLoad = new System.Windows.Forms.Button();
+            this.btnAdd = new System.Windows.Forms.Button();
+            this.btnEdit = new System.Windows.Forms.Button();
+            this.btnDelete = new System.Windows.Forms.Button();
+            this.btnRefresh = new System.Windows.Forms.Button();
+            this.btnPayments = new System.Windows.Forms.Button();
+            this.btnUsers = new System.Windows.Forms.Button();
+            this.btnLogout = new System.Windows.Forms.Button();
+            this.lblTitle = new System.Windows.Forms.Label();
+            this.lblUserInfo = new System.Windows.Forms.Label();
+            this.btnLog = new System.Windows.Forms.Button();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvList)).BeginInit();
+            this.SuspendLayout();
+
+            // dgvList
+            this.dgvList.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
+            this.dgvList.Location = new System.Drawing.Point(20, 120);
+            this.dgvList.MultiSelect = false;
+            this.dgvList.Name = "dgvList";
+            this.dgvList.ReadOnly = true;
+            this.dgvList.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
+            this.dgvList.Size = new System.Drawing.Size(1150, 530);
+            this.dgvList.TabIndex = 13;
+
+            // cbTables
+            this.cbTables.DropDownStyle = ComboBoxStyle.DropDownList;
+            this.cbTables.Location = new System.Drawing.Point(46, 70);
+            this.cbTables.Name = "cbTables";
+            this.cbTables.Size = new System.Drawing.Size(150, 21);
+            this.cbTables.TabIndex = 4;
+            this.cbTables.SelectedIndexChanged += CbTables_SelectedIndexChanged;
+
+            // btnLoad
+            this.btnLoad.BackColor = System.Drawing.Color.LightBlue;
+            this.btnLoad.Location = new System.Drawing.Point(240, 70);
+            this.btnLoad.Name = "btnLoad";
+            this.btnLoad.Size = new System.Drawing.Size(90, 35);
+            this.btnLoad.TabIndex = 5;
+            this.btnLoad.Text = "Загрузить";
+            this.btnLoad.UseVisualStyleBackColor = false;
+            this.btnLoad.Click += BtnLoad_Click;
+
+            // btnAdd
+            this.btnAdd.BackColor = System.Drawing.Color.LightGreen;
+            this.btnAdd.Location = new System.Drawing.Point(336, 70);
+            this.btnAdd.Name = "btnAdd";
+            this.btnAdd.Size = new System.Drawing.Size(90, 35);
+            this.btnAdd.TabIndex = 6;
+            this.btnAdd.Text = "Добавить";
+            this.btnAdd.UseVisualStyleBackColor = false;
+            this.btnAdd.Click += BtnAdd_Click;
+
+            // btnEdit
+            this.btnEdit.BackColor = System.Drawing.Color.LightYellow;
+            this.btnEdit.Location = new System.Drawing.Point(432, 70);
+            this.btnEdit.Name = "btnEdit";
+            this.btnEdit.Size = new System.Drawing.Size(90, 35);
+            this.btnEdit.TabIndex = 7;
+            this.btnEdit.Text = "Изменить";
+            this.btnEdit.UseVisualStyleBackColor = false;
+            this.btnEdit.Click += BtnEdit_Click;
+
+            // btnDelete
+            this.btnDelete.BackColor = System.Drawing.Color.LightCoral;
+            this.btnDelete.Location = new System.Drawing.Point(537, 70);
+            this.btnDelete.Name = "btnDelete";
+            this.btnDelete.Size = new System.Drawing.Size(90, 35);
+            this.btnDelete.TabIndex = 8;
+            this.btnDelete.Text = "Удалить";
+            this.btnDelete.UseVisualStyleBackColor = false;
+            this.btnDelete.Click += BtnDelete_Click;
+
+            // btnRefresh
+            this.btnRefresh.BackColor = System.Drawing.Color.LightGray;
+            this.btnRefresh.Location = new System.Drawing.Point(633, 70);
+            this.btnRefresh.Name = "btnRefresh";
+            this.btnRefresh.Size = new System.Drawing.Size(90, 35);
+            this.btnRefresh.TabIndex = 9;
+            this.btnRefresh.Text = "Обновить";
+            this.btnRefresh.UseVisualStyleBackColor = false;
+            this.btnRefresh.Click += BtnRefresh_Click;
+
+            // btnPayments
+            this.btnPayments.BackColor = System.Drawing.Color.LightGoldenrodYellow;
+            this.btnPayments.Location = new System.Drawing.Point(738, 70);
+            this.btnPayments.Name = "btnPayments";
+            this.btnPayments.Size = new System.Drawing.Size(90, 35);
+            this.btnPayments.TabIndex = 10;
+            this.btnPayments.Text = "Платежи";
+            this.btnPayments.UseVisualStyleBackColor = false;
+            this.btnPayments.Click += BtnPayments_Click;
+
+            // btnUsers
+            this.btnUsers.BackColor = System.Drawing.Color.Lavender;
+            this.btnUsers.Location = new System.Drawing.Point(834, 70);
+            this.btnUsers.Name = "btnUsers";
+            this.btnUsers.Size = new System.Drawing.Size(100, 35);
+            this.btnUsers.TabIndex = 11;
+            this.btnUsers.Text = "Пользователи";
+            this.btnUsers.UseVisualStyleBackColor = false;
+            this.btnUsers.Click += BtnUsers_Click;
+
+            // btnLog
+            this.btnLog.BackColor = System.Drawing.Color.LightSteelBlue;
+            this.btnLog.Location = new System.Drawing.Point(940, 70);
+            this.btnLog.Name = "btnLog";
+            this.btnLog.Size = new System.Drawing.Size(120, 35);
+            this.btnLog.TabIndex = 14;
+            this.btnLog.Text = "Журнал операций";
+            this.btnLog.UseVisualStyleBackColor = false;
+            this.btnLog.Click += BtnLog_Click;
+
+            // btnLogout
+            this.btnLogout.BackColor = System.Drawing.Color.IndianRed;
+            this.btnLogout.ForeColor = System.Drawing.Color.White;
+            this.btnLogout.Location = new System.Drawing.Point(1080, 12);
+            this.btnLogout.Name = "btnLogout";
+            this.btnLogout.Size = new System.Drawing.Size(90, 35);
+            this.btnLogout.TabIndex = 12;
+            this.btnLogout.Text = "Выход";
+            this.btnLogout.UseVisualStyleBackColor = false;
+            this.btnLogout.Click += BtnLogout_Click;
+
+            // lblTitle
+            this.lblTitle.Font = new System.Drawing.Font("Segoe UI", 16F, System.Drawing.FontStyle.Bold);
+            this.lblTitle.ForeColor = System.Drawing.Color.DarkBlue;
+            this.lblTitle.Location = new System.Drawing.Point(20, 20);
+            this.lblTitle.Name = "lblTitle";
+            this.lblTitle.Size = new System.Drawing.Size(700, 40);
+            this.lblTitle.TabIndex = 1;
+            this.lblTitle.Text = "Система учёта ЖКХ (Администратор)";
+
+            // lblUserInfo
+            this.lblUserInfo.Location = new System.Drawing.Point(657, 24);
+            this.lblUserInfo.Name = "lblUserInfo";
+            this.lblUserInfo.Size = new System.Drawing.Size(300, 30);
+            this.lblUserInfo.TabIndex = 2;
+            this.lblUserInfo.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
+
+            // AdminForm
+            this.BackColor = System.Drawing.Color.WhiteSmoke;
+            this.ClientSize = new System.Drawing.Size(1184, 661);
+            this.Controls.Add(this.btnLog);
+            this.Controls.Add(this.lblTitle);
+            this.Controls.Add(this.lblUserInfo);
+            this.Controls.Add(this.cbTables);
+            this.Controls.Add(this.btnLoad);
+            this.Controls.Add(this.btnAdd);
+            this.Controls.Add(this.btnEdit);
+            this.Controls.Add(this.btnDelete);
+            this.Controls.Add(this.btnRefresh);
+            this.Controls.Add(this.btnPayments);
+            this.Controls.Add(this.btnUsers);
+            this.Controls.Add(this.btnLogout);
+            this.Controls.Add(this.dgvList);
+            this.Name = "AdminForm";
+            this.StartPosition = FormStartPosition.CenterScreen;
+            this.Text = "ЖКХ - Администратор";
+
+            ((System.ComponentModel.ISupportInitialize)(this.dgvList)).EndInit();
+            this.ResumeLayout(false);
+        }
+    }
+}

+ 365 - 0
ZhkhApp/AdminForm.cs

@@ -0,0 +1,365 @@
+using System;
+using System.Data;
+using System.Windows.Forms;
+using Npgsql;
+
+namespace ZhkhApp
+{
+    public partial class AdminForm : Form
+    {
+        private string currentView = "v_receipts_full";
+        private bool isUpdating = false;
+
+        public AdminForm()
+        {
+            InitializeComponent();
+            LoadViewsList();
+            LoadData();
+            DisplayUserInfo();
+        }
+
+        // Загружаем представления для просмотра + таблицы для редактирования
+        private void LoadViewsList()
+        {
+            cbTables.Items.Clear();
+            cbTables.Items.AddRange(new[] {
+                "Квитанции (полные)",           // v_receipts_full
+                "Задолженность по квартирам",   // v_apartment_debt
+                "История платежей",             // v_payment_history
+                "---",                          // разделитель
+                "Управление: Квартиры",         // apartments (редактирование)
+                "Управление: Люди",             // persons
+                "Управление: Квитанции",        // receipts
+                "Управление: Платежи",          // payments
+                "Управление: Пользователи",     // users
+                "Журнал операций"               // operation_log
+            });
+            cbTables.SelectedIndex = 0;
+        }
+
+        private void CbTables_SelectedIndexChanged(object sender, EventArgs e)
+        {
+            string selected = cbTables.SelectedItem.ToString();
+            isUpdating = true;
+
+            switch (selected)
+            {
+                case "Квитанции (полные)":
+                    currentView = "v_receipts_full";
+                    EnableEditButtons(false);
+                    break;
+                case "Задолженность по квартирам":
+                    currentView = "v_apartment_debt";
+                    EnableEditButtons(false);
+                    break;
+                case "История платежей":
+                    currentView = "v_payment_history";
+                    EnableEditButtons(false);
+                    break;
+                case "Управление: Квартиры":
+                    currentView = "apartments";
+                    EnableEditButtons(true);
+                    break;
+                case "Управление: Люди":
+                    currentView = "persons";
+                    EnableEditButtons(true);
+                    break;
+                case "Управление: Квитанции":
+                    currentView = "receipts";
+                    EnableEditButtons(true);
+                    break;
+                case "Управление: Платежи":
+                    currentView = "payments";
+                    EnableEditButtons(true);
+                    break;
+                case "Управление: Пользователи":
+                    currentView = "users";
+                    EnableEditButtons(true);
+                    break;
+                case "Журнал операций":
+                    currentView = "operation_log";
+                    EnableEditButtons(false);
+                    break;
+                default:
+                    currentView = "v_receipts_full";
+                    EnableEditButtons(false);
+                    break;
+            }
+            isUpdating = false;
+            LoadData();
+        }
+
+        private void EnableEditButtons(bool enable)
+        {
+            btnAdd.Enabled = enable;
+            btnEdit.Enabled = enable;
+            btnDelete.Enabled = enable;
+        }
+
+        private void BtnLoad_Click(object sender, EventArgs e) => LoadData();
+        private void BtnRefresh_Click(object sender, EventArgs e) => LoadData();
+
+        private void LoadData()
+        {
+            try
+            {
+                string query = $"SELECT * FROM {currentView}";
+
+                using (var conn = new NpgsqlConnection(MainForm.ConnectionString))
+                {
+                    conn.Open();
+                    using (var adapter = new NpgsqlDataAdapter(query, conn))
+                    {
+                        DataTable dt = new DataTable();
+                        adapter.Fill(dt);
+                        dgvList.DataSource = dt;
+                    }
+                }
+                SetRussianHeaders();
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}");
+            }
+        }
+
+        private void SetRussianHeaders()
+        {
+            // v_receipts_full
+            if (currentView == "v_receipts_full")
+            {
+                if (dgvList.Columns["receipt_id"] != null)
+                    dgvList.Columns["receipt_id"].HeaderText = "№";
+                if (dgvList.Columns["apt_number"] != null)
+                    dgvList.Columns["apt_number"].HeaderText = "Квартира";
+                if (dgvList.Columns["full_address"] != null)
+                    dgvList.Columns["full_address"].HeaderText = "Адрес";
+                if (dgvList.Columns["period_month"] != null)
+                    dgvList.Columns["period_month"].HeaderText = "Месяц";
+                if (dgvList.Columns["period_year"] != null)
+                    dgvList.Columns["period_year"].HeaderText = "Год";
+                if (dgvList.Columns["total_sum"] != null)
+                    dgvList.Columns["total_sum"].HeaderText = "Сумма, руб";
+                if (dgvList.Columns["penalty"] != null)
+                    dgvList.Columns["penalty"].HeaderText = "Пени, руб";
+                if (dgvList.Columns["total_with_penalty"] != null)
+                    dgvList.Columns["total_with_penalty"].HeaderText = "Итого, руб";
+                if (dgvList.Columns["status"] != null)
+                    dgvList.Columns["status"].HeaderText = "Статус";
+                if (dgvList.Columns["debtor_name"] != null)
+                    dgvList.Columns["debtor_name"].HeaderText = "Должник";
+            }
+            // v_apartment_debt
+            else if (currentView == "v_apartment_debt")
+            {
+                if (dgvList.Columns["apt_number"] != null)
+                    dgvList.Columns["apt_number"].HeaderText = "Квартира";
+                if (dgvList.Columns["address"] != null)
+                    dgvList.Columns["address"].HeaderText = "Адрес";
+                if (dgvList.Columns["total_accrued"] != null)
+                    dgvList.Columns["total_accrued"].HeaderText = "Начислено, руб";
+                if (dgvList.Columns["total_paid"] != null)
+                    dgvList.Columns["total_paid"].HeaderText = "Оплачено, руб";
+                if (dgvList.Columns["current_debt"] != null)
+                    dgvList.Columns["current_debt"].HeaderText = "Задолженность, руб";
+            }
+            // v_payment_history
+            else if (currentView == "v_payment_history")
+            {
+                if (dgvList.Columns["payment_id"] != null)
+                    dgvList.Columns["payment_id"].HeaderText = "№ платежа";
+                if (dgvList.Columns["apt_number"] != null)
+                    dgvList.Columns["apt_number"].HeaderText = "Квартира";
+                if (dgvList.Columns["payer_name"] != null)
+                    dgvList.Columns["payer_name"].HeaderText = "Плательщик";
+                if (dgvList.Columns["payment_date"] != null)
+                    dgvList.Columns["payment_date"].HeaderText = "Дата оплаты";
+                if (dgvList.Columns["amount"] != null)
+                    dgvList.Columns["amount"].HeaderText = "Сумма, руб";
+                if (dgvList.Columns["payment_method"] != null)
+                    dgvList.Columns["payment_method"].HeaderText = "Способ оплаты";
+            }
+            // apartments
+            else if (currentView == "apartments")
+            {
+                if (dgvList.Columns["apartment_id"] != null)
+                    dgvList.Columns["apartment_id"].HeaderText = "ID";
+                if (dgvList.Columns["apt_number"] != null)
+                    dgvList.Columns["apt_number"].HeaderText = "№ квартиры";
+                if (dgvList.Columns["area"] != null)
+                    dgvList.Columns["area"].HeaderText = "Площадь, м²";
+                if (dgvList.Columns["resident_count"] != null)
+                    dgvList.Columns["resident_count"].HeaderText = "Проживает";
+                if (dgvList.Columns["address_id"] != null)
+                    dgvList.Columns["address_id"].HeaderText = "ID адреса";
+            }
+            // persons
+            else if (currentView == "persons")
+            {
+                if (dgvList.Columns["person_id"] != null)
+                    dgvList.Columns["person_id"].HeaderText = "ID";
+                if (dgvList.Columns["last_name"] != null)
+                    dgvList.Columns["last_name"].HeaderText = "Фамилия";
+                if (dgvList.Columns["first_name"] != null)
+                    dgvList.Columns["first_name"].HeaderText = "Имя";
+                if (dgvList.Columns["middle_name"] != null)
+                    dgvList.Columns["middle_name"].HeaderText = "Отчество";
+            }
+            // receipts
+            else if (currentView == "receipts")
+            {
+                if (dgvList.Columns["receipt_id"] != null)
+                    dgvList.Columns["receipt_id"].HeaderText = "№";
+                if (dgvList.Columns["apartment_id"] != null)
+                    dgvList.Columns["apartment_id"].HeaderText = "ID квартиры";
+                if (dgvList.Columns["total_sum"] != null)
+                    dgvList.Columns["total_sum"].HeaderText = "Сумма";
+                if (dgvList.Columns["penalty"] != null)
+                    dgvList.Columns["penalty"].HeaderText = "Пени";
+                if (dgvList.Columns["status"] != null)
+                    dgvList.Columns["status"].HeaderText = "Статус";
+            }
+            // payments
+            else if (currentView == "payments")
+            {
+                if (dgvList.Columns["payment_id"] != null)
+                    dgvList.Columns["payment_id"].HeaderText = "ID";
+                if (dgvList.Columns["receipt_id"] != null)
+                    dgvList.Columns["receipt_id"].HeaderText = "ID квитанции";
+                if (dgvList.Columns["amount"] != null)
+                    dgvList.Columns["amount"].HeaderText = "Сумма";
+                if (dgvList.Columns["payment_date"] != null)
+                    dgvList.Columns["payment_date"].HeaderText = "Дата";
+                if (dgvList.Columns["payment_method"] != null)
+                    dgvList.Columns["payment_method"].HeaderText = "Способ";
+            }
+            // users
+            else if (currentView == "users")
+            {
+                if (dgvList.Columns["user_id"] != null)
+                    dgvList.Columns["user_id"].HeaderText = "ID";
+                if (dgvList.Columns["username"] != null)
+                    dgvList.Columns["username"].HeaderText = "Логин";
+                if (dgvList.Columns["role_id"] != null)
+                    dgvList.Columns["role_id"].HeaderText = "ID роли";
+                if (dgvList.Columns["person_id"] != null)
+                    dgvList.Columns["person_id"].HeaderText = "ID человека";
+            }
+            // operation_log
+            else if (currentView == "operation_log")
+            {
+                if (dgvList.Columns["log_id"] != null)
+                    dgvList.Columns["log_id"].HeaderText = "ID";
+                if (dgvList.Columns["table_name"] != null)
+                    dgvList.Columns["table_name"].HeaderText = "Таблица";
+                if (dgvList.Columns["operation"] != null)
+                    dgvList.Columns["operation"].HeaderText = "Операция";
+                if (dgvList.Columns["changed_by"] != null)
+                    dgvList.Columns["changed_by"].HeaderText = "Кто изменил";
+                if (dgvList.Columns["record_id"] != null)
+                    dgvList.Columns["record_id"].HeaderText = "ID записи";
+                if (dgvList.Columns["change_date"] != null)
+                    dgvList.Columns["change_date"].HeaderText = "Дата/время";
+            }
+        }
+
+        private void DisplayUserInfo()
+        {
+            lblUserInfo.Text = $"Администратор: {MainForm.CurrentUsername}";
+        }
+
+        private string GetPrimaryKeyName(string table)
+        {
+            switch (table)
+            {
+                case "apartments": return "apartment_id";
+                case "persons": return "person_id";
+                case "receipts": return "receipt_id";
+                case "payments": return "payment_id";
+                case "users": return "user_id";
+                default: return table + "_id";
+            }
+        }
+
+        private void BtnAdd_Click(object sender, EventArgs e)
+        {
+            if (currentView == "v_receipts_full" || currentView == "v_apartment_debt" ||
+                currentView == "v_payment_history" || currentView == "operation_log")
+            {
+                MessageBox.Show("Нельзя добавить запись в представление!");
+                return;
+            }
+            var editForm = new EditForm(currentView, null);
+            if (editForm.ShowDialog() == DialogResult.OK) LoadData();
+        }
+
+        private void BtnEdit_Click(object sender, EventArgs e)
+        {
+            if (currentView == "v_receipts_full" || currentView == "v_apartment_debt" ||
+                currentView == "v_payment_history" || currentView == "operation_log")
+            {
+                MessageBox.Show("Нельзя редактировать представление!");
+                return;
+            }
+            if (dgvList.CurrentRow == null) { MessageBox.Show("Выберите запись!"); return; }
+            string pkName = GetPrimaryKeyName(currentView);
+            int id = Convert.ToInt32(dgvList.CurrentRow.Cells[pkName].Value);
+            var editForm = new EditForm(currentView, id);
+            if (editForm.ShowDialog() == DialogResult.OK) LoadData();
+        }
+
+        private void BtnDelete_Click(object sender, EventArgs e)
+        {
+            if (currentView == "v_receipts_full" || currentView == "v_apartment_debt" ||
+                currentView == "v_payment_history" || currentView == "operation_log")
+            {
+                MessageBox.Show("Нельзя удалить запись из представления!");
+                return;
+            }
+            if (dgvList.CurrentRow == null) { MessageBox.Show("Выберите запись!"); return; }
+            if (MessageBox.Show("Удалить?", "Подтверждение", MessageBoxButtons.YesNo) == DialogResult.Yes)
+            {
+                try
+                {
+                    string pkName = GetPrimaryKeyName(currentView);
+                    int id = Convert.ToInt32(dgvList.CurrentRow.Cells[pkName].Value);
+                    using (var conn = new NpgsqlConnection(MainForm.ConnectionString))
+                    {
+                        conn.Open();
+                        using (var cmd = new NpgsqlCommand($"DELETE FROM {currentView} WHERE {pkName} = @id", conn))
+                        {
+                            cmd.Parameters.AddWithValue("@id", id);
+                            cmd.ExecuteNonQuery();
+                        }
+                    }
+                    LoadData();
+                    MessageBox.Show("Запись удалена!");
+                }
+                catch (Exception ex) { MessageBox.Show($"Ошибка: {ex.Message}"); }
+            }
+        }
+
+        private void BtnPayments_Click(object sender, EventArgs e)
+        {
+            currentView = "payments";
+            cbTables.SelectedItem = "Управление: Платежи";
+            LoadData();
+        }
+        private void BtnLog_Click(object sender, EventArgs e)
+        {
+            LogForm logForm = new LogForm();
+            logForm.ShowDialog();
+        }
+        private void BtnUsers_Click(object sender, EventArgs e)
+        {
+            currentView = "users";
+            cbTables.SelectedItem = "Управление: Пользователи";
+            LoadData();
+        }
+
+        private void BtnLogout_Click(object sender, EventArgs e)
+        {
+            Application.Restart();
+        }
+    }
+}

+ 120 - 0
ZhkhApp/AdminForm.resx

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 30 - 0
ZhkhApp/App.config

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
+    </startup>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="System.Numerics.Vectors" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.1.4.0" newVersion="4.1.4.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
+</configuration>

+ 36 - 0
ZhkhApp/DatabaseHelper.cs

@@ -0,0 +1,36 @@
+using Npgsql;
+using System.Data;
+
+namespace ZhkhApp
+{
+    public static class DatabaseHelper
+    {
+        public static DataTable ExecuteQuery(string query)
+        {
+            using (NpgsqlConnection conn = new NpgsqlConnection(MainForm.ConnectionString))
+            {
+                conn.Open();
+                using (NpgsqlDataAdapter adapter = new NpgsqlDataAdapter(query, conn))
+                {
+                    DataTable dt = new DataTable();
+                    adapter.Fill(dt);
+                    return dt;
+                }
+            }
+        }
+
+        public static int ExecuteNonQuery(string query, NpgsqlParameter[] parameters = null)
+        {
+            using (NpgsqlConnection conn = new NpgsqlConnection(MainForm.ConnectionString))
+            {
+                conn.Open();
+                using (NpgsqlCommand cmd = new NpgsqlCommand(query, conn))
+                {
+                    if (parameters != null)
+                        cmd.Parameters.AddRange(parameters);
+                    return cmd.ExecuteNonQuery();
+                }
+            }
+        }
+    }
+}

+ 60 - 0
ZhkhApp/EditForm.Designer.cs

@@ -0,0 +1,60 @@
+using System.Windows.Forms;
+
+namespace ZhkhApp
+{
+    partial class EditForm
+    {
+        private System.ComponentModel.IContainer components = null;
+        private Button btnSave, btnCancel;
+
+        // Объявление всех полей (ОБЯЗАТЕЛЬНО!)
+        private TextBox txtNumber, txtArea, txtResidentCount;
+        private TextBox txtLastName, txtFirstName, txtMiddleName;
+        private TextBox txtMonth, txtYear, txtPenalty, txtTotalSum;
+        private TextBox txtUsername, txtPassword;
+        private TextBox txtAmount;
+        private ComboBox cbAddress, cbApartment, cbPerson, cbRole;
+        private ComboBox cbReceipt, cbPaymentMethod;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+                components.Dispose();
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.btnSave = new Button();
+            this.btnCancel = new Button();
+            this.SuspendLayout();
+
+            // btnSave
+            this.btnSave.Text = "Сохранить";
+            this.btnSave.Location = new System.Drawing.Point(130, 420);
+            this.btnSave.Size = new System.Drawing.Size(100, 35);
+            this.btnSave.BackColor = System.Drawing.Color.LightGreen;
+            this.btnSave.Click += BtnSave_Click;
+
+            // btnCancel
+            this.btnCancel.Text = "Отмена";
+            this.btnCancel.Location = new System.Drawing.Point(250, 420);
+            this.btnCancel.Size = new System.Drawing.Size(100, 35);
+            this.btnCancel.BackColor = System.Drawing.Color.LightCoral;
+            this.btnCancel.Click += BtnCancel_Click;
+
+            // EditForm
+            this.Text = "Редактирование";
+            this.Size = new System.Drawing.Size(480, 500);
+            this.StartPosition = FormStartPosition.CenterParent;
+            this.BackColor = System.Drawing.Color.White;
+            this.FormBorderStyle = FormBorderStyle.FixedDialog;
+            this.MaximizeBox = false;
+
+            this.Controls.Add(this.btnSave);
+            this.Controls.Add(this.btnCancel);
+
+            this.ResumeLayout(false);
+        }
+    }
+}

+ 404 - 0
ZhkhApp/EditForm.cs

@@ -0,0 +1,404 @@
+using System;
+using System.Data;
+using System.Windows.Forms;
+using Npgsql;
+
+namespace ZhkhApp
+{
+    public partial class EditForm : Form
+    {
+        private string tableName;
+        private int? editId;
+
+        public EditForm(string table, int? id)
+        {
+            tableName = table;
+            editId = id;
+            InitializeComponent();
+            CreateDynamicFields();
+            LoadComboBoxes();
+
+            if (editId.HasValue)
+            {
+                this.Text = $"Редактирование: {GetRussianName()}";
+                LoadDataForEdit();
+            }
+            else
+            {
+                this.Text = $"Добавление: {GetRussianName()}";
+            }
+        }
+
+        private string GetRussianName()
+        {
+            switch (tableName)
+            {
+                case "apartments": return "Квартира";
+                case "persons": return "Человек";
+                case "receipts": return "Квитанция";
+                case "users": return "Пользователь";
+                case "payments": return "Платёж";
+                default: return tableName;
+            }
+        }
+
+        private string GetPrimaryKeyName(string table)
+        {
+            switch (table)
+            {
+                case "apartments": return "apartment_id";
+                case "persons": return "person_id";
+                case "receipts": return "receipt_id";
+                case "payments": return "payment_id";
+                case "users": return "user_id";
+                default: return table + "_id";
+            }
+        }
+
+        private void CreateDynamicFields()
+        {
+            int y = 30;
+
+            if (tableName == "apartments")
+            {
+                AddField("Номер квартиры:", ref y, out txtNumber);
+                AddField("Площадь (кв.м):", ref y, out txtArea);
+                AddField("Кол-во проживающих:", ref y, out txtResidentCount);
+                AddCombo("Адрес:", ref y, out cbAddress);
+            }
+            else if (tableName == "persons")
+            {
+                AddField("Фамилия:", ref y, out txtLastName);
+                AddField("Имя:", ref y, out txtFirstName);
+                AddField("Отчество:", ref y, out txtMiddleName);
+            }
+            else if (tableName == "receipts")
+            {
+                AddCombo("Квартира:", ref y, out cbApartment);
+                AddField("Месяц (1-12):", ref y, out txtMonth);
+                AddField("Год:", ref y, out txtYear);
+                AddField("Пени (руб):", ref y, out txtPenalty);
+                AddField("Сумма (руб):", ref y, out txtTotalSum);
+                AddCombo("Выписано на:", ref y, out cbPerson);
+            }
+            else if (tableName == "users")
+            {
+                AddField("Логин:", ref y, out txtUsername);
+                AddField("Пароль:", ref y, out txtPassword);
+                AddCombo("Роль:", ref y, out cbRole);
+                AddCombo("Связать с человеком:", ref y, out cbPerson);
+            }
+            else if (tableName == "payments")
+            {
+                AddCombo("Квитанция:", ref y, out cbReceipt);
+                AddField("Сумма оплаты:", ref y, out txtAmount);
+                AddCombo("Способ оплаты:", ref y, out cbPaymentMethod);
+            }
+        }
+
+        private void AddField(string labelText, ref int y, out TextBox tb)
+        {
+            Label lbl = new Label()
+            {
+                Text = labelText,
+                Location = new System.Drawing.Point(20, y),
+                Size = new System.Drawing.Size(150, 25)
+            };
+            tb = new TextBox()
+            {
+                Location = new System.Drawing.Point(180, y),
+                Size = new System.Drawing.Size(200, 25)
+            };
+            this.Controls.Add(lbl);
+            this.Controls.Add(tb);
+            y += 40;
+        }
+
+        private void AddCombo(string labelText, ref int y, out ComboBox cb)
+        {
+            Label lbl = new Label()
+            {
+                Text = labelText,
+                Location = new System.Drawing.Point(20, y),
+                Size = new System.Drawing.Size(150, 25)
+            };
+            cb = new ComboBox()
+            {
+                Location = new System.Drawing.Point(180, y),
+                Size = new System.Drawing.Size(200, 28),
+                DropDownStyle = ComboBoxStyle.DropDownList
+            };
+            this.Controls.Add(lbl);
+            this.Controls.Add(cb);
+            y += 40;
+        }
+
+        private void LoadComboBoxes()
+        {
+            try
+            {
+                if (cbAddress != null)
+                {
+                    using (var conn = new NpgsqlConnection(MainForm.ConnectionString))
+                    {
+                        conn.Open();
+                        string query = "SELECT address_id, street_name || ', д.' || house_number as addr FROM addresses JOIN streets ON addresses.street_id = streets.street_id";
+                        var da = new NpgsqlDataAdapter(query, conn);
+                        DataTable dt = new DataTable();
+                        da.Fill(dt);
+                        cbAddress.DisplayMember = "addr";
+                        cbAddress.ValueMember = "address_id";
+                        cbAddress.DataSource = dt;
+                    }
+                }
+                if (cbApartment != null)
+                {
+                    using (var conn = new NpgsqlConnection(MainForm.ConnectionString))
+                    {
+                        conn.Open();
+                        var da = new NpgsqlDataAdapter("SELECT apartment_id, apt_number FROM apartments", conn);
+                        DataTable dt = new DataTable();
+                        da.Fill(dt);
+                        cbApartment.DisplayMember = "apt_number";
+                        cbApartment.ValueMember = "apartment_id";
+                        cbApartment.DataSource = dt;
+                    }
+                }
+                if (cbPerson != null)
+                {
+                    using (var conn = new NpgsqlConnection(MainForm.ConnectionString))
+                    {
+                        conn.Open();
+                        var da = new NpgsqlDataAdapter("SELECT person_id, last_name || ' ' || first_name as name FROM persons", conn);
+                        DataTable dt = new DataTable();
+                        da.Fill(dt);
+                        cbPerson.DisplayMember = "name";
+                        cbPerson.ValueMember = "person_id";
+                        cbPerson.DataSource = dt;
+                    }
+                }
+                if (cbRole != null)
+                {
+                    using (var conn = new NpgsqlConnection(MainForm.ConnectionString))
+                    {
+                        conn.Open();
+                        var da = new NpgsqlDataAdapter("SELECT role_id, role_name FROM roles", conn);
+                        DataTable dt = new DataTable();
+                        da.Fill(dt);
+                        cbRole.DisplayMember = "role_name";
+                        cbRole.ValueMember = "role_id";
+                        cbRole.DataSource = dt;
+                    }
+                }
+                if (cbReceipt != null)
+                {
+                    using (var conn = new NpgsqlConnection(MainForm.ConnectionString))
+                    {
+                        conn.Open();
+                        string query = @"
+                            SELECT r.receipt_id, 
+                                   '№' || r.receipt_id || ' (кв.' || a.apt_number || ')' as receipt_info
+                            FROM receipts r
+                            JOIN apartments a ON r.apartment_id = a.apartment_id
+                            ORDER BY r.receipt_id";
+                        var da = new NpgsqlDataAdapter(query, conn);
+                        DataTable dt = new DataTable();
+                        da.Fill(dt);
+                        cbReceipt.DisplayMember = "receipt_info";
+                        cbReceipt.ValueMember = "receipt_id";
+                        cbReceipt.DataSource = dt;
+                    }
+                }
+                if (cbPaymentMethod != null)
+                {
+                    cbPaymentMethod.Items.Clear();
+                    cbPaymentMethod.Items.AddRange(new[] { "Банковская карта", "Наличные (в кассе)", "Онлайн-кошелёк" });
+                    cbPaymentMethod.DropDownStyle = ComboBoxStyle.DropDownList;
+                    cbPaymentMethod.SelectedIndex = 0;
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки справочников: {ex.Message}");
+            }
+        }
+
+        private void LoadDataForEdit()
+        {
+            try
+            {
+                string pkName = GetPrimaryKeyName(tableName);
+                using (var conn = new NpgsqlConnection(MainForm.ConnectionString))
+                {
+                    conn.Open();
+                    using (var cmd = new NpgsqlCommand($"SELECT * FROM {tableName} WHERE {pkName} = @id", conn))
+                    {
+                        cmd.Parameters.AddWithValue("@id", editId.Value);
+                        using (var reader = cmd.ExecuteReader())
+                        {
+                            if (reader.Read())
+                            {
+                                if (tableName == "apartments")
+                                {
+                                    txtNumber.Text = reader["apt_number"].ToString();
+                                    txtArea.Text = reader["area"].ToString();
+                                    txtResidentCount.Text = reader["resident_count"].ToString();
+                                    cbAddress.SelectedValue = reader["address_id"];
+                                }
+                                else if (tableName == "persons")
+                                {
+                                    txtLastName.Text = reader["last_name"].ToString();
+                                    txtFirstName.Text = reader["first_name"].ToString();
+                                    txtMiddleName.Text = reader["middle_name"].ToString();
+                                }
+                                else if (tableName == "receipts")
+                                {
+                                    cbApartment.SelectedValue = reader["apartment_id"];
+                                    txtMonth.Text = reader["period_month"].ToString();
+                                    txtYear.Text = reader["period_year"].ToString();
+                                    txtPenalty.Text = reader["penalty"].ToString();
+                                    txtTotalSum.Text = reader["total_sum"].ToString();
+                                    cbPerson.SelectedValue = reader["issued_to_person_id"];
+                                }
+                                else if (tableName == "users")
+                                {
+                                    txtUsername.Text = reader["username"].ToString();
+                                    txtPassword.Text = "";
+                                    cbRole.SelectedValue = reader["role_id"];
+                                    cbPerson.SelectedValue = reader["person_id"] != DBNull.Value ? reader["person_id"] : null;
+                                }
+                                else if (tableName == "payments")
+                                {
+                                    cbReceipt.SelectedValue = reader["receipt_id"];
+                                    txtAmount.Text = reader["amount"].ToString();
+                                    cbPaymentMethod.SelectedItem = reader["payment_method"].ToString();
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки данных: {ex.Message}");
+            }
+        }
+
+        private void BtnSave_Click(object sender, EventArgs e)
+        {
+            try
+            {
+                using (var conn = new NpgsqlConnection(MainForm.ConnectionString))
+                {
+                    conn.Open();
+
+                    var cmd = new NpgsqlCommand();
+                    cmd.Connection = conn;
+
+                    if (tableName == "apartments")
+                    {
+                        if (editId.HasValue)
+                        {
+                            cmd.CommandText = "UPDATE apartments SET apt_number=@n, area=@a, resident_count=@c, address_id=@ad WHERE apartment_id=@id";
+                            cmd.Parameters.AddWithValue("@id", editId.Value);
+                        }
+                        else
+                        {
+                            cmd.CommandText = "INSERT INTO apartments (apt_number, area, resident_count, address_id) VALUES (@n, @a, @c, @ad)";
+                        }
+                        cmd.Parameters.AddWithValue("@n", txtNumber.Text);
+                        cmd.Parameters.AddWithValue("@a", decimal.Parse(txtArea.Text));
+                        cmd.Parameters.AddWithValue("@c", int.Parse(txtResidentCount.Text));
+                        cmd.Parameters.AddWithValue("@ad", cbAddress.SelectedValue);
+                    }
+                    else if (tableName == "persons")
+                    {
+                        if (editId.HasValue)
+                        {
+                            cmd.CommandText = "UPDATE persons SET last_name=@l, first_name=@f, middle_name=@m WHERE person_id=@id";
+                            cmd.Parameters.AddWithValue("@id", editId.Value);
+                        }
+                        else
+                        {
+                            cmd.CommandText = "INSERT INTO persons (last_name, first_name, middle_name) VALUES (@l, @f, @m)";
+                        }
+                        cmd.Parameters.AddWithValue("@l", txtLastName.Text);
+                        cmd.Parameters.AddWithValue("@f", txtFirstName.Text);
+                        cmd.Parameters.AddWithValue("@m", txtMiddleName.Text);
+                    }
+                    else if (tableName == "receipts")
+                    {
+                        if (editId.HasValue)
+                        {
+                            cmd.CommandText = "UPDATE receipts SET apartment_id=@ap, period_month=@m, period_year=@y, penalty=@p, total_sum=@t, issued_to_person_id=@i WHERE receipt_id=@id";
+                            cmd.Parameters.AddWithValue("@id", editId.Value);
+                        }
+                        else
+                        {
+                            cmd.CommandText = "INSERT INTO receipts (apartment_id, period_month, period_year, penalty, total_sum, issued_to_person_id) VALUES (@ap, @m, @y, @p, @t, @i)";
+                        }
+                        cmd.Parameters.AddWithValue("@ap", cbApartment.SelectedValue);
+                        cmd.Parameters.AddWithValue("@m", int.Parse(txtMonth.Text));
+                        cmd.Parameters.AddWithValue("@y", int.Parse(txtYear.Text));
+                        cmd.Parameters.AddWithValue("@p", decimal.Parse(txtPenalty.Text == "" ? "0" : txtPenalty.Text));
+                        cmd.Parameters.AddWithValue("@t", decimal.Parse(txtTotalSum.Text));
+                        cmd.Parameters.AddWithValue("@i", cbPerson.SelectedValue);
+                    }
+                    else if (tableName == "payments")
+                    {
+                        if (editId.HasValue)
+                        {
+                            cmd.CommandText = "UPDATE payments SET receipt_id=@rid, amount=@amt, payment_method=@pm WHERE payment_id=@id";
+                            cmd.Parameters.AddWithValue("@id", editId.Value);
+                        }
+                        else
+                        {
+                            cmd.CommandText = "INSERT INTO payments (receipt_id, amount, payment_method) VALUES (@rid, @amt, @pm)";
+                        }
+                        cmd.Parameters.AddWithValue("@rid", cbReceipt.SelectedValue);
+                        cmd.Parameters.AddWithValue("@amt", decimal.Parse(txtAmount.Text));
+                        cmd.Parameters.AddWithValue("@pm", cbPaymentMethod.SelectedItem.ToString());
+                    }
+                    else if (tableName == "users")
+                    {
+                        if (editId.HasValue)
+                        {
+                            if (string.IsNullOrEmpty(txtPassword.Text))
+                            {
+                                cmd.CommandText = "UPDATE users SET username=@u, role_id=@r, person_id=@per WHERE user_id=@id";
+                            }
+                            else
+                            {
+                                cmd.CommandText = "UPDATE users SET username=@u, password_hash=MD5(@p), role_id=@r, person_id=@per WHERE user_id=@id";
+                                cmd.Parameters.AddWithValue("@p", txtPassword.Text);
+                            }
+                            cmd.Parameters.AddWithValue("@id", editId.Value);
+                        }
+                        else
+                        {
+                            cmd.CommandText = "INSERT INTO users (username, password_hash, role_id, person_id) VALUES (@u, MD5(@p), @r, @per)";
+                            cmd.Parameters.AddWithValue("@p", txtPassword.Text);
+                        }
+                        cmd.Parameters.AddWithValue("@u", txtUsername.Text);
+                        cmd.Parameters.AddWithValue("@r", cbRole.SelectedValue);
+                        cmd.Parameters.AddWithValue("@per", cbPerson.SelectedValue == null ? DBNull.Value : cbPerson.SelectedValue);
+                    }
+
+                    cmd.ExecuteNonQuery();
+                }
+                this.DialogResult = DialogResult.OK;
+                this.Close();
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка сохранения: {ex.Message}");
+            }
+        }
+
+        private void BtnCancel_Click(object sender, EventArgs e)
+        {
+            this.DialogResult = DialogResult.Cancel;
+            this.Close();
+        }
+    }
+}

+ 53 - 0
ZhkhApp/LogForm.Designer.cs

@@ -0,0 +1,53 @@
+using System.Windows.Forms;
+
+namespace ZhkhApp
+{
+    partial class LogForm
+    {
+        private DataGridView dgvLog;
+        private Button btnRefresh, btnClose;
+        private Label lblTitle;
+
+        private void InitializeComponent()
+        {
+            this.dgvLog = new DataGridView();
+            this.btnRefresh = new Button();
+            this.btnClose = new Button();
+            this.lblTitle = new Label();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvLog)).BeginInit();
+            this.SuspendLayout();
+
+            this.Text = "Журнал операций";
+            this.Size = new System.Drawing.Size(1000, 500);
+            this.StartPosition = FormStartPosition.CenterScreen;
+
+            this.lblTitle.Text = "Журнал изменений квитанций";
+            this.lblTitle.Font = new System.Drawing.Font("Segoe UI", 14, System.Drawing.FontStyle.Bold);
+            this.lblTitle.Location = new System.Drawing.Point(20, 20);
+            this.lblTitle.Size = new System.Drawing.Size(400, 30);
+
+            this.dgvLog.Location = new System.Drawing.Point(20, 60);
+            this.dgvLog.Size = new System.Drawing.Size(950, 380);
+            this.dgvLog.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
+            this.dgvLog.ReadOnly = true;
+
+            this.btnRefresh.Text = "Обновить";
+            this.btnRefresh.Location = new System.Drawing.Point(800, 450);
+            this.btnRefresh.Size = new System.Drawing.Size(80, 30);
+            this.btnRefresh.Click += BtnRefresh_Click;
+
+            this.btnClose.Text = "Закрыть";
+            this.btnClose.Location = new System.Drawing.Point(890, 450);
+            this.btnClose.Size = new System.Drawing.Size(80, 30);
+            this.btnClose.Click += BtnClose_Click;
+
+            this.Controls.Add(this.lblTitle);
+            this.Controls.Add(this.dgvLog);
+            this.Controls.Add(this.btnRefresh);
+            this.Controls.Add(this.btnClose);
+
+            ((System.ComponentModel.ISupportInitialize)(this.dgvLog)).EndInit();
+            this.ResumeLayout(false);
+        }
+    }
+}

+ 68 - 0
ZhkhApp/LogForm.cs

@@ -0,0 +1,68 @@
+using Npgsql;
+using System;
+using System.Data;
+using System.Windows.Forms;
+using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window;
+
+namespace ZhkhApp
+{
+    public partial class LogForm : Form
+    {
+        public LogForm()
+        {
+            InitializeComponent();
+            LoadLog();
+        }
+
+        private void LoadLog()
+        {
+            try
+            {
+                string query = @"
+                    SELECT log_id, table_name, operation, changed_by, record_id, change_date
+                    FROM operation_log
+                    ORDER BY change_date DESC
+                    LIMIT 500";
+
+                using (var conn = new NpgsqlConnection(MainForm.ConnectionString))
+                {
+                    conn.Open();
+                    using (var adapter = new NpgsqlDataAdapter(query, conn))
+                    {
+                        DataTable dt = new DataTable();
+                        adapter.Fill(dt);
+                        dgvLog.DataSource = dt;
+                    }
+                }
+
+                // Установка заголовков
+                if (dgvLog.Columns["log_id"] != null)
+                    dgvLog.Columns["log_id"].HeaderText = "ID";
+                if (dgvLog.Columns["table_name"] != null)
+                    dgvLog.Columns["table_name"].HeaderText = "Таблица";
+                if (dgvLog.Columns["operation"] != null)
+                    dgvLog.Columns["operation"].HeaderText = "Операция";
+                if (dgvLog.Columns["changed_by"] != null)
+                    dgvLog.Columns["changed_by"].HeaderText = "Пользователь";
+                if (dgvLog.Columns["record_id"] != null)
+                    dgvLog.Columns["record_id"].HeaderText = "ID записи";
+                if (dgvLog.Columns["change_date"] != null)
+                    dgvLog.Columns["change_date"].HeaderText = "Дата/время";
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки журнала: {ex.Message}");
+            }
+        }
+
+        private void BtnRefresh_Click(object sender, EventArgs e)
+        {
+            LoadLog();
+        }
+
+        private void BtnClose_Click(object sender, EventArgs e)
+        {
+            this.Close();
+        }
+    }
+}

+ 91 - 0
ZhkhApp/LoginForm.Designer.cs

@@ -0,0 +1,91 @@
+using System.Windows.Forms;
+
+namespace ZhkhApp
+{
+    partial class LoginForm
+    {
+        private System.ComponentModel.IContainer components = null;
+        private TextBox txtUsername;
+        private TextBox txtPassword;
+        private Button btnLogin;
+        private Button btnExit;
+        private Label lblStatus;
+        private Label lblTitle;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.txtUsername = new TextBox();
+            this.txtPassword = new TextBox();
+            this.btnLogin = new Button();
+            this.btnExit = new Button();
+            this.lblStatus = new Label();
+            this.lblTitle = new Label();
+            this.SuspendLayout();
+
+            // txtUsername
+            this.txtUsername.Location = new System.Drawing.Point(160, 80);
+            this.txtUsername.Size = new System.Drawing.Size(160, 23);
+
+            // txtPassword (без PasswordChar здесь, он будет установлен в коде)
+            this.txtPassword.Location = new System.Drawing.Point(160, 120);
+            this.txtPassword.Size = new System.Drawing.Size(160, 23);
+            this.txtPassword.UseSystemPasswordChar = true;  // вместо PasswordChar
+
+            // btnLogin
+            this.btnLogin.Text = "Войти";
+            this.btnLogin.Location = new System.Drawing.Point(80, 200);
+            this.btnLogin.Size = new System.Drawing.Size(90, 35);
+            this.btnLogin.BackColor = System.Drawing.Color.LightGreen;
+            this.btnLogin.Click += BtnLogin_Click;
+
+            // btnExit
+            this.btnExit.Text = "Выход";
+            this.btnExit.Location = new System.Drawing.Point(200, 200);
+            this.btnExit.Size = new System.Drawing.Size(90, 35);
+            this.btnExit.BackColor = System.Drawing.Color.LightCoral;
+            this.btnExit.Click += BtnExit_Click;
+
+            // lblStatus
+            this.lblStatus.Text = "";
+            this.lblStatus.Location = new System.Drawing.Point(50, 160);
+            this.lblStatus.Size = new System.Drawing.Size(270, 25);
+            this.lblStatus.ForeColor = System.Drawing.Color.Red;
+
+            // lblTitle
+            this.lblTitle.Text = "Система управления ЖКХ";
+            this.lblTitle.Font = new System.Drawing.Font("Segoe UI", 14, System.Drawing.FontStyle.Bold);
+            this.lblTitle.Location = new System.Drawing.Point(60, 20);
+            this.lblTitle.Size = new System.Drawing.Size(260, 30);
+            this.lblTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
+            this.lblTitle.ForeColor = System.Drawing.Color.DarkBlue;
+
+            // LoginForm
+            this.Text = "Авторизация - ЖКХ";
+            this.Size = new System.Drawing.Size(380, 280);
+            this.StartPosition = FormStartPosition.CenterScreen;
+            this.FormBorderStyle = FormBorderStyle.FixedDialog;
+            this.BackColor = System.Drawing.Color.WhiteSmoke;
+
+            this.Controls.Add(this.lblTitle);
+            this.Controls.Add(this.lblStatus);
+            this.Controls.Add(this.btnExit);
+            this.Controls.Add(this.btnLogin);
+            this.Controls.Add(this.txtPassword);
+            this.Controls.Add(this.txtUsername);
+            this.Controls.Add(new Label() { Text = "Пользователь:", Location = new System.Drawing.Point(50, 80), Size = new System.Drawing.Size(100, 25) });
+            this.Controls.Add(new Label() { Text = "Пароль:", Location = new System.Drawing.Point(50, 120), Size = new System.Drawing.Size(100, 25) });
+
+            this.ResumeLayout(false);
+            this.PerformLayout();
+        }
+    }
+}

+ 125 - 0
ZhkhApp/LoginForm.cs

@@ -0,0 +1,125 @@
+using System;
+using System.Drawing;
+using System.Security.Cryptography;
+using System.Text;
+using System.Windows.Forms;
+using Npgsql;
+
+namespace ZhkhApp
+{
+    public partial class LoginForm : Form
+    {
+        private string connectionString = "Host=edu-pg.itiscaf.ru;Port=5432;Database=dbwork;Username=grigoreva_va;Password=3&x28bky7d#;SSL Mode=Disable;";
+        private bool isPasswordVisible = false;
+        private Button btnTogglePassword;
+
+        public LoginForm()
+        {
+            InitializeComponent();
+            SetupPasswordToggle();
+        }
+
+        private void SetupPasswordToggle()
+        {
+            btnTogglePassword = new Button();
+            btnTogglePassword.Text = "👁️";
+            btnTogglePassword.Size = new Size(25, 23);
+            btnTogglePassword.FlatStyle = FlatStyle.Flat;
+            btnTogglePassword.BackColor = Color.Transparent;
+            btnTogglePassword.Cursor = Cursors.Hand;
+            btnTogglePassword.Click += BtnTogglePassword_Click;
+            btnTogglePassword.Location = new Point(txtPassword.Right - 25, txtPassword.Top + 1);
+            this.Controls.Add(btnTogglePassword);
+            btnTogglePassword.BringToFront();
+        }
+
+        private void BtnTogglePassword_Click(object sender, EventArgs e)
+        {
+            isPasswordVisible = !isPasswordVisible;
+            if (isPasswordVisible)
+            {
+                txtPassword.UseSystemPasswordChar = false;
+                btnTogglePassword.Text = "🙈";
+            }
+            else
+            {
+                txtPassword.UseSystemPasswordChar = true;
+                btnTogglePassword.Text = "👁️";
+            }
+        }
+
+        private void BtnLogin_Click(object sender, EventArgs e)
+        {
+            string username = txtUsername.Text.Trim();
+            string password = txtPassword.Text;
+
+            if (string.IsNullOrEmpty(username))
+            {
+                lblStatus.Text = "Введите имя пользователя!";
+                return;
+            }
+
+            if (string.IsNullOrEmpty(password))
+            {
+                lblStatus.Text = "Введите пароль!";
+                return;
+            }
+
+            if (AuthenticateUser(username, password))
+            {
+                this.DialogResult = DialogResult.OK;
+                this.Close();
+            }
+            else
+            {
+                lblStatus.Text = "Неверное имя пользователя или пароль!";
+            }
+        }
+
+        private bool AuthenticateUser(string username, string password)
+        {
+            string query = "SELECT * FROM authenticate_user(@username, @password)";
+
+            using (var conn = new NpgsqlConnection(connectionString))
+            {
+                try
+                {
+                    conn.Open();
+
+                    // Убираем SET app.current_user — он вызывает ошибку
+                    // Триггер будет использовать current_user (grigoreva_va)
+
+                    using (var cmd = new NpgsqlCommand(query, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@username", username);
+                        cmd.Parameters.AddWithValue("@password", password);
+
+                        using (var reader = cmd.ExecuteReader())
+                        {
+                            if (reader.Read())
+                            {
+                                MainForm.CurrentUserId = reader.GetInt32(0);
+                                MainForm.CurrentRole = reader.GetString(1);
+                                MainForm.CurrentUserFullName = reader.GetString(2);
+                                MainForm.CurrentUsername = username;
+                                MainForm.ConnectionString = connectionString;
+
+                                return true;
+                            }
+                        }
+                    }
+                }
+                catch (Exception ex)
+                {
+                    MessageBox.Show($"Ошибка подключения: {ex.Message}");
+                }
+            }
+            return false;
+        }
+
+        private void BtnExit_Click(object sender, EventArgs e)
+        {
+            Application.Exit();
+        }
+    }
+}

+ 90 - 0
ZhkhApp/MainForm.Designer.cs

@@ -0,0 +1,90 @@
+using System.Windows.Forms;
+
+namespace ZhkhApp
+{
+    partial class MainForm
+    {
+        private System.ComponentModel.IContainer components = null;
+        private DataGridView dgvList;
+        private ComboBox cbTables;
+        private Button btnLoad, btnAdd, btnEdit, btnDelete, btnRefresh, btnLogout;
+        private Label lblTitle, lblUserInfo;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null)) components.Dispose();
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.dgvList = new DataGridView();
+            this.cbTables = new ComboBox();
+            this.btnLoad = new Button();
+            this.btnAdd = new Button();
+            this.btnEdit = new Button();
+            this.btnDelete = new Button();
+            this.btnRefresh = new Button();
+            this.btnLogout = new Button();
+            this.lblTitle = new Label();
+            this.lblUserInfo = new Label();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvList)).BeginInit();
+            this.SuspendLayout();
+
+            // dgvList
+            this.dgvList.Location = new System.Drawing.Point(20, 115);
+            this.dgvList.Size = new System.Drawing.Size(1050, 490);
+            this.dgvList.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
+            this.dgvList.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
+            this.dgvList.MultiSelect = false;
+            this.dgvList.ReadOnly = true;
+
+            // cbTables
+            this.cbTables.Location = new System.Drawing.Point(150, 73);
+            this.cbTables.Size = new System.Drawing.Size(180, 28);
+            this.cbTables.DropDownStyle = ComboBoxStyle.DropDownList;
+            this.cbTables.SelectedIndexChanged += CbTables_SelectedIndexChanged;
+
+            // Buttons
+            this.btnLoad.Text = "Загрузить"; this.btnLoad.Location = new System.Drawing.Point(350, 70); this.btnLoad.Size = new System.Drawing.Size(100, 35); this.btnLoad.BackColor = System.Drawing.Color.LightBlue; this.btnLoad.Click += BtnLoad_Click;
+            this.btnAdd.Text = "Добавить"; this.btnAdd.Location = new System.Drawing.Point(460, 70); this.btnAdd.Size = new System.Drawing.Size(100, 35); this.btnAdd.BackColor = System.Drawing.Color.LightGreen; this.btnAdd.Click += BtnAdd_Click;
+            this.btnEdit.Text = "Изменить"; this.btnEdit.Location = new System.Drawing.Point(570, 70); this.btnEdit.Size = new System.Drawing.Size(100, 35); this.btnEdit.BackColor = System.Drawing.Color.LightYellow; this.btnEdit.Click += BtnEdit_Click;
+            this.btnDelete.Text = "Удалить"; this.btnDelete.Location = new System.Drawing.Point(680, 70); this.btnDelete.Size = new System.Drawing.Size(100, 35); this.btnDelete.BackColor = System.Drawing.Color.LightCoral; this.btnDelete.Click += BtnDelete_Click;
+            this.btnRefresh.Text = "Обновить"; this.btnRefresh.Location = new System.Drawing.Point(790, 70); this.btnRefresh.Size = new System.Drawing.Size(100, 35); this.btnRefresh.BackColor = System.Drawing.Color.LightGray; this.btnRefresh.Click += BtnRefresh_Click;
+            this.btnLogout.Text = "Выход"; this.btnLogout.Location = new System.Drawing.Point(900, 70); this.btnLogout.Size = new System.Drawing.Size(100, 35); this.btnLogout.BackColor = System.Drawing.Color.IndianRed; this.btnLogout.ForeColor = System.Drawing.Color.White; this.btnLogout.Click += BtnLogout_Click;
+
+            // lblTitle
+            this.lblTitle.Text = "Система учёта квартир и квитанций ЖКХ";
+            this.lblTitle.Font = new System.Drawing.Font("Segoe UI", 16, System.Drawing.FontStyle.Bold);
+            this.lblTitle.Location = new System.Drawing.Point(20, 20);
+            this.lblTitle.Size = new System.Drawing.Size(600, 40);
+            this.lblTitle.ForeColor = System.Drawing.Color.DarkBlue;
+
+            // lblUserInfo
+            this.lblUserInfo.Location = new System.Drawing.Point(750, 25);
+            this.lblUserInfo.Size = new System.Drawing.Size(300, 30);
+            this.lblUserInfo.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
+
+            // MainForm
+            this.Text = "ЖКХ - Управление";
+            this.Size = new System.Drawing.Size(1100, 650);
+            this.StartPosition = FormStartPosition.CenterScreen;
+            this.BackColor = System.Drawing.Color.WhiteSmoke;
+
+            this.Controls.Add(this.lblTitle);
+            this.Controls.Add(this.lblUserInfo);
+            this.Controls.Add(new Label() { Text = "Выберите таблицу:", Location = new System.Drawing.Point(20, 75), Size = new System.Drawing.Size(120, 25) });
+            this.Controls.Add(this.cbTables);
+            this.Controls.Add(this.btnLoad);
+            this.Controls.Add(this.btnAdd);
+            this.Controls.Add(this.btnEdit);
+            this.Controls.Add(this.btnDelete);
+            this.Controls.Add(this.btnRefresh);
+            this.Controls.Add(this.btnLogout);
+            this.Controls.Add(this.dgvList);
+
+            ((System.ComponentModel.ISupportInitialize)(this.dgvList)).EndInit();
+            this.ResumeLayout(false);
+        }
+    }
+}

+ 200 - 0
ZhkhApp/MainForm.cs

@@ -0,0 +1,200 @@
+using System;
+using System.Data;
+using System.Windows.Forms;
+using Npgsql;
+
+namespace ZhkhApp
+{
+    public partial class MainForm : Form
+    {
+        public static string ConnectionString { get; set; }
+        public static int CurrentUserId { get; set; }
+        public static string CurrentUsername { get; set; }
+        public static string CurrentUserFullName { get; set; }
+        public static string CurrentRole { get; set; }
+
+        private string currentTable = "apartments";
+
+        public MainForm()
+        {
+            InitializeComponent();
+            LoadTablesList();
+            LoadData();
+            SetUIPermissions();
+            DisplayUserInfo();
+        }
+
+        private string GetPrimaryKeyName(string tableName)
+        {
+            switch (tableName)
+            {
+                case "apartments": return "apartment_id";
+                case "persons": return "person_id";
+                case "receipts": return "receipt_id";
+                default: return tableName + "_id";
+            }
+        }
+
+        private void LoadTablesList()
+        {
+            cbTables.Items.Clear();
+            cbTables.Items.AddRange(new[] { "apartments", "persons", "receipts" });
+            cbTables.SelectedIndex = 0;
+        }
+
+        private void CbTables_SelectedIndexChanged(object sender, EventArgs e)
+        {
+            currentTable = cbTables.SelectedItem.ToString();
+            LoadData();
+        }
+
+        private void BtnLoad_Click(object sender, EventArgs e) => LoadData();
+        private void BtnRefresh_Click(object sender, EventArgs e) => LoadData();
+
+        private void LoadData()
+        {
+            try
+            {
+                string pkName = GetPrimaryKeyName(currentTable);
+                string query = $"SELECT * FROM {currentTable} ORDER BY {pkName}";
+
+                using (NpgsqlConnection conn = new NpgsqlConnection(ConnectionString))
+                {
+                    conn.Open();
+                    using (NpgsqlDataAdapter adapter = new NpgsqlDataAdapter(query, conn))
+                    {
+                        DataTable dt = new DataTable();
+                        adapter.Fill(dt);
+                        dgvList.DataSource = dt;
+                    }
+                }
+                SetRussianHeaders();
+                AddCalculatedColumn();
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
+            }
+        }
+
+        private void AddCalculatedColumn()
+        {
+            if (currentTable == "receipts" && dgvList.Columns["total_with_penalty"] == null)
+            {
+                DataGridViewTextBoxColumn calcColumn = new DataGridViewTextBoxColumn();
+                calcColumn.Name = "total_with_penalty";
+                calcColumn.HeaderText = "Итого с пеней";
+                calcColumn.ReadOnly = true;
+                dgvList.Columns.Add(calcColumn);
+
+                foreach (DataGridViewRow row in dgvList.Rows)
+                {
+                    if (row.Cells["total_sum"]?.Value != null && row.Cells["penalty"]?.Value != null)
+                    {
+                        decimal total = Convert.ToDecimal(row.Cells["total_sum"].Value);
+                        decimal penalty = Convert.ToDecimal(row.Cells["penalty"].Value);
+                        row.Cells["total_with_penalty"].Value = total + penalty;
+                    }
+                }
+            }
+        }
+
+        private void SetRussianHeaders()
+        {
+            if (currentTable == "apartments")
+            {
+                if (dgvList.Columns["apartment_id"] != null) dgvList.Columns["apartment_id"].HeaderText = "ID";
+                if (dgvList.Columns["apt_number"] != null) dgvList.Columns["apt_number"].HeaderText = "№ квартиры";
+                if (dgvList.Columns["area"] != null) dgvList.Columns["area"].HeaderText = "Площадь (кв.м)";
+                if (dgvList.Columns["resident_count"] != null) dgvList.Columns["resident_count"].HeaderText = "Проживает";
+            }
+            else if (currentTable == "persons")
+            {
+                if (dgvList.Columns["person_id"] != null) dgvList.Columns["person_id"].HeaderText = "ID";
+                if (dgvList.Columns["last_name"] != null) dgvList.Columns["last_name"].HeaderText = "Фамилия";
+                if (dgvList.Columns["first_name"] != null) dgvList.Columns["first_name"].HeaderText = "Имя";
+                if (dgvList.Columns["middle_name"] != null) dgvList.Columns["middle_name"].HeaderText = "Отчество";
+            }
+            else if (currentTable == "receipts")
+            {
+                if (dgvList.Columns["receipt_id"] != null) dgvList.Columns["receipt_id"].HeaderText = "№ квитанции";
+                if (dgvList.Columns["apartment_id"] != null) dgvList.Columns["apartment_id"].HeaderText = "ID квартиры";
+                if (dgvList.Columns["period_month"] != null) dgvList.Columns["period_month"].HeaderText = "Месяц";
+                if (dgvList.Columns["period_year"] != null) dgvList.Columns["period_year"].HeaderText = "Год";
+                if (dgvList.Columns["penalty"] != null) dgvList.Columns["penalty"].HeaderText = "Пени";
+                if (dgvList.Columns["total_sum"] != null) dgvList.Columns["total_sum"].HeaderText = "Сумма";
+            }
+        }
+
+        private void SetUIPermissions()
+        {
+            if (CurrentRole == "guest")
+            {
+                btnAdd.Enabled = false;
+                btnEdit.Enabled = false;
+                btnDelete.Enabled = false;
+            }
+        }
+
+        private void DisplayUserInfo()
+        {
+            lblUserInfo.Text = $"Пользователь: {CurrentUsername} ({CurrentRole})";
+        }
+
+        private void BtnAdd_Click(object sender, EventArgs e)
+        {
+            var editForm = new EditForm(currentTable, null);
+            if (editForm.ShowDialog() == DialogResult.OK) LoadData();
+        }
+
+        private void BtnEdit_Click(object sender, EventArgs e)
+        {
+            if (dgvList.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите запись!", "Внимание");
+                return;
+            }
+
+            string pkName = GetPrimaryKeyName(currentTable);
+            int id = Convert.ToInt32(dgvList.CurrentRow.Cells[pkName].Value);
+            var editForm = new EditForm(currentTable, id);
+            if (editForm.ShowDialog() == DialogResult.OK) LoadData();
+        }
+
+        private void BtnDelete_Click(object sender, EventArgs e)
+        {
+            if (dgvList.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите запись!", "Внимание");
+                return;
+            }
+
+            if (MessageBox.Show("Удалить?", "Подтверждение", MessageBoxButtons.YesNo) == DialogResult.Yes)
+            {
+                try
+                {
+                    string pkName = GetPrimaryKeyName(currentTable);
+                    int id = Convert.ToInt32(dgvList.CurrentRow.Cells[pkName].Value);
+
+                    using (NpgsqlConnection conn = new NpgsqlConnection(ConnectionString))
+                    {
+                        conn.Open();
+                        using (NpgsqlCommand cmd = new NpgsqlCommand($"DELETE FROM {currentTable} WHERE {pkName} = @id", conn))
+                        {
+                            cmd.Parameters.AddWithValue("@id", id);
+                            cmd.ExecuteNonQuery();
+                        }
+                    }
+                    LoadData();
+                    MessageBox.Show("Запись удалена!", "Успех");
+                }
+                catch (Exception ex)
+                {
+                    MessageBox.Show($"Ошибка: {ex.Message}");
+                }
+            }
+        }
+
+        private void BtnLogout_Click(object sender, EventArgs e) => Application.Restart();
+    }
+}

+ 102 - 0
ZhkhApp/OperatorForm.Designer.cs

@@ -0,0 +1,102 @@
+using System.Windows.Forms;
+
+namespace ZhkhApp
+{
+    partial class OperatorForm
+    {
+        private System.ComponentModel.IContainer components = null;
+        private DataGridView dgvList;
+        private ComboBox cbTables;
+        private Button btnLoad, btnAdd, btnEdit, btnDelete, btnRefresh, btnLogout, btnPayments;
+        private Label lblTitle, lblUserInfo;
+        private Button btnReports;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null)) components.Dispose();
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.dgvList = new DataGridView();
+            this.cbTables = new ComboBox();
+            this.btnLoad = new Button();
+            this.btnAdd = new Button();
+            this.btnEdit = new Button();
+            this.btnDelete = new Button();
+            this.btnRefresh = new Button();
+            this.btnPayments = new Button();
+            this.btnLogout = new Button();
+            this.lblTitle = new Label();
+            this.lblUserInfo = new Label();
+            this.btnReports = new Button();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvList)).BeginInit();
+            this.SuspendLayout();
+
+            // dgvList
+            this.dgvList.Location = new System.Drawing.Point(20, 120);
+            this.dgvList.Size = new System.Drawing.Size(1050, 490);
+            this.dgvList.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
+            this.dgvList.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
+            this.dgvList.MultiSelect = false;
+            this.dgvList.ReadOnly = true;
+
+            // cbTables
+            this.cbTables.Location = new System.Drawing.Point(95, 73);
+            this.cbTables.Size = new System.Drawing.Size(150, 28);
+            this.cbTables.DropDownStyle = ComboBoxStyle.DropDownList;
+            this.cbTables.SelectedIndexChanged += CbTables_SelectedIndexChanged;
+
+            // Buttons
+            this.btnLoad.Text = "Загрузить"; this.btnLoad.Location = new System.Drawing.Point(260, 70); this.btnLoad.Size = new System.Drawing.Size(90, 35); this.btnLoad.BackColor = System.Drawing.Color.LightBlue; this.btnLoad.Click += BtnLoad_Click;
+            this.btnAdd.Text = "Добавить"; this.btnAdd.Location = new System.Drawing.Point(360, 70); this.btnAdd.Size = new System.Drawing.Size(90, 35); this.btnAdd.BackColor = System.Drawing.Color.LightGreen; this.btnAdd.Click += BtnAdd_Click;
+            this.btnEdit.Text = "Изменить"; this.btnEdit.Location = new System.Drawing.Point(460, 70); this.btnEdit.Size = new System.Drawing.Size(90, 35); this.btnEdit.BackColor = System.Drawing.Color.LightYellow; this.btnEdit.Click += BtnEdit_Click;
+            this.btnDelete.Text = "Удалить"; this.btnDelete.Location = new System.Drawing.Point(560, 70); this.btnDelete.Size = new System.Drawing.Size(90, 35); this.btnDelete.BackColor = System.Drawing.Color.LightCoral; this.btnDelete.Click += BtnDelete_Click;
+            this.btnRefresh.Text = "Обновить"; this.btnRefresh.Location = new System.Drawing.Point(660, 70); this.btnRefresh.Size = new System.Drawing.Size(90, 35); this.btnRefresh.BackColor = System.Drawing.Color.LightGray; this.btnRefresh.Click += BtnRefresh_Click;
+            this.btnPayments.Text = "Принять оплату"; this.btnPayments.Location = new System.Drawing.Point(760, 70); this.btnPayments.Size = new System.Drawing.Size(120, 35); this.btnPayments.BackColor = System.Drawing.Color.LightGoldenrodYellow; this.btnPayments.Click += BtnPayments_Click;
+            this.btnLogout.Text = "Выход"; this.btnLogout.Location = new System.Drawing.Point(900, 70); this.btnLogout.Size = new System.Drawing.Size(90, 35); this.btnLogout.BackColor = System.Drawing.Color.IndianRed; this.btnLogout.ForeColor = System.Drawing.Color.White; this.btnLogout.Click += BtnLogout_Click;
+
+
+            this.btnReports = new Button();
+            this.btnReports.Text = "Отчёты";
+            this.btnReports.Location = new System.Drawing.Point(700, 70);
+            this.btnReports.Size = new System.Drawing.Size(100, 35);
+            this.btnReports.Click += BtnReports_Click;
+
+            // lblTitle
+            this.lblTitle.Text = "Система учёта ЖКХ (Оператор)";
+            this.lblTitle.Font = new System.Drawing.Font("Segoe UI", 16, System.Drawing.FontStyle.Bold);
+            this.lblTitle.Location = new System.Drawing.Point(20, 20);
+            this.lblTitle.Size = new System.Drawing.Size(600, 40);
+            this.lblTitle.ForeColor = System.Drawing.Color.DarkGreen;
+
+            // lblUserInfo
+            this.lblUserInfo.Location = new System.Drawing.Point(750, 25);
+            this.lblUserInfo.Size = new System.Drawing.Size(300, 30);
+            this.lblUserInfo.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
+
+            // OperatorForm
+            this.Text = "ЖКХ - Оператор";
+            this.Size = new System.Drawing.Size(1100, 650);
+            this.StartPosition = FormStartPosition.CenterScreen;
+            this.BackColor = System.Drawing.Color.WhiteSmoke;
+
+            this.Controls.Add(this.lblTitle);
+            this.Controls.Add(this.lblUserInfo);
+            this.Controls.Add(new Label() { Text = "Таблица:", Location = new System.Drawing.Point(20, 75), Size = new System.Drawing.Size(70, 25) });
+            this.Controls.Add(this.cbTables);
+            this.Controls.Add(this.btnLoad);
+            this.Controls.Add(this.btnAdd);
+            this.Controls.Add(this.btnEdit);
+            this.Controls.Add(this.btnDelete);
+            this.Controls.Add(this.btnRefresh);
+            this.Controls.Add(this.btnPayments);
+            this.Controls.Add(this.btnLogout);
+            this.Controls.Add(this.dgvList);
+
+            ((System.ComponentModel.ISupportInitialize)(this.dgvList)).EndInit();
+            this.ResumeLayout(false);
+        }
+    }
+}

+ 228 - 0
ZhkhApp/OperatorForm.cs

@@ -0,0 +1,228 @@
+using System;
+using System.Data;
+using System.Windows.Forms;
+using Npgsql;
+
+namespace ZhkhApp
+{
+    public partial class OperatorForm : Form
+    {
+        private string currentView = "v_receipts_full";  // вместо таблицы receipts
+
+        public OperatorForm()
+        {
+            InitializeComponent();
+            LoadViewsList();
+            LoadData();
+            DisplayUserInfo();
+        }
+
+        // Загружаем представления, а не таблицы
+        private void LoadViewsList()
+        {
+            cbTables.Items.Clear();
+            cbTables.Items.AddRange(new[] {
+                "Квитанции (полные)",
+                "Задолженность по квартирам",
+                "История платежей"
+            });
+            cbTables.SelectedIndex = 0;
+        }
+
+        private void CbTables_SelectedIndexChanged(object sender, EventArgs e)
+        {
+            switch (cbTables.SelectedIndex)
+            {
+                case 0: currentView = "v_receipts_full"; break;
+                case 1: currentView = "v_apartment_debt"; break;
+                case 2: currentView = "v_payment_history"; break;
+            }
+            LoadData();
+        }
+
+        private void BtnLoad_Click(object sender, EventArgs e) => LoadData();
+        private void BtnRefresh_Click(object sender, EventArgs e) => LoadData();
+
+        private void LoadData()
+        {
+            try
+            {
+                string query = $"SELECT * FROM {currentView}";
+
+                using (var conn = new NpgsqlConnection(MainForm.ConnectionString))
+                {
+                    conn.Open();
+                    using (var adapter = new NpgsqlDataAdapter(query, conn))
+                    {
+                        DataTable dt = new DataTable();
+                        adapter.Fill(dt);
+                        dgvList.DataSource = dt;
+                    }
+                }
+                SetRussianHeaders();
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}");
+            }
+        }
+
+        private void SetRussianHeaders()
+        {
+            if (currentView == "v_receipts_full")
+            {
+                if (dgvList.Columns["receipt_id"] != null)
+                    dgvList.Columns["receipt_id"].HeaderText = "№";
+                if (dgvList.Columns["apt_number"] != null)
+                    dgvList.Columns["apt_number"].HeaderText = "Квартира";
+                if (dgvList.Columns["full_address"] != null)
+                    dgvList.Columns["full_address"].HeaderText = "Адрес";
+                if (dgvList.Columns["period_month"] != null)
+                    dgvList.Columns["period_month"].HeaderText = "Месяц";
+                if (dgvList.Columns["period_year"] != null)
+                    dgvList.Columns["period_year"].HeaderText = "Год";
+                if (dgvList.Columns["total_sum"] != null)
+                    dgvList.Columns["total_sum"].HeaderText = "Сумма, руб";
+                if (dgvList.Columns["penalty"] != null)
+                    dgvList.Columns["penalty"].HeaderText = "Пени, руб";
+                if (dgvList.Columns["total_with_penalty"] != null)
+                    dgvList.Columns["total_with_penalty"].HeaderText = "Итого, руб";
+                if (dgvList.Columns["status"] != null)
+                    dgvList.Columns["status"].HeaderText = "Статус";
+                if (dgvList.Columns["debtor_name"] != null)
+                    dgvList.Columns["debtor_name"].HeaderText = "Должник";
+            }
+            else if (currentView == "v_apartment_debt")
+            {
+                if (dgvList.Columns["apt_number"] != null)
+                    dgvList.Columns["apt_number"].HeaderText = "Квартира";
+                if (dgvList.Columns["address"] != null)
+                    dgvList.Columns["address"].HeaderText = "Адрес";
+                if (dgvList.Columns["total_receipts"] != null)
+                    dgvList.Columns["total_receipts"].HeaderText = "Всего квитанций";
+                if (dgvList.Columns["total_accrued"] != null)
+                    dgvList.Columns["total_accrued"].HeaderText = "Начислено, руб";
+                if (dgvList.Columns["total_paid"] != null)
+                    dgvList.Columns["total_paid"].HeaderText = "Оплачено, руб";
+                if (dgvList.Columns["current_debt"] != null)
+                    dgvList.Columns["current_debt"].HeaderText = "Задолженность, руб";
+            }
+            else if (currentView == "v_payment_history")
+            {
+                if (dgvList.Columns["payment_id"] != null)
+                    dgvList.Columns["payment_id"].HeaderText = "№ платежа";
+                if (dgvList.Columns["apt_number"] != null)
+                    dgvList.Columns["apt_number"].HeaderText = "Квартира";
+                if (dgvList.Columns["payer_name"] != null)
+                    dgvList.Columns["payer_name"].HeaderText = "Плательщик";
+                if (dgvList.Columns["period_month"] != null)
+                    dgvList.Columns["period_month"].HeaderText = "Месяц";
+                if (dgvList.Columns["period_year"] != null)
+                    dgvList.Columns["period_year"].HeaderText = "Год";
+                if (dgvList.Columns["payment_date"] != null)
+                    dgvList.Columns["payment_date"].HeaderText = "Дата оплаты";
+                if (dgvList.Columns["amount"] != null)
+                    dgvList.Columns["amount"].HeaderText = "Сумма, руб";
+                if (dgvList.Columns["payment_method"] != null)
+                    dgvList.Columns["payment_method"].HeaderText = "Способ оплаты";
+            }
+        }
+
+        private void DisplayUserInfo()
+        {
+            lblUserInfo.Text = $"Оператор: {MainForm.CurrentUsername}";
+        }
+
+        // Добавление/редактирование/удаление только для квитанций
+        private void BtnAdd_Click(object sender, EventArgs e)
+        {
+            if (currentView != "v_receipts_full")
+            {
+                MessageBox.Show("Добавление доступно только для квитанций!");
+                return;
+            }
+            var editForm = new EditForm("receipts", null);
+            if (editForm.ShowDialog() == DialogResult.OK) LoadData();
+        }
+
+        private void BtnEdit_Click(object sender, EventArgs e)
+        {
+            if (currentView != "v_receipts_full")
+            {
+                MessageBox.Show("Редактирование доступно только для квитанций!");
+                return;
+            }
+            if (dgvList.CurrentRow == null) { MessageBox.Show("Выберите запись!"); return; }
+
+            int id = Convert.ToInt32(dgvList.CurrentRow.Cells["receipt_id"].Value);
+            var editForm = new EditForm("receipts", id);
+            if (editForm.ShowDialog() == DialogResult.OK) LoadData();
+        }
+
+        private void BtnDelete_Click(object sender, EventArgs e)
+        {
+            if (currentView != "v_receipts_full")
+            {
+                MessageBox.Show("Удаление доступно только для квитанций!");
+                return;
+            }
+            if (dgvList.CurrentRow == null) { MessageBox.Show("Выберите запись!"); return; }
+            if (MessageBox.Show("Удалить?", "Подтверждение", MessageBoxButtons.YesNo) == DialogResult.Yes)
+            {
+                try
+                {
+                    int id = Convert.ToInt32(dgvList.CurrentRow.Cells["receipt_id"].Value);
+                    using (var conn = new NpgsqlConnection(MainForm.ConnectionString))
+                    {
+                        conn.Open();
+                        using (var cmd = new NpgsqlCommand($"DELETE FROM receipts WHERE receipt_id = @id", conn))
+                        {
+                            cmd.Parameters.AddWithValue("@id", id);
+                            cmd.ExecuteNonQuery();
+                        }
+                    }
+                    LoadData();
+                    MessageBox.Show("Запись удалена!");
+                }
+                catch (Exception ex) { MessageBox.Show($"Ошибка: {ex.Message}"); }
+            }
+        }
+
+        // Приём оплаты
+        private void BtnReports_Click(object sender, EventArgs e)
+        {
+            ReportForm reportForm = new ReportForm();
+            reportForm.ShowDialog();
+        }
+        private void BtnPayments_Click(object sender, EventArgs e)
+        {
+            if (dgvList.CurrentRow == null || currentView != "v_receipts_full")
+            {
+                MessageBox.Show("Выберите квитанцию для оплаты!");
+                return;
+            }
+
+            int receiptId = Convert.ToInt32(dgvList.CurrentRow.Cells["receipt_id"].Value);
+            decimal totalWithPenalty = Convert.ToDecimal(dgvList.CurrentRow.Cells["total_with_penalty"].Value);
+            decimal debt = Convert.ToDecimal(dgvList.CurrentRow.Cells["remaining_amount"]);
+
+            if (debt <= 0)
+            {
+                MessageBox.Show("Эта квитанция уже оплачена!");
+                return;
+            }
+
+            var paymentForm = new PaymentForm(receiptId, totalWithPenalty, 0);
+            if (paymentForm.ShowDialog() == DialogResult.OK)
+            {
+                LoadData();
+                MessageBox.Show("Платёж принят!");
+            }
+        }
+
+        private void BtnLogout_Click(object sender, EventArgs e)
+        {
+            Application.Restart();
+        }
+    }
+}

+ 217 - 0
ZhkhApp/PaymentForm.Designer.cs

@@ -0,0 +1,217 @@
+using System.Windows.Forms;
+
+namespace ZhkhApp
+{
+    partial class PaymentForm
+    {
+        private System.ComponentModel.IContainer components = null;
+        private Label lblTitle, lblReceiptInfo, lblAmountToPay, lblPenaltyInfo;
+        private Label lblMethod, lblStatus, lblCardNumber, lblCardExpiry, lblCardCvv;
+        private TextBox txtAmount, txtCardNumber, txtCardExpiry, txtCardCvv;
+        private ComboBox cbMethod;
+        private Button btnPay, btnCancel;
+        private Panel pnlCardDetails;
+        private Label lblAmountLabel;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+                components.Dispose();
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.lblTitle = new Label();
+            this.lblReceiptInfo = new Label();
+            this.lblAmountToPay = new Label();
+            this.lblPenaltyInfo = new Label();
+            this.lblMethod = new Label();
+            this.lblStatus = new Label();
+            this.txtAmount = new TextBox();
+            this.cbMethod = new ComboBox();
+            this.btnPay = new Button();
+            this.btnCancel = new Button();
+            this.pnlCardDetails = new Panel();
+            this.lblCardNumber = new Label();
+            this.txtCardNumber = new TextBox();
+            this.lblCardExpiry = new Label();
+            this.txtCardExpiry = new TextBox();
+            this.lblCardCvv = new Label();
+            this.txtCardCvv = new TextBox();
+            this.lblAmountLabel = new Label();
+            this.pnlCardDetails.SuspendLayout();
+            this.SuspendLayout();
+
+            // lblTitle
+            this.lblTitle.Font = new System.Drawing.Font("Segoe UI", 14F, System.Drawing.FontStyle.Bold);
+            this.lblTitle.ForeColor = System.Drawing.Color.DarkBlue;
+            this.lblTitle.Location = new System.Drawing.Point(20, 20);
+            this.lblTitle.Name = "lblTitle";
+            this.lblTitle.Size = new System.Drawing.Size(440, 30);
+            this.lblTitle.TabIndex = 1;
+            this.lblTitle.Text = "Оплата жилищно-коммунальных услуг";
+            this.lblTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
+
+            // lblReceiptInfo
+            this.lblReceiptInfo.Location = new System.Drawing.Point(20, 60);
+            this.lblReceiptInfo.Name = "lblReceiptInfo";
+            this.lblReceiptInfo.Size = new System.Drawing.Size(440, 25);
+            this.lblReceiptInfo.TabIndex = 2;
+            this.lblReceiptInfo.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
+
+            // lblAmountToPay
+            this.lblAmountToPay.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Bold);
+            this.lblAmountToPay.Location = new System.Drawing.Point(50, 100);
+            this.lblAmountToPay.Name = "lblAmountToPay";
+            this.lblAmountToPay.Size = new System.Drawing.Size(400, 25);
+            this.lblAmountToPay.TabIndex = 3;
+
+            // lblPenaltyInfo
+            this.lblPenaltyInfo.ForeColor = System.Drawing.Color.DarkRed;
+            this.lblPenaltyInfo.Location = new System.Drawing.Point(50, 130);
+            this.lblPenaltyInfo.Name = "lblPenaltyInfo";
+            this.lblPenaltyInfo.Size = new System.Drawing.Size(400, 25);
+            this.lblPenaltyInfo.TabIndex = 4;
+
+            // lblMethod
+            this.lblMethod.Location = new System.Drawing.Point(50, 210);
+            this.lblMethod.Name = "lblMethod";
+            this.lblMethod.Size = new System.Drawing.Size(120, 25);
+            this.lblMethod.TabIndex = 6;
+            this.lblMethod.Text = "Способ оплаты:";
+
+            // lblStatus
+            this.lblStatus.ForeColor = System.Drawing.Color.Red;
+            this.lblStatus.Location = new System.Drawing.Point(50, 390);
+            this.lblStatus.Name = "lblStatus";
+            this.lblStatus.Size = new System.Drawing.Size(400, 30);
+            this.lblStatus.TabIndex = 9;
+            this.lblStatus.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
+
+            // txtAmount
+            this.txtAmount.Location = new System.Drawing.Point(180, 168);
+            this.txtAmount.Name = "txtAmount";
+            this.txtAmount.Size = new System.Drawing.Size(150, 20);
+            this.txtAmount.TabIndex = 5;
+
+            // cbMethod
+            this.cbMethod.DropDownStyle = ComboBoxStyle.DropDownList;
+            this.cbMethod.Location = new System.Drawing.Point(180, 208);
+            this.cbMethod.Name = "cbMethod";
+            this.cbMethod.Size = new System.Drawing.Size(200, 21);
+            this.cbMethod.TabIndex = 7;
+            this.cbMethod.SelectedIndexChanged += CbMethod_SelectedIndexChanged;
+
+            // btnPay
+            this.btnPay.BackColor = System.Drawing.Color.LightGreen;
+            this.btnPay.Location = new System.Drawing.Point(100, 423);
+            this.btnPay.Name = "btnPay";
+            this.btnPay.Size = new System.Drawing.Size(120, 35);
+            this.btnPay.TabIndex = 10;
+            this.btnPay.Text = "Оплатить";
+            this.btnPay.UseVisualStyleBackColor = false;
+            this.btnPay.Click += BtnPay_Click;  // <-- ДОБАВИТЬ
+
+            // btnCancel
+            this.btnCancel.BackColor = System.Drawing.Color.LightCoral;
+            this.btnCancel.Location = new System.Drawing.Point(251, 423);
+            this.btnCancel.Name = "btnCancel";
+            this.btnCancel.Size = new System.Drawing.Size(120, 35);
+            this.btnCancel.TabIndex = 11;
+            this.btnCancel.Text = "Отмена";
+            this.btnCancel.UseVisualStyleBackColor = false;
+            this.btnCancel.Click += BtnCancel_Click;  // <-- ДОБАВИТЬ
+
+            // pnlCardDetails
+            this.pnlCardDetails.BorderStyle = BorderStyle.FixedSingle;
+            this.pnlCardDetails.Controls.Add(this.lblCardNumber);
+            this.pnlCardDetails.Controls.Add(this.txtCardNumber);
+            this.pnlCardDetails.Controls.Add(this.lblCardExpiry);
+            this.pnlCardDetails.Controls.Add(this.txtCardExpiry);
+            this.pnlCardDetails.Controls.Add(this.lblCardCvv);
+            this.pnlCardDetails.Controls.Add(this.txtCardCvv);
+            this.pnlCardDetails.Location = new System.Drawing.Point(30, 250);
+            this.pnlCardDetails.Name = "pnlCardDetails";
+            this.pnlCardDetails.Size = new System.Drawing.Size(420, 120);
+            this.pnlCardDetails.TabIndex = 8;
+
+            // lblCardNumber
+            this.lblCardNumber.Location = new System.Drawing.Point(20, 15);
+            this.lblCardNumber.Name = "lblCardNumber";
+            this.lblCardNumber.Size = new System.Drawing.Size(100, 25);
+            this.lblCardNumber.TabIndex = 0;
+            this.lblCardNumber.Text = "Номер карты:";
+
+            // txtCardNumber
+            this.txtCardNumber.Location = new System.Drawing.Point(130, 13);
+            this.txtCardNumber.MaxLength = 19;
+            this.txtCardNumber.Name = "txtCardNumber";
+            this.txtCardNumber.Size = new System.Drawing.Size(200, 20);
+            this.txtCardNumber.TabIndex = 1;
+            this.txtCardNumber.TextChanged += TxtCardNumber_TextChanged;
+
+            // lblCardExpiry
+            this.lblCardExpiry.Location = new System.Drawing.Point(20, 50);
+            this.lblCardExpiry.Name = "lblCardExpiry";
+            this.lblCardExpiry.Size = new System.Drawing.Size(100, 25);
+            this.lblCardExpiry.TabIndex = 2;
+            this.lblCardExpiry.Text = "Срок (MM/ГГ):";
+
+            // txtCardExpiry
+            this.txtCardExpiry.Location = new System.Drawing.Point(130, 48);
+            this.txtCardExpiry.MaxLength = 5;
+            this.txtCardExpiry.Name = "txtCardExpiry";
+            this.txtCardExpiry.Size = new System.Drawing.Size(80, 20);
+            this.txtCardExpiry.TabIndex = 3;
+
+            // lblCardCvv
+            this.lblCardCvv.Location = new System.Drawing.Point(230, 50);
+            this.lblCardCvv.Name = "lblCardCvv";
+            this.lblCardCvv.Size = new System.Drawing.Size(50, 25);
+            this.lblCardCvv.TabIndex = 4;
+            this.lblCardCvv.Text = "CVV:";
+
+            // txtCardCvv
+            this.txtCardCvv.Location = new System.Drawing.Point(280, 48);
+            this.txtCardCvv.MaxLength = 3;
+            this.txtCardCvv.Name = "txtCardCvv";
+            this.txtCardCvv.PasswordChar = '*';
+            this.txtCardCvv.Size = new System.Drawing.Size(60, 20);
+            this.txtCardCvv.TabIndex = 5;
+
+            // lblAmountLabel
+            this.lblAmountLabel.Location = new System.Drawing.Point(50, 170);
+            this.lblAmountLabel.Name = "lblAmountLabel";
+            this.lblAmountLabel.Size = new System.Drawing.Size(120, 25);
+            this.lblAmountLabel.TabIndex = 0;
+            this.lblAmountLabel.Text = "Сумма оплаты:";
+
+            // PaymentForm
+            this.BackColor = System.Drawing.Color.White;
+            this.ClientSize = new System.Drawing.Size(464, 481);
+            this.Controls.Add(this.lblAmountLabel);
+            this.Controls.Add(this.lblTitle);
+            this.Controls.Add(this.lblReceiptInfo);
+            this.Controls.Add(this.lblAmountToPay);
+            this.Controls.Add(this.lblPenaltyInfo);
+            this.Controls.Add(this.txtAmount);
+            this.Controls.Add(this.lblMethod);
+            this.Controls.Add(this.cbMethod);
+            this.Controls.Add(this.pnlCardDetails);
+            this.Controls.Add(this.lblStatus);
+            this.Controls.Add(this.btnPay);
+            this.Controls.Add(this.btnCancel);
+            this.FormBorderStyle = FormBorderStyle.FixedDialog;
+            this.MaximizeBox = false;
+            this.Name = "PaymentForm";
+            this.StartPosition = FormStartPosition.CenterParent;
+            this.Text = "Оплата квитанции";
+
+            this.pnlCardDetails.ResumeLayout(false);
+            this.pnlCardDetails.PerformLayout();
+            this.ResumeLayout(false);
+            this.PerformLayout();
+        }
+    }
+}

+ 204 - 0
ZhkhApp/PaymentForm.cs

@@ -0,0 +1,204 @@
+using System;
+using System.Windows.Forms;
+using Npgsql;
+
+namespace ZhkhApp
+{
+    public partial class PaymentForm : Form
+    {
+        private int receiptId;
+        private decimal totalAmount;
+        private decimal remainingAmount;
+        private int userId;
+
+        public PaymentForm(int receiptId, decimal totalAmount, int userId)
+        {
+            this.receiptId = receiptId;
+            this.totalAmount = totalAmount;
+            this.userId = userId;
+            InitializeComponent();
+
+            cbMethod.Items.Clear();
+            cbMethod.Items.AddRange(new[] { "Банковская карта", "Наличные (в кассе)", "Онлайн-кошелёк" });
+            cbMethod.SelectedIndex = 0;
+
+            LoadReceiptInfo();
+            LoadPenalty();
+        }
+
+        private void LoadReceiptInfo()
+        {
+            try
+            {
+                string query = @"
+                    SELECT r.receipt_id, a.apt_number, r.period_month, r.period_year,
+                           r.total_sum, r.penalty, r.remaining_amount
+                    FROM receipts r
+                    JOIN apartments a ON r.apartment_id = a.apartment_id
+                    WHERE r.receipt_id = @receiptId";
+
+                using (var conn = new NpgsqlConnection(MainForm.ConnectionString))
+                {
+                    conn.Open();
+                    using (var cmd = new NpgsqlCommand(query, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@receiptId", receiptId);
+                        using (var reader = cmd.ExecuteReader())
+                        {
+                            if (reader.Read())
+                            {
+                                string aptNumber = reader["apt_number"].ToString();
+                                int month = Convert.ToInt32(reader["period_month"]);
+                                int year = Convert.ToInt32(reader["period_year"]);
+                                remainingAmount = Convert.ToDecimal(reader["remaining_amount"]);
+
+                                lblReceiptInfo.Text = $"Квитанция №{receiptId} | Кв.{aptNumber} | {month}.{year}";
+                                lblAmountToPay.Text = $"Сумма к оплате: {remainingAmount:F2} руб.";
+                                txtAmount.Text = remainingAmount.ToString("F2");
+
+                                if (remainingAmount <= 0)
+                                {
+                                    btnPay.Enabled = false;
+                                    lblStatus.Text = "Квитанция уже оплачена!";
+                                }
+                                else
+                                {
+                                    btnPay.Enabled = true;
+                                }
+                            }
+                            else
+                            {
+                                lblStatus.Text = "Квитанция не найдена!";
+                            }
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                lblStatus.Text = $"Ошибка: {ex.Message}";
+            }
+        }
+
+        private void LoadPenalty()
+        {
+            try
+            {
+                string query = "SELECT calculate_penalty(@receiptId, CURRENT_DATE)";
+
+                using (var conn = new NpgsqlConnection(MainForm.ConnectionString))
+                {
+                    conn.Open();
+                    using (var cmd = new NpgsqlCommand(query, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@receiptId", receiptId);
+                        object result = cmd.ExecuteScalar();
+                        decimal penalty = result != DBNull.Value ? Convert.ToDecimal(result) : 0;
+                        lblPenaltyInfo.Text = $"Пени за просрочку: {penalty:F2} руб.";
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                lblPenaltyInfo.Text = "Пени: ошибка расчета";
+            }
+        }
+
+        private void CbMethod_SelectedIndexChanged(object sender, EventArgs e)
+        {
+            pnlCardDetails.Visible = (cbMethod.SelectedIndex == 0);
+        }
+
+        private void TxtCardNumber_TextChanged(object sender, EventArgs e)
+        {
+            string text = txtCardNumber.Text.Replace(" ", "");
+            if (text.Length >= 4)
+            {
+                if (text.Length >= 8)
+                {
+                    if (text.Length >= 12)
+                    {
+                        txtCardNumber.Text = $"{text.Substring(0, 4)} {text.Substring(4, 4)} {text.Substring(8, 4)} {text.Substring(12)}";
+                    }
+                    else
+                    {
+                        txtCardNumber.Text = $"{text.Substring(0, 4)} {text.Substring(4, 4)} {text.Substring(8)}";
+                    }
+                }
+                else
+                {
+                    txtCardNumber.Text = $"{text.Substring(0, 4)} {text.Substring(4)}";
+                }
+                txtCardNumber.SelectionStart = txtCardNumber.Text.Length;
+            }
+        }
+
+        private void BtnPay_Click(object sender, EventArgs e)
+        {
+            if (!decimal.TryParse(txtAmount.Text, out decimal paymentAmount) || paymentAmount <= 0)
+            {
+                lblStatus.Text = "Введите корректную сумму оплаты!";
+                return;
+            }
+
+            if (paymentAmount > remainingAmount)
+            {
+                lblStatus.Text = "Сумма оплаты не может превышать остаток задолженности!";
+                return;
+            }
+
+            if (cbMethod.SelectedIndex == 0)
+            {
+                string cardNumber = txtCardNumber.Text.Replace(" ", "");
+                if (cardNumber.Length < 16)
+                {
+                    lblStatus.Text = "Введите корректный номер карты (16 цифр)!";
+                    return;
+                }
+                if (txtCardExpiry.Text.Length < 5)
+                {
+                    lblStatus.Text = "Введите срок действия карты (ММ/ГГ)!";
+                    return;
+                }
+                if (txtCardCvv.Text.Length < 3)
+                {
+                    lblStatus.Text = "Введите CVV код (3 цифры)!";
+                    return;
+                }
+            }
+
+            try
+            {
+                using (var conn = new NpgsqlConnection(MainForm.ConnectionString))
+                {
+                    conn.Open();
+
+                    string insertPayment = @"
+                        INSERT INTO payments (receipt_id, payment_date, amount, payment_method, status)
+                        VALUES (@receiptId, CURRENT_DATE, @amount, @method, 'paid')";
+
+                    using (var cmd = new NpgsqlCommand(insertPayment, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@receiptId", receiptId);
+                        cmd.Parameters.AddWithValue("@amount", paymentAmount);
+                        cmd.Parameters.AddWithValue("@method", cbMethod.SelectedItem.ToString());
+                        cmd.ExecuteNonQuery();
+                    }
+                }
+
+                this.DialogResult = DialogResult.OK;
+                this.Close();
+            }
+            catch (Exception ex)
+            {
+                lblStatus.Text = $"Ошибка оплаты: {ex.Message}";
+            }
+        }
+
+        private void BtnCancel_Click(object sender, EventArgs e)
+        {
+            this.DialogResult = DialogResult.Cancel;
+            this.Close();
+        }
+    }
+}

+ 120 - 0
ZhkhApp/PaymentForm.resx

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 38 - 0
ZhkhApp/Program.cs

@@ -0,0 +1,38 @@
+using System;
+using System.Windows.Forms;
+
+namespace ZhkhApp
+{
+    internal static class Program
+    {
+        [STAThread]
+        static void Main()
+        {
+            Application.EnableVisualStyles();
+            Application.SetCompatibleTextRenderingDefault(false);
+
+            using (LoginForm loginForm = new LoginForm())
+            {
+                if (loginForm.ShowDialog() == DialogResult.OK)
+                {
+                    switch (MainForm.CurrentRole)
+                    {
+                        case "admin":
+                            Application.Run(new AdminForm());
+                            break;
+                        case "operator":
+                            Application.Run(new OperatorForm());
+                            break;
+                        default:
+                            Application.Run(new ResidentForm());
+                            break;
+                    }
+                }
+                else
+                {
+                    Application.Exit();
+                }
+            }
+        }
+    }
+}

+ 33 - 0
ZhkhApp/Properties/AssemblyInfo.cs

@@ -0,0 +1,33 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// Общие сведения об этой сборке предоставляются следующим набором
+// набора атрибутов. Измените значения этих атрибутов для изменения сведений,
+// связанных со сборкой.
+[assembly: AssemblyTitle("ZhkhApp")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ZhkhApp")]
+[assembly: AssemblyCopyright("Copyright ©  2026")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Установка значения False для параметра ComVisible делает типы в этой сборке невидимыми
+// для компонентов COM. Если необходимо обратиться к типу в этой сборке через
+// COM, следует установить атрибут ComVisible в TRUE для этого типа.
+[assembly: ComVisible(false)]
+
+// Следующий GUID служит для идентификации библиотеки типов, если этот проект будет видимым для COM
+[assembly: Guid("0c8dcfc1-9a73-4a5f-a272-5fbbb83d0556")]
+
+// Сведения о версии сборки состоят из указанных ниже четырех значений:
+//
+//      Основной номер версии
+//      Дополнительный номер версии
+//      Номер сборки
+//      Редакция
+//
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 71 - 0
ZhkhApp/Properties/Resources.Designer.cs

@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     Этот код создан программным средством.
+//     Версия среды выполнения: 4.0.30319.42000
+//
+//     Изменения в этом файле могут привести к неправильному поведению и будут утрачены, если
+//     код создан повторно.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace ZhkhApp.Properties
+{
+
+
+    /// <summary>
+    ///   Класс ресурсов со строгим типом для поиска локализованных строк и пр.
+    /// </summary>
+    // Этот класс был автоматически создан при помощи StronglyTypedResourceBuilder
+    // класс с помощью таких средств, как ResGen или Visual Studio.
+    // Для добавления или удаления члена измените файл .ResX, а затем перезапустите ResGen
+    // с параметром /str или заново постройте свой VS-проект.
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources
+    {
+
+        private static global::System.Resources.ResourceManager resourceMan;
+
+        private static global::System.Globalization.CultureInfo resourceCulture;
+
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources()
+        {
+        }
+
+        /// <summary>
+        ///   Возврат кэшированного экземпляра ResourceManager, используемого этим классом.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager
+        {
+            get
+            {
+                if ((resourceMan == null))
+                {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ZhkhApp.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+
+        /// <summary>
+        ///   Переопределяет свойство CurrentUICulture текущего потока для всех
+        ///   подстановки ресурсов с помощью этого класса ресурсов со строгим типом.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture
+        {
+            get
+            {
+                return resourceCulture;
+            }
+            set
+            {
+                resourceCulture = value;
+            }
+        }
+    }
+}

+ 117 - 0
ZhkhApp/Properties/Resources.resx

@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 30 - 0
ZhkhApp/Properties/Settings.Designer.cs

@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace ZhkhApp.Properties
+{
+
+
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+    {
+
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+        public static Settings Default
+        {
+            get
+            {
+                return defaultInstance;
+            }
+        }
+    }
+}

+ 7 - 0
ZhkhApp/Properties/Settings.settings

@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>

+ 152 - 0
ZhkhApp/ReportForm.Designer.cs

@@ -0,0 +1,152 @@
+using System;
+using System.Windows.Forms;
+
+namespace ZhkhApp
+{
+    partial class ReportForm
+    {
+        private System.ComponentModel.IContainer components = null;
+        private DataGridView dgvDebt, dgvPayments, dgvReceipts;
+        private Label lblTitle, lblTotalDebt, lblSummary;
+        private DateTimePicker dtpStartDate, dtpEndDate;
+        private Button btnGenerateReport, btnRefreshDebt, btnRefreshPayments, btnClose;
+        private TabControl tabControl;
+        private TabPage tabDebt, tabPayments, tabReceipts;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+                components.Dispose();
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.dgvDebt = new DataGridView();
+            this.dgvPayments = new DataGridView();
+            this.dgvReceipts = new DataGridView();
+            this.lblTitle = new Label();
+            this.lblTotalDebt = new Label();
+            this.lblSummary = new Label();
+            this.dtpStartDate = new DateTimePicker();
+            this.dtpEndDate = new DateTimePicker();
+            this.btnGenerateReport = new Button();
+            this.btnRefreshDebt = new Button();
+            this.btnRefreshPayments = new Button();
+            this.btnClose = new Button();
+            this.tabControl = new TabControl();
+            this.tabDebt = new TabPage();
+            this.tabPayments = new TabPage();
+            this.tabReceipts = new TabPage();
+
+            ((System.ComponentModel.ISupportInitialize)(this.dgvDebt)).BeginInit();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvPayments)).BeginInit();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvReceipts)).BeginInit();
+            this.tabControl.SuspendLayout();
+            this.tabDebt.SuspendLayout();
+            this.tabPayments.SuspendLayout();
+            this.tabReceipts.SuspendLayout();
+            this.SuspendLayout();
+
+            // lblTitle
+            this.lblTitle.Text = "Аналитические отчёты ЖКХ";
+            this.lblTitle.Font = new System.Drawing.Font("Segoe UI", 16, System.Drawing.FontStyle.Bold);
+            this.lblTitle.Location = new System.Drawing.Point(20, 20);
+            this.lblTitle.Size = new System.Drawing.Size(400, 40);
+            this.lblTitle.ForeColor = System.Drawing.Color.DarkBlue;
+
+            // btnClose
+            this.btnClose.Text = "Закрыть";
+            this.btnClose.Location = new System.Drawing.Point(850, 25);
+            this.btnClose.Size = new System.Drawing.Size(100, 35);
+            this.btnClose.BackColor = System.Drawing.Color.LightCoral;
+            this.btnClose.Click += BtnClose_Click;
+
+            // tabControl
+            this.tabControl.Location = new System.Drawing.Point(20, 80);
+            this.tabControl.Size = new System.Drawing.Size(960, 520);
+
+            // tabDebt
+            this.tabDebt.Text = "Задолженность по квартирам";
+            this.dgvDebt.Dock = DockStyle.Fill;
+            this.dgvDebt.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
+            this.dgvDebt.ReadOnly = true;
+            this.tabDebt.Controls.Add(this.dgvDebt);
+            this.tabDebt.Controls.Add(this.lblTotalDebt);
+
+            this.lblTotalDebt.Location = new System.Drawing.Point(10, 440);
+            this.lblTotalDebt.Size = new System.Drawing.Size(400, 30);
+            this.lblTotalDebt.Font = new System.Drawing.Font("Segoe UI", 10, System.Drawing.FontStyle.Bold);
+
+            this.btnRefreshDebt.Text = "Обновить";
+            this.btnRefreshDebt.Location = new System.Drawing.Point(800, 440);
+            this.btnRefreshDebt.Size = new System.Drawing.Size(120, 30);
+            this.btnRefreshDebt.Click += BtnRefreshDebt_Click;
+            this.tabDebt.Controls.Add(this.btnRefreshDebt);
+
+            // tabPayments
+            this.tabPayments.Text = "История платежей";
+            this.dgvPayments.Dock = DockStyle.Fill;
+            this.dgvPayments.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
+            this.dgvPayments.ReadOnly = true;
+            this.tabPayments.Controls.Add(this.dgvPayments);
+
+            this.btnRefreshPayments.Text = "Обновить";
+            this.btnRefreshPayments.Location = new System.Drawing.Point(800, 440);
+            this.btnRefreshPayments.Size = new System.Drawing.Size(120, 30);
+            this.btnRefreshPayments.Click += BtnRefreshPayments_Click;
+            this.tabPayments.Controls.Add(this.btnRefreshPayments);
+
+            // tabReceipts
+            this.tabReceipts.Text = "Квитанции за период";
+            this.dgvReceipts.Dock = DockStyle.Fill;
+            this.dgvReceipts.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
+            this.dgvReceipts.ReadOnly = true;
+            this.tabReceipts.Controls.Add(this.dgvReceipts);
+            this.tabReceipts.Controls.Add(this.lblSummary);
+            this.tabReceipts.Controls.Add(this.dtpStartDate);
+            this.tabReceipts.Controls.Add(this.dtpEndDate);
+            this.tabReceipts.Controls.Add(this.btnGenerateReport);
+
+            this.dtpStartDate.Location = new System.Drawing.Point(10, 10);
+            this.dtpStartDate.Size = new System.Drawing.Size(120, 25);
+            this.dtpStartDate.Value = new DateTime(2024, 1, 1);
+
+            this.dtpEndDate.Location = new System.Drawing.Point(140, 10);
+            this.dtpEndDate.Size = new System.Drawing.Size(120, 25);
+            this.dtpEndDate.Value = DateTime.Now;
+
+            this.btnGenerateReport.Text = "Сформировать";
+            this.btnGenerateReport.Location = new System.Drawing.Point(270, 8);
+            this.btnGenerateReport.Size = new System.Drawing.Size(120, 30);
+            this.btnGenerateReport.Click += BtnGenerateReport_Click;
+
+            this.lblSummary.Location = new System.Drawing.Point(10, 450);
+            this.lblSummary.Size = new System.Drawing.Size(600, 30);
+            this.lblSummary.Font = new System.Drawing.Font("Segoe UI", 10, System.Drawing.FontStyle.Bold);
+
+            this.tabControl.Controls.Add(this.tabDebt);
+            this.tabControl.Controls.Add(this.tabPayments);
+            this.tabControl.Controls.Add(this.tabReceipts);
+
+            // ReportForm
+            this.Text = "ЖКХ - Отчёты";
+            this.Size = new System.Drawing.Size(1020, 650);
+            this.StartPosition = FormStartPosition.CenterScreen;
+            this.BackColor = System.Drawing.Color.WhiteSmoke;
+
+            this.Controls.Add(this.lblTitle);
+            this.Controls.Add(this.btnClose);
+            this.Controls.Add(this.tabControl);
+
+            ((System.ComponentModel.ISupportInitialize)(this.dgvDebt)).EndInit();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvPayments)).EndInit();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvReceipts)).EndInit();
+            this.tabControl.ResumeLayout(false);
+            this.tabDebt.ResumeLayout(false);
+            this.tabPayments.ResumeLayout(false);
+            this.tabReceipts.ResumeLayout(false);
+            this.ResumeLayout(false);
+        }
+    }
+}

+ 212 - 0
ZhkhApp/ReportForm.cs

@@ -0,0 +1,212 @@
+using System;
+using System.Data;
+using System.Windows.Forms;
+using Npgsql;
+
+namespace ZhkhApp
+{
+    public partial class ReportForm : Form
+    {
+        public ReportForm()
+        {
+            InitializeComponent();
+            LoadDebtReport();
+            LoadPaymentHistory();
+        }
+
+        // 1. Отчёт по задолженности (используем v_apartment_debt)
+        private void LoadDebtReport()
+        {
+            try
+            {
+                string query = @"
+                    SELECT apt_number, address, total_accrued, total_paid, current_debt
+                    FROM v_apartment_debt
+                    WHERE current_debt > 0
+                    ORDER BY current_debt DESC";
+
+                using (var conn = new NpgsqlConnection(MainForm.ConnectionString))
+                {
+                    conn.Open();
+                    using (var adapter = new NpgsqlDataAdapter(query, conn))
+                    {
+                        DataTable dt = new DataTable();
+                        adapter.Fill(dt);
+                        dgvDebt.DataSource = dt;
+
+                        // Подсчёт общей суммы задолженности
+                        decimal totalDebt = 0;
+                        foreach (DataRow row in dt.Rows)
+                        {
+                            totalDebt += Convert.ToDecimal(row["current_debt"]);
+                        }
+                        lblTotalDebt.Text = $"Общая задолженность: {totalDebt:F2} руб.";
+                    }
+                }
+                SetDebtHeaders();
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки отчёта по задолженности: {ex.Message}");
+            }
+        }
+
+        // 2. История платежей (используем v_payment_history)
+        private void LoadPaymentHistory()
+        {
+            try
+            {
+                string query = @"
+                    SELECT apt_number, payer_name, period_month, period_year, 
+                           payment_date, amount, payment_method
+                    FROM v_payment_history
+                    ORDER BY payment_date DESC
+                    LIMIT 100";
+
+                using (var conn = new NpgsqlConnection(MainForm.ConnectionString))
+                {
+                    conn.Open();
+                    using (var adapter = new NpgsqlDataAdapter(query, conn))
+                    {
+                        DataTable dt = new DataTable();
+                        adapter.Fill(dt);
+                        dgvPayments.DataSource = dt;
+                    }
+                }
+                SetPaymentHeaders();
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки истории платежей: {ex.Message}");
+            }
+        }
+
+        // 3. Отчёт по квитанциям за период (используем v_receipts_full)
+        private void LoadReceiptsReport(DateTime startDate, DateTime endDate)
+        {
+            try
+            {
+                string query = @"
+                    SELECT receipt_id, apt_number, full_address, period_month, period_year,
+                           total_sum, penalty, total_with_penalty, status, remaining_amount
+                    FROM v_receipts_full
+                    WHERE period_year BETWEEN @startYear AND @endYear
+                    ORDER BY period_year DESC, period_month DESC";
+
+                using (var conn = new NpgsqlConnection(MainForm.ConnectionString))
+                {
+                    conn.Open();
+                    using (var adapter = new NpgsqlDataAdapter(query, conn))
+                    {
+                        adapter.SelectCommand.Parameters.AddWithValue("@startYear", startDate.Year);
+                        adapter.SelectCommand.Parameters.AddWithValue("@endYear", endDate.Year);
+                        DataTable dt = new DataTable();
+                        adapter.Fill(dt);
+                        dgvReceipts.DataSource = dt;
+                    }
+                }
+                SetReceiptHeaders();
+
+                // Подсчёт итогов
+                decimal totalSum = 0;
+                decimal totalPenalty = 0;
+                decimal totalRemaining = 0;
+                foreach (DataGridViewRow row in dgvReceipts.Rows)
+                {
+                    totalSum += Convert.ToDecimal(row.Cells["total_sum"]);
+                    totalPenalty += Convert.ToDecimal(row.Cells["penalty"]);
+                    totalRemaining += Convert.ToDecimal(row.Cells["remaining_amount"]);
+                }
+                lblSummary.Text = $"Итого начислено: {totalSum:F2} руб. | Пени: {totalPenalty:F2} руб. | Задолженность: {totalRemaining:F2} руб.";
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки отчёта: {ex.Message}");
+            }
+        }
+
+        // Установка заголовков для dgvDebt
+        private void SetDebtHeaders()
+        {
+            if (dgvDebt.Columns["apt_number"] != null)
+                dgvDebt.Columns["apt_number"].HeaderText = "Квартира";
+            if (dgvDebt.Columns["address"] != null)
+                dgvDebt.Columns["address"].HeaderText = "Адрес";
+            if (dgvDebt.Columns["total_accrued"] != null)
+                dgvDebt.Columns["total_accrued"].HeaderText = "Начислено, руб";
+            if (dgvDebt.Columns["total_paid"] != null)
+                dgvDebt.Columns["total_paid"].HeaderText = "Оплачено, руб";
+            if (dgvDebt.Columns["current_debt"] != null)
+                dgvDebt.Columns["current_debt"].HeaderText = "Задолженность, руб";
+        }
+
+        // Установка заголовков для dgvPayments
+        private void SetPaymentHeaders()
+        {
+            if (dgvPayments.Columns["apt_number"] != null)
+                dgvPayments.Columns["apt_number"].HeaderText = "Квартира";
+            if (dgvPayments.Columns["payer_name"] != null)
+                dgvPayments.Columns["payer_name"].HeaderText = "Плательщик";
+            if (dgvPayments.Columns["period_month"] != null)
+                dgvPayments.Columns["period_month"].HeaderText = "Месяц";
+            if (dgvPayments.Columns["period_year"] != null)
+                dgvPayments.Columns["period_year"].HeaderText = "Год";
+            if (dgvPayments.Columns["payment_date"] != null)
+                dgvPayments.Columns["payment_date"].HeaderText = "Дата оплаты";
+            if (dgvPayments.Columns["amount"] != null)
+                dgvPayments.Columns["amount"].HeaderText = "Сумма, руб";
+            if (dgvPayments.Columns["payment_method"] != null)
+                dgvPayments.Columns["payment_method"].HeaderText = "Способ оплаты";
+        }
+
+        // Установка заголовков для dgvReceipts
+        private void SetReceiptHeaders()
+        {
+            if (dgvReceipts.Columns["receipt_id"] != null)
+                dgvReceipts.Columns["receipt_id"].HeaderText = "№";
+            if (dgvReceipts.Columns["apt_number"] != null)
+                dgvReceipts.Columns["apt_number"].HeaderText = "Квартира";
+            if (dgvReceipts.Columns["full_address"] != null)
+                dgvReceipts.Columns["full_address"].HeaderText = "Адрес";
+            if (dgvReceipts.Columns["period_month"] != null)
+                dgvReceipts.Columns["period_month"].HeaderText = "Месяц";
+            if (dgvReceipts.Columns["period_year"] != null)
+                dgvReceipts.Columns["period_year"].HeaderText = "Год";
+            if (dgvReceipts.Columns["total_sum"] != null)
+                dgvReceipts.Columns["total_sum"].HeaderText = "Сумма, руб";
+            if (dgvReceipts.Columns["penalty"] != null)
+                dgvReceipts.Columns["penalty"].HeaderText = "Пени, руб";
+            if (dgvReceipts.Columns["total_with_penalty"] != null)
+                dgvReceipts.Columns["total_with_penalty"].HeaderText = "Итого, руб";
+            if (dgvReceipts.Columns["status"] != null)
+                dgvReceipts.Columns["status"].HeaderText = "Статус";
+            if (dgvReceipts.Columns["remaining_amount"] != null)
+                dgvReceipts.Columns["remaining_amount"].HeaderText = "Остаток, руб";
+        }
+
+        // Обработчик кнопки "Сформировать отчёт по квитанциям"
+        private void BtnGenerateReport_Click(object sender, EventArgs e)
+        {
+            DateTime startDate = dtpStartDate.Value;
+            DateTime endDate = dtpEndDate.Value;
+            LoadReceiptsReport(startDate, endDate);
+        }
+
+        // Обработчик кнопки "Обновить задолженность"
+        private void BtnRefreshDebt_Click(object sender, EventArgs e)
+        {
+            LoadDebtReport();
+        }
+
+        // Обработчик кнопки "Обновить историю"
+        private void BtnRefreshPayments_Click(object sender, EventArgs e)
+        {
+            LoadPaymentHistory();
+        }
+
+        private void BtnClose_Click(object sender, EventArgs e)
+        {
+            this.Close();
+        }
+    }
+}

+ 110 - 0
ZhkhApp/ResidentForm.Designer.cs

@@ -0,0 +1,110 @@
+using System.Windows.Forms;
+
+namespace ZhkhApp
+{
+    partial class ResidentForm
+    {
+        private System.ComponentModel.IContainer components = null;
+        private DataGridView dgvReceipts;
+        private Button btnPay, btnRefresh, btnLogout;
+        private Label lblTitle, lblUserInfo, lblTotalDebt, lblPenaltyInfo;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.dgvReceipts = new DataGridView();
+            this.btnPay = new Button();
+            this.btnRefresh = new Button();
+            this.btnLogout = new Button();
+            this.lblTitle = new Label();
+            this.lblUserInfo = new Label();
+            this.lblTotalDebt = new Label();
+            this.lblPenaltyInfo = new Label();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvReceipts)).BeginInit();
+            this.SuspendLayout();
+
+            // dgvReceipts
+            this.dgvReceipts.Location = new System.Drawing.Point(20, 110);
+            this.dgvReceipts.Size = new System.Drawing.Size(850, 400);
+            this.dgvReceipts.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
+            this.dgvReceipts.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
+            this.dgvReceipts.MultiSelect = false;
+            this.dgvReceipts.ReadOnly = true;
+            this.dgvReceipts.SelectionChanged += new System.EventHandler(this.dgvReceipts_SelectionChanged);
+
+            // btnPay
+            this.btnPay.Text = "Оплатить выбранную квитанцию";
+            this.btnPay.Location = new System.Drawing.Point(20, 520);
+            this.btnPay.Size = new System.Drawing.Size(200, 40);
+            this.btnPay.BackColor = System.Drawing.Color.LightGreen;
+            this.btnPay.Click += new System.EventHandler(this.BtnPay_Click);
+
+            // btnRefresh
+            this.btnRefresh.Text = "Обновить";
+            this.btnRefresh.Location = new System.Drawing.Point(240, 520);
+            this.btnRefresh.Size = new System.Drawing.Size(100, 40);
+            this.btnRefresh.BackColor = System.Drawing.Color.LightGray;
+            this.btnRefresh.Click += new System.EventHandler(this.BtnRefresh_Click);
+
+            // btnLogout
+            this.btnLogout.Text = "Выход";
+            this.btnLogout.Location = new System.Drawing.Point(770, 520);
+            this.btnLogout.Size = new System.Drawing.Size(100, 40);
+            this.btnLogout.BackColor = System.Drawing.Color.IndianRed;
+            this.btnLogout.ForeColor = System.Drawing.Color.White;
+            this.btnLogout.Click += new System.EventHandler(this.BtnLogout_Click);
+
+            // lblTitle
+            this.lblTitle.Text = "Личный кабинет ЖКХ";
+            this.lblTitle.Font = new System.Drawing.Font("Segoe UI", 16, System.Drawing.FontStyle.Bold);
+            this.lblTitle.Location = new System.Drawing.Point(20, 20);
+            this.lblTitle.Size = new System.Drawing.Size(400, 40);
+            this.lblTitle.ForeColor = System.Drawing.Color.DarkBlue;
+
+            // lblUserInfo
+            this.lblUserInfo.Location = new System.Drawing.Point(600, 25);
+            this.lblUserInfo.Size = new System.Drawing.Size(250, 30);
+            this.lblUserInfo.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
+
+            // lblTotalDebt
+            this.lblTotalDebt.Text = "Общая задолженность: 0 руб.";
+            this.lblTotalDebt.Font = new System.Drawing.Font("Segoe UI", 12, System.Drawing.FontStyle.Bold);
+            this.lblTotalDebt.Location = new System.Drawing.Point(20, 70);
+            this.lblTotalDebt.Size = new System.Drawing.Size(400, 30);
+            this.lblTotalDebt.ForeColor = System.Drawing.Color.Red;
+
+            // lblPenaltyInfo
+            this.lblPenaltyInfo.Text = "Пени по выбранной квитанции: 0 руб.";
+            this.lblPenaltyInfo.Font = new System.Drawing.Font("Segoe UI", 10, System.Drawing.FontStyle.Regular);
+            this.lblPenaltyInfo.Location = new System.Drawing.Point(20, 565);
+            this.lblPenaltyInfo.Size = new System.Drawing.Size(350, 25);
+            this.lblPenaltyInfo.ForeColor = System.Drawing.Color.DarkRed;
+
+            // ResidentForm
+            this.Text = "ЖКХ - Личный кабинет";
+            this.Size = new System.Drawing.Size(900, 620);
+            this.StartPosition = FormStartPosition.CenterScreen;
+            this.BackColor = System.Drawing.Color.WhiteSmoke;
+
+            this.Controls.Add(this.lblTitle);
+            this.Controls.Add(this.lblUserInfo);
+            this.Controls.Add(this.lblTotalDebt);
+            this.Controls.Add(this.lblPenaltyInfo);
+            this.Controls.Add(this.dgvReceipts);
+            this.Controls.Add(this.btnPay);
+            this.Controls.Add(this.btnRefresh);
+            this.Controls.Add(this.btnLogout);
+
+            ((System.ComponentModel.ISupportInitialize)(this.dgvReceipts)).EndInit();
+            this.ResumeLayout(false);
+        }
+    }
+}

+ 172 - 0
ZhkhApp/ResidentForm.cs

@@ -0,0 +1,172 @@
+using System;
+using System.Data;
+using System.Windows.Forms;
+using Npgsql;
+
+namespace ZhkhApp
+{
+    public partial class ResidentForm : Form
+    {
+        private int userId;
+        private decimal totalDebt = 0;
+
+        public ResidentForm()
+        {
+            InitializeComponent();
+            userId = MainForm.CurrentUserId;
+            LoadReceipts();
+            CalculateTotalDebt();
+            DisplayUserInfo();
+        }
+
+        // Загрузка квитанций через представление v_receipts_full
+        private void LoadReceipts()
+        {
+            try
+            {
+                // Используем представление v_receipts_full
+                // Фильтруем по person_id (это issued_to_person_id в представлении)
+                string query = @"
+                    SELECT receipt_id, apt_number, period_month, period_year, 
+                           total_sum, penalty, total_with_penalty, status, remaining_amount AS debt
+                    FROM v_receipts_full
+                    WHERE debtor_name ILIKE '%' || @userName || '%'
+                    ORDER BY period_year DESC, period_month DESC";
+
+                using (var conn = new NpgsqlConnection(MainForm.ConnectionString))
+                {
+                    conn.Open();
+                    using (var adapter = new NpgsqlDataAdapter(query, conn))
+                    {
+                        adapter.SelectCommand.Parameters.AddWithValue("@userName", MainForm.CurrentUserFullName);
+                        DataTable dt = new DataTable();
+                        adapter.Fill(dt);
+                        dgvReceipts.DataSource = dt;
+                    }
+                }
+
+                SetRussianHeaders();
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки: {ex.Message}");
+            }
+        }
+
+        // Настройка заголовков колонок
+        private void SetRussianHeaders()
+        {
+            if (dgvReceipts.Columns["receipt_id"] != null)
+                dgvReceipts.Columns["receipt_id"].HeaderText = "№";
+            if (dgvReceipts.Columns["apt_number"] != null)
+                dgvReceipts.Columns["apt_number"].HeaderText = "Квартира";
+            if (dgvReceipts.Columns["period_month"] != null)
+                dgvReceipts.Columns["period_month"].HeaderText = "Месяц";
+            if (dgvReceipts.Columns["period_year"] != null)
+                dgvReceipts.Columns["period_year"].HeaderText = "Год";
+            if (dgvReceipts.Columns["total_sum"] != null)
+                dgvReceipts.Columns["total_sum"].HeaderText = "Сумма, руб";
+            if (dgvReceipts.Columns["penalty"] != null)
+                dgvReceipts.Columns["penalty"].HeaderText = "Пени, руб";
+            if (dgvReceipts.Columns["total_with_penalty"] != null)
+                dgvReceipts.Columns["total_with_penalty"].HeaderText = "Итого, руб";
+            if (dgvReceipts.Columns["status"] != null)
+                dgvReceipts.Columns["status"].HeaderText = "Статус";
+            if (dgvReceipts.Columns["debt"] != null)
+                dgvReceipts.Columns["debt"].HeaderText = "Задолженность, руб";
+        }
+
+        // Расчёт общей задолженности (из DataGridView)
+        private void CalculateTotalDebt()
+        {
+            totalDebt = 0;
+            foreach (DataGridViewRow row in dgvReceipts.Rows)
+            {
+                if (row.Cells["debt"]?.Value != null)
+                {
+                    totalDebt += Convert.ToDecimal(row.Cells["debt"].Value);
+                }
+            }
+            lblTotalDebt.Text = $"Общая задолженность: {totalDebt:F2} руб.";
+            lblTotalDebt.ForeColor = totalDebt > 0 ? System.Drawing.Color.Red : System.Drawing.Color.Green;
+        }
+
+        // Показать пеню для выбранной квитанции (используем функцию calculate_penalty)
+        private void ShowPenaltyForSelectedReceipt()
+        {
+            if (dgvReceipts.CurrentRow == null) return;
+
+            int receiptId = Convert.ToInt32(dgvReceipts.CurrentRow.Cells["receipt_id"].Value);
+            decimal penalty = CalculatePenalty(receiptId);
+
+            // Можно показать в отдельной метке или в MessageBox
+            lblPenaltyInfo.Text = $"Пени на сегодня: {penalty:F2} руб.";
+        }
+
+        // Вызов функции calculate_penalty из БД
+        private decimal CalculatePenalty(int receiptId)
+        {
+            string query = "SELECT calculate_penalty(@receiptId, CURRENT_DATE)";
+
+            using (var conn = new NpgsqlConnection(MainForm.ConnectionString))
+            {
+                conn.Open();
+                using (var cmd = new NpgsqlCommand(query, conn))
+                {
+                    cmd.Parameters.AddWithValue("@receiptId", receiptId);
+                    object result = cmd.ExecuteScalar();
+                    return result != DBNull.Value ? Convert.ToDecimal(result) : 0;
+                }
+            }
+        }
+
+        private void DisplayUserInfo()
+        {
+            lblUserInfo.Text = $"Житель: {MainForm.CurrentUserFullName}";
+        }
+
+        private void BtnPay_Click(object sender, EventArgs e)
+        {
+            if (dgvReceipts.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите квитанцию для оплаты!");
+                return;
+            }
+
+            int receiptId = Convert.ToInt32(dgvReceipts.CurrentRow.Cells["receipt_id"].Value);
+            decimal totalWithPenalty = Convert.ToDecimal(dgvReceipts.CurrentRow.Cells["total_with_penalty"].Value);
+            decimal debt = Convert.ToDecimal(dgvReceipts.CurrentRow.Cells["debt"].Value);
+
+            if (debt <= 0)
+            {
+                MessageBox.Show("Эта квитанция уже оплачена!");
+                return;
+            }
+
+            var paymentForm = new PaymentForm(receiptId, totalWithPenalty, userId);
+            if (paymentForm.ShowDialog() == DialogResult.OK)
+            {
+                LoadReceipts();
+                CalculateTotalDebt();
+                MessageBox.Show("Оплата прошла успешно!");
+            }
+        }
+
+        private void BtnRefresh_Click(object sender, EventArgs e)
+        {
+            LoadReceipts();
+            CalculateTotalDebt();
+        }
+
+        private void BtnLogout_Click(object sender, EventArgs e)
+        {
+            Application.Restart();
+        }
+
+        // При выборе строки в DataGridView показываем пеню
+        private void dgvReceipts_SelectionChanged(object sender, EventArgs e)
+        {
+            ShowPenaltyForSelectedReceipt();
+        }
+    }
+}

+ 185 - 0
ZhkhApp/ZhkhApp.csproj

@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{0C8DCFC1-9A73-4A5F-A272-5FBBB83D0556}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <RootNamespace>ZhkhApp</RootNamespace>
+    <AssemblyName>ZhkhApp</AssemblyName>
+    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.8.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Bcl.HashCode, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Bcl.HashCode.1.1.1\lib\net461\Microsoft.Bcl.HashCode.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.8.0.0\lib\net462\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Logging.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.Logging.Abstractions.8.0.0\lib\net462\Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
+    </Reference>
+    <Reference Include="Npgsql, Version=8.0.3.0, Culture=neutral, PublicKeyToken=5d8b90d52f46fda7, processorArchitecture=MSIL">
+      <HintPath>..\packages\Npgsql.8.0.3\lib\netstandard2.0\Npgsql.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Collections.Immutable, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Collections.Immutable.8.0.0\lib\net462\System.Collections.Immutable.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Core" />
+    <Reference Include="System.Diagnostics.DiagnosticSource, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Diagnostics.DiagnosticSource.8.0.0\lib\net462\System.Diagnostics.DiagnosticSource.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Numerics" />
+    <Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Text.Encodings.Web, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Text.Encodings.Web.8.0.0\lib\net462\System.Text.Encodings.Web.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Text.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Text.Json.8.0.0\lib\net462\System.Text.Json.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Threading.Channels, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Threading.Channels.8.0.0\lib\net462\System.Threading.Channels.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
+    </Reference>
+    <Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Deployment" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="AdminForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="AdminForm.Designer.cs">
+      <DependentUpon>AdminForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="DatabaseHelper.cs" />
+    <Compile Include="EditForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="EditForm.Designer.cs">
+      <DependentUpon>EditForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="LogForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="LogForm.Designer.cs">
+      <DependentUpon>LogForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="LoginForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="LoginForm.Designer.cs">
+      <DependentUpon>LoginForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="MainForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="MainForm.Designer.cs">
+      <DependentUpon>MainForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="OperatorForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="OperatorForm.Designer.cs">
+      <DependentUpon>OperatorForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="PaymentForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="PaymentForm.Designer.cs">
+      <DependentUpon>PaymentForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="ReportForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="ReportForm.Designer.cs">
+      <DependentUpon>ReportForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="ResidentForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="ResidentForm.Designer.cs">
+      <DependentUpon>ResidentForm.cs</DependentUpon>
+    </Compile>
+    <EmbeddedResource Include="AdminForm.resx">
+      <DependentUpon>AdminForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="PaymentForm.resx">
+      <DependentUpon>PaymentForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
+    <None Include="packages.config" />
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>

+ 19 - 0
ZhkhApp/packages.config

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Microsoft.Bcl.AsyncInterfaces" version="8.0.0" targetFramework="net472" />
+  <package id="Microsoft.Bcl.HashCode" version="1.1.1" targetFramework="net472" />
+  <package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="8.0.0" targetFramework="net472" />
+  <package id="Microsoft.Extensions.Logging.Abstractions" version="8.0.0" targetFramework="net472" />
+  <package id="Npgsql" version="8.0.3" targetFramework="net472" />
+  <package id="System.Buffers" version="4.5.1" targetFramework="net472" />
+  <package id="System.Collections.Immutable" version="8.0.0" targetFramework="net472" />
+  <package id="System.Diagnostics.DiagnosticSource" version="8.0.0" targetFramework="net472" />
+  <package id="System.Memory" version="4.5.5" targetFramework="net472" />
+  <package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net472" />
+  <package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net472" />
+  <package id="System.Text.Encodings.Web" version="8.0.0" targetFramework="net472" />
+  <package id="System.Text.Json" version="8.0.0" targetFramework="net472" />
+  <package id="System.Threading.Channels" version="8.0.0" targetFramework="net472" />
+  <package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
+  <package id="System.ValueTuple" version="4.5.0" targetFramework="net472" />
+</packages>