ソースを参照

Добавил исходники ProviderApp

Egor_Sizov 1 週間 前
コミット
5d1d1301fa
100 ファイル変更5784 行追加0 行削除
  1. BIN
      ProviderApp/.gitignore
  2. 155 0
      ProviderApp/AddEditContractForm.Designer.cs
  3. 198 0
      ProviderApp/AddEditContractForm.cs
  4. 120 0
      ProviderApp/AddEditContractForm.resx
  5. 145 0
      ProviderApp/AddEditTariffForm.Designer.cs
  6. 184 0
      ProviderApp/AddEditTariffForm.cs
  7. 120 0
      ProviderApp/AddEditTariffForm.resx
  8. 278 0
      ProviderApp/AnalyticsForm.Designer.cs
  9. 228 0
      ProviderApp/AnalyticsForm.cs
  10. 120 0
      ProviderApp/AnalyticsForm.resx
  11. 283 0
      ProviderApp/AuthHelper.cs
  12. 203 0
      ProviderApp/ClientsForm.Designer.cs
  13. 204 0
      ProviderApp/ClientsForm.cs
  14. 120 0
      ProviderApp/ClientsForm.resx
  15. 296 0
      ProviderApp/ContractsForm.Designer.cs
  16. 240 0
      ProviderApp/ContractsForm.cs
  17. 120 0
      ProviderApp/ContractsForm.resx
  18. 57 0
      ProviderApp/CsvExporter.cs
  19. 23 0
      ProviderApp/DBConnection.cs
  20. 285 0
      ProviderApp/DashboardForm.Designer.cs
  21. 102 0
      ProviderApp/DashboardForm.cs
  22. 120 0
      ProviderApp/DashboardForm.resx
  23. 51 0
      ProviderApp/DatabaseHelper.cs
  24. 67 0
      ProviderApp/ExcelExporter.cs
  25. 176 0
      ProviderApp/LoginForm.Designer.cs
  26. 54 0
      ProviderApp/LoginForm.cs
  27. 120 0
      ProviderApp/LoginForm.resx
  28. 197 0
      ProviderApp/MainForm.Designer.cs
  29. 61 0
      ProviderApp/MainForm.cs
  30. 120 0
      ProviderApp/MainForm.resx
  31. 26 0
      ProviderApp/Program.cs
  32. 16 0
      ProviderApp/Properties/PublishProfiles/FolderProfile.pubxml
  33. 8 0
      ProviderApp/Properties/PublishProfiles/FolderProfile.pubxml.user
  34. 18 0
      ProviderApp/ProviderApp.csproj
  35. 35 0
      ProviderApp/ProviderApp.csproj.user
  36. 121 0
      ProviderApp/TariffsForm.Designer.cs
  37. 183 0
      ProviderApp/TariffsForm.cs
  38. 120 0
      ProviderApp/TariffsForm.resx
  39. BIN
      ProviderApp/bin/Debug/net10.0-windows/EPPlus.Interfaces.dll
  40. BIN
      ProviderApp/bin/Debug/net10.0-windows/EPPlus.dll
  41. BIN
      ProviderApp/bin/Debug/net10.0-windows/Guna.UI2.dll
  42. BIN
      ProviderApp/bin/Debug/net10.0-windows/Microsoft.Extensions.Configuration.Abstractions.dll
  43. BIN
      ProviderApp/bin/Debug/net10.0-windows/Microsoft.Extensions.Configuration.FileExtensions.dll
  44. BIN
      ProviderApp/bin/Debug/net10.0-windows/Microsoft.Extensions.Configuration.Json.dll
  45. BIN
      ProviderApp/bin/Debug/net10.0-windows/Microsoft.Extensions.Configuration.dll
  46. BIN
      ProviderApp/bin/Debug/net10.0-windows/Microsoft.Extensions.DependencyInjection.Abstractions.dll
  47. BIN
      ProviderApp/bin/Debug/net10.0-windows/Microsoft.Extensions.FileProviders.Abstractions.dll
  48. BIN
      ProviderApp/bin/Debug/net10.0-windows/Microsoft.Extensions.FileProviders.Physical.dll
  49. BIN
      ProviderApp/bin/Debug/net10.0-windows/Microsoft.Extensions.FileSystemGlobbing.dll
  50. BIN
      ProviderApp/bin/Debug/net10.0-windows/Microsoft.Extensions.Logging.Abstractions.dll
  51. BIN
      ProviderApp/bin/Debug/net10.0-windows/Microsoft.Extensions.Primitives.dll
  52. BIN
      ProviderApp/bin/Debug/net10.0-windows/Microsoft.IO.RecyclableMemoryStream.dll
  53. BIN
      ProviderApp/bin/Debug/net10.0-windows/Npgsql.dll
  54. BIN
      ProviderApp/bin/Debug/net10.0-windows/OxyPlot.WindowsForms.dll
  55. BIN
      ProviderApp/bin/Debug/net10.0-windows/OxyPlot.dll
  56. 352 0
      ProviderApp/bin/Debug/net10.0-windows/ProviderApp.deps.json
  57. BIN
      ProviderApp/bin/Debug/net10.0-windows/ProviderApp.dll
  58. BIN
      ProviderApp/bin/Debug/net10.0-windows/ProviderApp.exe
  59. BIN
      ProviderApp/bin/Debug/net10.0-windows/ProviderApp.pdb
  60. 19 0
      ProviderApp/bin/Debug/net10.0-windows/ProviderApp.runtimeconfig.json
  61. BIN
      ProviderApp/bin/Debug/net10.0-windows/System.Management.dll
  62. BIN
      ProviderApp/bin/Debug/net10.0-windows/runtimes/win/lib/net7.0/System.Management.dll
  63. BIN
      ProviderApp/bin/Release/net10.0-windows/EPPlus.Interfaces.dll
  64. BIN
      ProviderApp/bin/Release/net10.0-windows/EPPlus.dll
  65. BIN
      ProviderApp/bin/Release/net10.0-windows/Guna.UI2.dll
  66. BIN
      ProviderApp/bin/Release/net10.0-windows/Microsoft.Extensions.Configuration.Abstractions.dll
  67. BIN
      ProviderApp/bin/Release/net10.0-windows/Microsoft.Extensions.Configuration.FileExtensions.dll
  68. BIN
      ProviderApp/bin/Release/net10.0-windows/Microsoft.Extensions.Configuration.Json.dll
  69. BIN
      ProviderApp/bin/Release/net10.0-windows/Microsoft.Extensions.Configuration.dll
  70. BIN
      ProviderApp/bin/Release/net10.0-windows/Microsoft.Extensions.DependencyInjection.Abstractions.dll
  71. BIN
      ProviderApp/bin/Release/net10.0-windows/Microsoft.Extensions.FileProviders.Abstractions.dll
  72. BIN
      ProviderApp/bin/Release/net10.0-windows/Microsoft.Extensions.FileProviders.Physical.dll
  73. BIN
      ProviderApp/bin/Release/net10.0-windows/Microsoft.Extensions.FileSystemGlobbing.dll
  74. BIN
      ProviderApp/bin/Release/net10.0-windows/Microsoft.Extensions.Logging.Abstractions.dll
  75. BIN
      ProviderApp/bin/Release/net10.0-windows/Microsoft.Extensions.Primitives.dll
  76. BIN
      ProviderApp/bin/Release/net10.0-windows/Microsoft.IO.RecyclableMemoryStream.dll
  77. BIN
      ProviderApp/bin/Release/net10.0-windows/Npgsql.dll
  78. BIN
      ProviderApp/bin/Release/net10.0-windows/Output/mysetup.exe
  79. BIN
      ProviderApp/bin/Release/net10.0-windows/OxyPlot.WindowsForms.dll
  80. BIN
      ProviderApp/bin/Release/net10.0-windows/OxyPlot.dll
  81. 352 0
      ProviderApp/bin/Release/net10.0-windows/ProviderApp.deps.json
  82. BIN
      ProviderApp/bin/Release/net10.0-windows/ProviderApp.dll
  83. BIN
      ProviderApp/bin/Release/net10.0-windows/ProviderApp.exe
  84. BIN
      ProviderApp/bin/Release/net10.0-windows/ProviderApp.pdb
  85. 20 0
      ProviderApp/bin/Release/net10.0-windows/ProviderApp.runtimeconfig.json
  86. 67 0
      ProviderApp/bin/Release/net10.0-windows/Setup.iss
  87. BIN
      ProviderApp/bin/Release/net10.0-windows/System.Management.dll
  88. BIN
      ProviderApp/bin/Release/net10.0-windows/runtimes/win/lib/net7.0/System.Management.dll
  89. BIN
      ProviderApp/bin/Release/net10.0-windows/win-x64/Accessibility.dll
  90. BIN
      ProviderApp/bin/Release/net10.0-windows/win-x64/EPPlus.Interfaces.dll
  91. BIN
      ProviderApp/bin/Release/net10.0-windows/win-x64/EPPlus.dll
  92. BIN
      ProviderApp/bin/Release/net10.0-windows/win-x64/Guna.UI2.dll
  93. BIN
      ProviderApp/bin/Release/net10.0-windows/win-x64/Microsoft.CSharp.dll
  94. BIN
      ProviderApp/bin/Release/net10.0-windows/win-x64/Microsoft.DiaSymReader.Native.amd64.dll
  95. BIN
      ProviderApp/bin/Release/net10.0-windows/win-x64/Microsoft.Extensions.Configuration.Abstractions.dll
  96. BIN
      ProviderApp/bin/Release/net10.0-windows/win-x64/Microsoft.Extensions.Configuration.FileExtensions.dll
  97. BIN
      ProviderApp/bin/Release/net10.0-windows/win-x64/Microsoft.Extensions.Configuration.Json.dll
  98. BIN
      ProviderApp/bin/Release/net10.0-windows/win-x64/Microsoft.Extensions.Configuration.dll
  99. BIN
      ProviderApp/bin/Release/net10.0-windows/win-x64/Microsoft.Extensions.DependencyInjection.Abstractions.dll
  100. BIN
      ProviderApp/bin/Release/net10.0-windows/win-x64/Microsoft.Extensions.FileProviders.Abstractions.dll

BIN
ProviderApp/.gitignore


+ 155 - 0
ProviderApp/AddEditContractForm.Designer.cs

@@ -0,0 +1,155 @@
+namespace ProviderApp
+{
+    partial class AddEditContractForm
+    {
+        private System.ComponentModel.IContainer components = null;
+        private Guna.UI2.WinForms.Guna2ComboBox cmbClient;
+        private Guna.UI2.WinForms.Guna2ComboBox cmbTariff;
+        private Guna.UI2.WinForms.Guna2ComboBox cmbEmployee;
+        private Guna.UI2.WinForms.Guna2DateTimePicker dtpSignDate;
+        private Guna.UI2.WinForms.Guna2ComboBox cmbStatus;
+        private Guna.UI2.WinForms.Guna2Button btnSave;
+        private Guna.UI2.WinForms.Guna2Button btnCancel;
+        private Guna.UI2.WinForms.Guna2Panel panel;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null)) components.Dispose();
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.components = new System.ComponentModel.Container();
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(500, 530);
+            this.StartPosition = FormStartPosition.CenterParent;
+            this.FormBorderStyle = FormBorderStyle.FixedDialog;
+            this.MaximizeBox = false;
+            this.MinimizeBox = false;
+            this.BackColor = System.Drawing.Color.FromArgb(30, 30, 30);
+            this.Text = "Договор";
+
+            // panel
+            this.panel = new Guna.UI2.WinForms.Guna2Panel();
+            this.panel.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.panel.BackColor = System.Drawing.Color.FromArgb(30, 30, 30);
+            this.panel.Padding = new System.Windows.Forms.Padding(20);
+            this.Controls.Add(this.panel);
+
+            int y = 20;
+            int step = 70;
+
+            // Title
+            var lblTitle = new Guna.UI2.WinForms.Guna2HtmlLabel();
+            lblTitle.Text = "Договор";
+            lblTitle.Font = new System.Drawing.Font("Segoe UI", 20F, System.Drawing.FontStyle.Bold);
+            lblTitle.ForeColor = System.Drawing.Color.White;
+            lblTitle.BackColor = System.Drawing.Color.Transparent;
+            lblTitle.Location = new System.Drawing.Point(20, 15);
+            lblTitle.Size = new System.Drawing.Size(110, 39);
+            this.panel.Controls.Add(lblTitle);
+            y = 70;
+
+            // Client
+            AddLabel("Клиент", y, this.panel);
+            this.cmbClient = CreateComboBox(130, y, 320);
+            this.panel.Controls.Add(this.cmbClient);
+            y += step;
+
+            // Tariff
+            AddLabel("Тариф", y, this.panel);
+            this.cmbTariff = CreateComboBox(130, y, 320);
+            this.panel.Controls.Add(this.cmbTariff);
+            y += step;
+
+            // Employee
+            AddLabel("Сотрудник", y, this.panel);
+            this.cmbEmployee = CreateComboBox(130, y, 320);
+            this.panel.Controls.Add(this.cmbEmployee);
+            y += step;
+
+            // Sign Date
+            AddLabel("Дата подписания", y, this.panel);
+            this.dtpSignDate = CreateDatePicker(150, y);
+            this.panel.Controls.Add(this.dtpSignDate);
+            y += step;
+
+            // Status
+            AddLabel("Статус", y, this.panel);
+            this.cmbStatus = CreateComboBox(130, y, 200);
+            this.cmbStatus.Items.AddRange(new object[] { "active", "suspended", "closed" });
+            this.cmbStatus.SelectedIndex = 0;
+            this.panel.Controls.Add(this.cmbStatus);
+            y += 60;
+
+            // Buttons
+            this.btnSave = CreateButton("Сохранить", 100, y, System.Drawing.Color.FromArgb(0, 120, 215));
+            this.btnSave.Click += new System.EventHandler(this.BtnSave_Click);
+            this.panel.Controls.Add(this.btnSave);
+
+            this.btnCancel = CreateButton("Отмена", 260, y, System.Drawing.Color.FromArgb(108, 117, 125));
+            this.btnCancel.Click += new System.EventHandler(this.BtnCancel_Click);
+            this.panel.Controls.Add(this.btnCancel);
+        }
+
+        private void AddLabel(string text, int y, Guna.UI2.WinForms.Guna2Panel panel)
+        {
+            var lbl = new Guna.UI2.WinForms.Guna2HtmlLabel();
+            lbl.Text = text;
+            lbl.Font = new System.Drawing.Font("Segoe UI", 11F);
+            lbl.ForeColor = System.Drawing.Color.FromArgb(200, 200, 200);
+            lbl.BackColor = System.Drawing.Color.Transparent;
+            lbl.Location = new System.Drawing.Point(20, y + 12);
+            lbl.Size = new System.Drawing.Size(100, 22);
+            panel.Controls.Add(lbl);
+        }
+
+        private Guna.UI2.WinForms.Guna2ComboBox CreateComboBox(int x, int y, int width)
+        {
+            var cb = new Guna.UI2.WinForms.Guna2ComboBox();
+            cb.FillColor = System.Drawing.Color.FromArgb(45, 45, 45);
+            cb.ForeColor = System.Drawing.Color.White;
+            cb.BorderColor = System.Drawing.Color.FromArgb(80, 80, 80);
+            cb.BorderRadius = 8;
+            cb.BorderThickness = 1;
+            cb.Location = new System.Drawing.Point(x, y);
+            cb.Size = new System.Drawing.Size(width, 42);
+            cb.Font = new System.Drawing.Font("Segoe UI", 11F);
+            cb.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            return cb;
+        }
+
+        private Guna.UI2.WinForms.Guna2DateTimePicker CreateDatePicker(int x, int y)
+        {
+            var dt = new Guna.UI2.WinForms.Guna2DateTimePicker();
+            dt.FillColor = System.Drawing.Color.FromArgb(45, 45, 45);
+            dt.ForeColor = System.Drawing.Color.White;
+            dt.BorderColor = System.Drawing.Color.FromArgb(80, 80, 80);
+            dt.BorderRadius = 8;
+            dt.BorderThickness = 1;
+            dt.Format = System.Windows.Forms.DateTimePickerFormat.Short;
+            dt.Location = new System.Drawing.Point(x, y);
+            dt.Size = new System.Drawing.Size(200, 42);
+            dt.Font = new System.Drawing.Font("Segoe UI", 11F);
+            return dt;
+        }
+
+        private Guna.UI2.WinForms.Guna2Button CreateButton(string text, int x, int y, System.Drawing.Color color)
+        {
+            var btn = new Guna.UI2.WinForms.Guna2Button();
+            btn.Text = text;
+            btn.FillColor = color;
+            btn.ForeColor = System.Drawing.Color.White;
+            btn.BorderRadius = 8;
+            btn.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Bold);
+            btn.Location = new System.Drawing.Point(x, y);
+            btn.Size = new System.Drawing.Size(130, 45);
+            btn.HoverState.FillColor = System.Drawing.Color.FromArgb(
+                color.R - 30 > 0 ? color.R - 30 : 0,
+                color.G - 30 > 0 ? color.G - 30 : 0,
+                color.B - 30 > 0 ? color.B - 30 : 0);
+            return btn;
+        }
+    }
+}

+ 198 - 0
ProviderApp/AddEditContractForm.cs

@@ -0,0 +1,198 @@
+using System;
+using System.Data;
+using System.Windows.Forms;
+using Npgsql;
+
+namespace ProviderApp
+{
+    public partial class AddEditContractForm : Form
+    {
+        private int? contractId = null;
+
+        public AddEditContractForm(int? id = null)
+        {
+            contractId = id;
+            InitializeComponent();
+
+            if (contractId.HasValue)
+            {
+                Text = "Редактирование договора";
+                LoadContractData();
+            }
+            else
+            {
+                Text = "Добавление договора";
+                dtpSignDate.Value = DateTime.Now;
+                cmbStatus.SelectedItem = "active";
+            }
+
+            LoadComboBoxes();
+        }
+
+        private void LoadComboBoxes()
+        {
+            try
+            {
+                using (var conn = DBConnection.GetConnection())
+                {
+                    // Загрузка клиентов
+                    string sqlClients = "SELECT client_id, last_name || ' ' || first_name AS name FROM clients ORDER BY last_name";
+                    using (var cmd = new NpgsqlCommand(sqlClients, conn))
+                    using (var da = new NpgsqlDataAdapter(cmd))
+                    {
+                        DataTable dt = new DataTable();
+                        da.Fill(dt);
+                        cmbClient.DisplayMember = "name";
+                        cmbClient.ValueMember = "client_id";
+                        cmbClient.DataSource = dt;
+                    }
+
+                    // Загрузка тарифов
+                    string sqlTariffs = "SELECT tariff_id, name FROM tariffs ORDER BY name";
+                    using (var cmd = new NpgsqlCommand(sqlTariffs, conn))
+                    using (var da = new NpgsqlDataAdapter(cmd))
+                    {
+                        DataTable dt = new DataTable();
+                        da.Fill(dt);
+                        cmbTariff.DisplayMember = "name";
+                        cmbTariff.ValueMember = "tariff_id";
+                        cmbTariff.DataSource = dt;
+                    }
+
+                    // Загрузка сотрудников
+                    string sqlEmployees = "SELECT employee_id, last_name || ' ' || first_name AS name FROM employees ORDER BY last_name";
+                    using (var cmd = new NpgsqlCommand(sqlEmployees, conn))
+                    using (var da = new NpgsqlDataAdapter(cmd))
+                    {
+                        DataTable dt = new DataTable();
+                        da.Fill(dt);
+                        cmbEmployee.DisplayMember = "name";
+                        cmbEmployee.ValueMember = "employee_id";
+                        cmbEmployee.DataSource = dt;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки данных: {ex.Message}");
+            }
+        }
+
+        private void LoadContractData()
+        {
+            try
+            {
+                using (var conn = DBConnection.GetConnection())
+                {
+                    string sql = "SELECT client_id, tariff_id, employee_id, sign_date, status FROM contracts WHERE contract_id = @id";
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@id", contractId.Value);
+                        using (var reader = cmd.ExecuteReader())
+                        {
+                            if (reader.Read())
+                            {
+                                cmbClient.SelectedValue = reader["client_id"];
+                                cmbTariff.SelectedValue = reader["tariff_id"];
+                                cmbEmployee.SelectedValue = reader["employee_id"];
+
+                                // ИСПРАВЛЕНО: работаем с DateOnly напрямую
+                                object signDateObj = reader["sign_date"];
+                                if (signDateObj is DateOnly dateOnly)
+                                {
+                                    dtpSignDate.Value = dateOnly.ToDateTime(TimeOnly.MinValue);
+                                }
+                                else if (signDateObj is DateTime dateTime)
+                                {
+                                    dtpSignDate.Value = dateTime;
+                                }
+                                else
+                                {
+                                    dtpSignDate.Value = Convert.ToDateTime(signDateObj);
+                                }
+
+                                cmbStatus.SelectedItem = reader["status"].ToString();
+                            }
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки договора: {ex.Message}");
+            }
+        }
+
+        private void BtnSave_Click(object sender, EventArgs e)
+        {
+            if (cmbClient.SelectedValue == null || cmbTariff.SelectedValue == null || cmbEmployee.SelectedValue == null)
+            {
+                MessageBox.Show("Заполните все поля.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+                return;
+            }
+
+            try
+            {
+                using (var conn = DBConnection.GetConnection())
+                {
+                    if (contractId.HasValue)
+                    {
+                        // Редактирование
+                        string sql = @"UPDATE contracts SET client_id=@client, tariff_id=@tariff, employee_id=@employee, 
+                                      sign_date=@date, status=@status WHERE contract_id=@id";
+                        using (var cmd = new NpgsqlCommand(sql, conn))
+                        {
+                            cmd.Parameters.AddWithValue("@id", contractId.Value);
+                            cmd.Parameters.AddWithValue("@client", cmbClient.SelectedValue);
+                            cmd.Parameters.AddWithValue("@tariff", cmbTariff.SelectedValue);
+                            cmd.Parameters.AddWithValue("@employee", cmbEmployee.SelectedValue);
+                            cmd.Parameters.AddWithValue("@date", DateOnly.FromDateTime(dtpSignDate.Value));
+                            cmd.Parameters.AddWithValue("@status", cmbStatus.SelectedItem.ToString());
+                            cmd.ExecuteNonQuery();
+                        }
+                        MessageBox.Show("Договор успешно обновлён!", "Успех", MessageBoxButtons.OK, MessageBoxIcon.Information);
+                    }
+                    else
+                    {
+                        // Добавление
+                        string sql = @"INSERT INTO contracts (client_id, tariff_id, employee_id, sign_date, status) 
+                                      VALUES (@client, @tariff, @employee, @date, @status)
+                                      RETURNING contract_id";
+
+                        using (var cmd = new NpgsqlCommand(sql, conn))
+                        {
+                            cmd.Parameters.AddWithValue("@client", cmbClient.SelectedValue);
+                            cmd.Parameters.AddWithValue("@tariff", cmbTariff.SelectedValue);
+                            cmd.Parameters.AddWithValue("@employee", cmbEmployee.SelectedValue);
+                            cmd.Parameters.AddWithValue("@date", DateOnly.FromDateTime(dtpSignDate.Value));
+                            cmd.Parameters.AddWithValue("@status", cmbStatus.SelectedItem.ToString());
+
+                            int newId = Convert.ToInt32(cmd.ExecuteScalar());
+                            MessageBox.Show($"Договор №{newId} успешно добавлен!", "Успех",
+                                MessageBoxButtons.OK, MessageBoxIcon.Information);
+                        }
+                    }
+                }
+
+                DialogResult = DialogResult.OK;
+                Close();
+            }
+            catch (PostgresException ex) when (ex.SqlState == "23505")
+            {
+                MessageBox.Show("Ошибка: такой договор уже существует. Попробуйте снова.",
+                    "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка сохранения: {ex.Message}",
+                    "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
+            }
+        }
+
+        private void BtnCancel_Click(object sender, EventArgs e)
+        {
+            DialogResult = DialogResult.Cancel;
+            Close();
+        }
+    }
+}

+ 120 - 0
ProviderApp/AddEditContractForm.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>

+ 145 - 0
ProviderApp/AddEditTariffForm.Designer.cs

@@ -0,0 +1,145 @@
+using System;
+using System.Drawing;
+using System.Windows.Forms;
+using Npgsql;
+using Guna.UI2.WinForms;
+
+namespace ProviderApp
+{
+    public class AddEditTariffForm : Form
+    {
+        private int? tariffId = null;
+        private Guna2TextBox txtName;
+        private Guna2TextBox txtPrice;
+        private Guna2TextBox txtSpeed;
+        private Guna2Button btnSave;
+        private Guna2Button btnCancel;
+        private Label lblTitle;
+
+        public AddEditTariffForm(int? id = null)
+        {
+            tariffId = id;
+            this.Size = new Size(480, 420);
+            this.StartPosition = FormStartPosition.CenterParent;
+            this.BackColor = Color.FromArgb(30, 30, 30);
+            this.FormBorderStyle = FormBorderStyle.FixedDialog;
+            this.MaximizeBox = false;
+
+            lblTitle = new Label()
+            {
+                Text = tariffId.HasValue ? "Редактирование тарифа" : "Добавление тарифа",
+                ForeColor = Color.White,
+                Font = new Font("Segoe UI", 16F, FontStyle.Bold),
+                Location = new Point(20, 20),
+                AutoSize = true
+            };
+            this.Controls.Add(lblTitle);
+
+            int y = 80;
+            int step = 55;
+
+            Label lblName = new Label() { Text = "Название:", ForeColor = Color.FromArgb(200, 200, 200), Location = new Point(20, y + 10), Size = new Size(100, 25) };
+            txtName = new Guna2TextBox() { PlaceholderText = "Введите название", FillColor = Color.FromArgb(45, 45, 45), ForeColor = Color.White, BorderColor = Color.FromArgb(230, 92, 0), Location = new Point(130, y), Size = new Size(310, 40), BorderRadius = 8 };
+            this.Controls.Add(lblName);
+            this.Controls.Add(txtName);
+            y += step;
+
+            Label lblPrice = new Label() { Text = "Цена (руб):", ForeColor = Color.FromArgb(200, 200, 200), Location = new Point(20, y + 10), Size = new Size(100, 25) };
+            txtPrice = new Guna2TextBox() { PlaceholderText = "Введите цену", FillColor = Color.FromArgb(45, 45, 45), ForeColor = Color.White, BorderColor = Color.FromArgb(230, 92, 0), Location = new Point(130, y), Size = new Size(310, 40), BorderRadius = 8 };
+            this.Controls.Add(lblPrice);
+            this.Controls.Add(txtPrice);
+            y += step;
+
+            Label lblSpeed = new Label() { Text = "Скорость (Мбит/с):", ForeColor = Color.FromArgb(200, 200, 200), Location = new Point(20, y + 10), Size = new Size(100, 25) };
+            txtSpeed = new Guna2TextBox() { PlaceholderText = "Введите скорость", FillColor = Color.FromArgb(45, 45, 45), ForeColor = Color.White, BorderColor = Color.FromArgb(230, 92, 0), Location = new Point(130, y), Size = new Size(310, 40), BorderRadius = 8 };
+            this.Controls.Add(lblSpeed);
+            this.Controls.Add(txtSpeed);
+            y += step;
+
+            btnSave = new Guna2Button() { Text = "Сохранить", FillColor = Color.FromArgb(230, 92, 0), ForeColor = Color.White, Location = new Point(100, y + 20), Size = new Size(120, 40), BorderRadius = 8 };
+            btnSave.Click += BtnSave_Click;
+            this.Controls.Add(btnSave);
+
+            btnCancel = new Guna2Button() { Text = "Отмена", FillColor = Color.FromArgb(80, 80, 80), ForeColor = Color.White, Location = new Point(240, y + 20), Size = new Size(120, 40), BorderRadius = 8 };
+            btnCancel.Click += (s, e) => this.DialogResult = DialogResult.Cancel;
+            this.Controls.Add(btnCancel);
+
+            if (tariffId.HasValue)
+                LoadTariffData();
+        }
+
+        private void LoadTariffData()
+        {
+            try
+            {
+                using (var conn = DBConnection.GetConnection())
+                {
+                    string sql = "SELECT name, monthly_price, speed_mbit FROM tariffs WHERE tariff_id = @id";
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@id", tariffId.Value);
+                        using (var reader = cmd.ExecuteReader())
+                        {
+                            if (reader.Read())
+                            {
+                                txtName.Text = reader["name"].ToString();
+                                txtPrice.Text = reader["monthly_price"].ToString();
+                                txtSpeed.Text = reader["speed_mbit"].ToString();
+                            }
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки: {ex.Message}");
+            }
+        }
+
+        private void BtnSave_Click(object sender, EventArgs e)
+        {
+            if (string.IsNullOrWhiteSpace(txtName.Text) || string.IsNullOrWhiteSpace(txtPrice.Text) || string.IsNullOrWhiteSpace(txtSpeed.Text))
+            {
+                MessageBox.Show("Заполните все поля.");
+                return;
+            }
+
+            try
+            {
+                using (var conn = DBConnection.GetConnection())
+                {
+                    if (tariffId.HasValue)
+                    {
+                        string sql = "UPDATE tariffs SET name=@name, monthly_price=@price, speed_mbit=@speed WHERE tariff_id=@id";
+                        using (var cmd = new NpgsqlCommand(sql, conn))
+                        {
+                            cmd.Parameters.AddWithValue("@id", tariffId.Value);
+                            cmd.Parameters.AddWithValue("@name", txtName.Text);
+                            cmd.Parameters.AddWithValue("@price", decimal.Parse(txtPrice.Text));
+                            cmd.Parameters.AddWithValue("@speed", int.Parse(txtSpeed.Text));
+                            cmd.ExecuteNonQuery();
+                        }
+                        MessageBox.Show("Тариф обновлён!");
+                    }
+                    else
+                    {
+                        string sql = @"INSERT INTO tariffs (name, monthly_price, speed_mbit) VALUES (@name, @price, @speed) RETURNING tariff_id";
+                        using (var cmd = new NpgsqlCommand(sql, conn))
+                        {
+                            cmd.Parameters.AddWithValue("@name", txtName.Text);
+                            cmd.Parameters.AddWithValue("@price", decimal.Parse(txtPrice.Text));
+                            cmd.Parameters.AddWithValue("@speed", int.Parse(txtSpeed.Text));
+                            int newId = Convert.ToInt32(cmd.ExecuteScalar());
+                            MessageBox.Show($"Тариф №{newId} добавлен!");
+                        }
+                    }
+                }
+                this.DialogResult = DialogResult.OK;
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}");
+            }
+        }
+    }
+}

+ 184 - 0
ProviderApp/AddEditTariffForm.cs

@@ -0,0 +1,184 @@
+using System;
+using System.Drawing;
+using System.Windows.Forms;
+using Npgsql;
+using Guna.UI2.WinForms;
+
+namespace ProviderApp
+{
+    public class AddEditClientForm : Form
+    {
+        private int? clientId = null;
+        private Guna2TextBox txtLastName, txtFirstName, txtMiddleName, txtPhone, txtAddress, txtBirthDate;
+        private Guna2Button btnSave, btnCancel;
+        private Label lblTitle;
+
+        public AddEditClientForm(int? id = null)
+        {
+            clientId = id;
+            this.Size = new Size(500, 550);
+            this.StartPosition = FormStartPosition.CenterParent;
+            this.BackColor = Color.FromArgb(30, 30, 30);
+            this.FormBorderStyle = FormBorderStyle.FixedDialog;
+            this.MaximizeBox = false;
+
+            lblTitle = new Label()
+            {
+                Text = id.HasValue ? "Редактирование клиента" : "Добавление клиента",
+                ForeColor = Color.White,
+                Font = new Font("Segoe UI", 16F, FontStyle.Bold),
+                Location = new Point(20, 20),
+                AutoSize = true
+            };
+            this.Controls.Add(lblTitle);
+
+            int y = 80, step = 55;
+
+            txtLastName = AddField("Фамилия:", ref y, step);
+            txtFirstName = AddField("Имя:", ref y, step);
+            txtMiddleName = AddField("Отчество:", ref y, step);
+            txtBirthDate = AddField("Дата рождения (ГГГГ-ММ-ДД):", ref y, step);
+            txtPhone = AddField("Телефон:", ref y, step);
+            txtAddress = AddField("Адрес:", ref y, step);
+
+            btnSave = new Guna2Button()
+            {
+                Text = "Сохранить",
+                FillColor = Color.FromArgb(230, 92, 0),
+                ForeColor = Color.White,
+                Location = new Point(100, y + 20),
+                Size = new Size(120, 40),
+                BorderRadius = 8
+            };
+            btnSave.Click += BtnSave_Click;
+            this.Controls.Add(btnSave);
+
+            btnCancel = new Guna2Button()
+            {
+                Text = "Отмена",
+                FillColor = Color.FromArgb(80, 80, 80),
+                ForeColor = Color.White,
+                Location = new Point(240, y + 20),
+                Size = new Size(120, 40),
+                BorderRadius = 8
+            };
+            btnCancel.Click += (s, e) => this.DialogResult = DialogResult.Cancel;
+            this.Controls.Add(btnCancel);
+
+            if (id.HasValue) LoadData();
+        }
+
+        private Guna2TextBox AddField(string label, ref int y, int step)
+        {
+            var lbl = new Label()
+            {
+                Text = label,
+                ForeColor = Color.FromArgb(200, 200, 200),
+                Location = new Point(20, y + 10),
+                Size = new Size(160, 25)
+            };
+            this.Controls.Add(lbl);
+
+            var tb = new Guna2TextBox()
+            {
+                PlaceholderText = "Введите " + label.TrimEnd(':'),
+                FillColor = Color.FromArgb(45, 45, 45),
+                ForeColor = Color.White,
+                BorderColor = Color.FromArgb(230, 92, 0),
+                Location = new Point(190, y),
+                Size = new Size(260, 40),
+                BorderRadius = 8
+            };
+            this.Controls.Add(tb);
+            y += step;
+            return tb;
+        }
+
+        private void LoadData()
+        {
+            using (var conn = DBConnection.GetConnection())
+            {
+                string sql = "SELECT last_name, first_name, middle_name, phone, address, birth_date FROM clients WHERE client_id = @id";
+                using (var cmd = new NpgsqlCommand(sql, conn))
+                {
+                    cmd.Parameters.AddWithValue("@id", clientId.Value);
+                    using (var r = cmd.ExecuteReader())
+                    {
+                        if (r.Read())
+                        {
+                            txtLastName.Text = r["last_name"].ToString();
+                            txtFirstName.Text = r["first_name"].ToString();
+                            txtMiddleName.Text = r["middle_name"].ToString();
+                            txtPhone.Text = r["phone"].ToString();
+                            txtAddress.Text = r["address"].ToString();
+
+                            // Используем DateOnly вместо DateTime
+                            if (r["birth_date"] != DBNull.Value)
+                            {
+                                DateOnly birthDate = (DateOnly)r["birth_date"];
+                                txtBirthDate.Text = birthDate.ToString("yyyy-MM-dd");
+                            }
+                            else
+                            {
+                                txtBirthDate.Text = "";
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        private void BtnSave_Click(object sender, EventArgs e)
+        {
+            if (string.IsNullOrWhiteSpace(txtLastName.Text) || string.IsNullOrWhiteSpace(txtFirstName.Text))
+            {
+                MessageBox.Show("Заполните фамилию и имя.");
+                return;
+            }
+
+            using (var conn = DBConnection.GetConnection())
+            {
+                if (clientId.HasValue)
+                {
+                    string sql = @"UPDATE clients SET 
+                last_name=@last, first_name=@first, middle_name=@middle, 
+                phone=@phone, address=@address, birth_date=@birth_date
+                WHERE client_id=@id";
+
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@id", clientId.Value);
+                        cmd.Parameters.AddWithValue("@last", txtLastName.Text);
+                        cmd.Parameters.AddWithValue("@first", txtFirstName.Text);
+                        cmd.Parameters.AddWithValue("@middle", txtMiddleName.Text);
+                        cmd.Parameters.AddWithValue("@phone", txtPhone.Text);
+                        cmd.Parameters.AddWithValue("@address", txtAddress.Text);
+                        cmd.Parameters.AddWithValue("@birth_date", string.IsNullOrEmpty(txtBirthDate.Text) ? DBNull.Value : (object)DateOnly.Parse(txtBirthDate.Text));
+                        cmd.ExecuteNonQuery();
+                    }
+                    MessageBox.Show("Клиент обновлён!");
+                }
+                else
+                {
+                    string sql = @"INSERT INTO clients (last_name, first_name, middle_name, phone, address, birth_date) 
+                          VALUES (@last, @first, @middle, @phone, @address, @birth_date)
+                          RETURNING client_id";
+
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@last", txtLastName.Text);
+                        cmd.Parameters.AddWithValue("@first", txtFirstName.Text);
+                        cmd.Parameters.AddWithValue("@middle", txtMiddleName.Text);
+                        cmd.Parameters.AddWithValue("@phone", txtPhone.Text);
+                        cmd.Parameters.AddWithValue("@address", txtAddress.Text);
+                        cmd.Parameters.AddWithValue("@birth_date", string.IsNullOrEmpty(txtBirthDate.Text) ? DBNull.Value : (object)DateOnly.Parse(txtBirthDate.Text));
+                        int newId = Convert.ToInt32(cmd.ExecuteScalar());
+                        MessageBox.Show($"Клиент №{newId} добавлен!");
+                    }
+                }
+            }
+            this.DialogResult = DialogResult.OK;
+            this.Close();
+        }
+    }
+}

+ 120 - 0
ProviderApp/AddEditTariffForm.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>

+ 278 - 0
ProviderApp/AnalyticsForm.Designer.cs

@@ -0,0 +1,278 @@
+using System.Drawing;
+using System.Windows.Forms;
+using Guna.UI2.WinForms;
+
+namespace ProviderApp
+{
+    partial class AnalyticsForm
+    {
+        private System.ComponentModel.IContainer components = null;
+        private Guna2Panel panel;
+        private Guna2HtmlLabel lblTitle;
+        private Guna2Button btnShow;
+        private Guna2Button btnExport;
+        private Guna2Button btnExportRevenue;
+        private Guna2Button btnFinancialReport;
+        private Guna2DataGridView dgvResult;
+        private Guna2DateTimePicker dtpFrom;
+        private Guna2DateTimePicker dtpTo;
+        private Guna2HtmlLabel lblFrom;
+        private Guna2HtmlLabel lblTo;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null)) components.Dispose();
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges13 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges14 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges1 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges2 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges3 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges4 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges5 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges6 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges7 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges8 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges9 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges10 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges11 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges12 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            DataGridViewCellStyle dataGridViewCellStyle1 = new DataGridViewCellStyle();
+            DataGridViewCellStyle dataGridViewCellStyle2 = new DataGridViewCellStyle();
+            DataGridViewCellStyle dataGridViewCellStyle3 = new DataGridViewCellStyle();
+            panel = new Guna2Panel();
+            lblTitle = new Guna2HtmlLabel();
+            lblFrom = new Guna2HtmlLabel();
+            dtpFrom = new Guna2DateTimePicker();
+            lblTo = new Guna2HtmlLabel();
+            dtpTo = new Guna2DateTimePicker();
+            btnShow = new Guna2Button();
+            btnExport = new Guna2Button();
+            btnExportRevenue = new Guna2Button();
+            btnFinancialReport = new Guna2Button();
+            dgvResult = new Guna2DataGridView();
+            panel.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)dgvResult).BeginInit();
+            SuspendLayout();
+            // 
+            // panel
+            // 
+            panel.BackColor = Color.FromArgb(30, 30, 30);
+            panel.Controls.Add(lblTitle);
+            panel.Controls.Add(lblFrom);
+            panel.Controls.Add(dtpFrom);
+            panel.Controls.Add(lblTo);
+            panel.Controls.Add(dtpTo);
+            panel.Controls.Add(btnShow);
+            panel.Controls.Add(btnExport);
+            panel.Controls.Add(btnExportRevenue);
+            panel.Controls.Add(btnFinancialReport);
+            panel.Controls.Add(dgvResult);
+            panel.CustomizableEdges = customizableEdges13;
+            panel.Dock = DockStyle.Fill;
+            panel.Location = new Point(0, 0);
+            panel.Name = "panel";
+            panel.Padding = new Padding(26, 28, 26, 28);
+            panel.ShadowDecoration.CustomizableEdges = customizableEdges14;
+            panel.Size = new Size(1006, 797);
+            panel.TabIndex = 0;
+            // 
+            // lblTitle
+            // 
+            lblTitle.BackColor = Color.Transparent;
+            lblTitle.Font = new Font("Segoe UI", 20F, FontStyle.Bold);
+            lblTitle.ForeColor = Color.White;
+            lblTitle.Location = new Point(26, 19);
+            lblTitle.Name = "lblTitle";
+            lblTitle.Size = new Size(268, 39);
+            lblTitle.TabIndex = 0;
+            lblTitle.Text = "Аналитика тарифов";
+            // 
+            // lblFrom
+            // 
+            lblFrom.BackColor = Color.Transparent;
+            lblFrom.Font = new Font("Segoe UI", 10F);
+            lblFrom.ForeColor = Color.White;
+            lblFrom.Location = new Point(26, 84);
+            lblFrom.Name = "lblFrom";
+            lblFrom.Size = new Size(46, 19);
+            lblFrom.TabIndex = 1;
+            lblFrom.Text = "С даты:";
+            // 
+            // dtpFrom
+            // 
+            dtpFrom.BorderRadius = 6;
+            dtpFrom.Checked = true;
+            dtpFrom.CustomizableEdges = customizableEdges1;
+            dtpFrom.FillColor = Color.FromArgb(45, 45, 45);
+            dtpFrom.Font = new Font("Segoe UI", 10F);
+            dtpFrom.ForeColor = Color.White;
+            dtpFrom.Format = DateTimePickerFormat.Short;
+            dtpFrom.Location = new Point(79, 80);
+            dtpFrom.MaxDate = new DateTime(9998, 12, 31, 0, 0, 0, 0);
+            dtpFrom.MinDate = new DateTime(1753, 1, 1, 0, 0, 0, 0);
+            dtpFrom.Name = "dtpFrom";
+            dtpFrom.ShadowDecoration.CustomizableEdges = customizableEdges2;
+            dtpFrom.Size = new Size(133, 28);
+            dtpFrom.TabIndex = 2;
+            dtpFrom.Value = new DateTime(2026, 5, 26, 10, 29, 57, 538);
+            // 
+            // lblTo
+            // 
+            lblTo.BackColor = Color.Transparent;
+            lblTo.Font = new Font("Segoe UI", 10F);
+            lblTo.ForeColor = Color.White;
+            lblTo.Location = new Point(230, 84);
+            lblTo.Name = "lblTo";
+            lblTo.Size = new Size(23, 19);
+            lblTo.TabIndex = 3;
+            lblTo.Text = "По:";
+            // 
+            // dtpTo
+            // 
+            dtpTo.BorderRadius = 6;
+            dtpTo.Checked = true;
+            dtpTo.CustomizableEdges = customizableEdges3;
+            dtpTo.FillColor = Color.FromArgb(45, 45, 45);
+            dtpTo.Font = new Font("Segoe UI", 10F);
+            dtpTo.ForeColor = Color.White;
+            dtpTo.Format = DateTimePickerFormat.Short;
+            dtpTo.Location = new Point(272, 80);
+            dtpTo.MaxDate = new DateTime(9998, 12, 31, 0, 0, 0, 0);
+            dtpTo.MinDate = new DateTime(1753, 1, 1, 0, 0, 0, 0);
+            dtpTo.Name = "dtpTo";
+            dtpTo.ShadowDecoration.CustomizableEdges = customizableEdges4;
+            dtpTo.Size = new Size(139, 28);
+            dtpTo.TabIndex = 4;
+            dtpTo.Value = new DateTime(2026, 5, 26, 10, 29, 57, 559);
+            // 
+            // btnShow
+            // 
+            btnShow.BorderRadius = 8;
+            btnShow.CustomizableEdges = customizableEdges5;
+            btnShow.FillColor = Color.Green;
+            btnShow.Font = new Font("Segoe UI", 10F, FontStyle.Bold);
+            btnShow.ForeColor = Color.White;
+            btnShow.Location = new Point(431, 70);
+            btnShow.Name = "btnShow";
+            btnShow.ShadowDecoration.CustomizableEdges = customizableEdges6;
+            btnShow.Size = new Size(145, 38);
+            btnShow.TabIndex = 5;
+            btnShow.Text = "Показать";
+            // 
+            // btnExport
+            // 
+            btnExport.BorderRadius = 8;
+            btnExport.CustomizableEdges = customizableEdges7;
+            btnExport.FillColor = Color.FromArgb(0, 120, 215);
+            btnExport.Font = new Font("Segoe UI", 11F, FontStyle.Bold);
+            btnExport.ForeColor = Color.White;
+            btnExport.Location = new Point(30, 132);
+            btnExport.Name = "btnExport";
+            btnExport.ShadowDecoration.CustomizableEdges = customizableEdges8;
+            btnExport.Size = new Size(241, 42);
+            btnExport.TabIndex = 6;
+            btnExport.Text = "📊 Экспорт аналитики";
+            // 
+            // btnExportRevenue
+            // 
+            btnExportRevenue.BorderRadius = 8;
+            btnExportRevenue.CustomizableEdges = customizableEdges9;
+            btnExportRevenue.FillColor = Color.FromArgb(0, 120, 215);
+            btnExportRevenue.Font = new Font("Segoe UI", 11F, FontStyle.Bold);
+            btnExportRevenue.ForeColor = Color.White;
+            btnExportRevenue.Location = new Point(285, 132);
+            btnExportRevenue.Name = "btnExportRevenue";
+            btnExportRevenue.ShadowDecoration.CustomizableEdges = customizableEdges10;
+            btnExportRevenue.Size = new Size(231, 42);
+            btnExportRevenue.TabIndex = 7;
+            btnExportRevenue.Text = "💰 Отчёт по выручке";
+            // 
+            // btnFinancialReport
+            // 
+            btnFinancialReport.BorderRadius = 8;
+            btnFinancialReport.CustomizableEdges = customizableEdges11;
+            btnFinancialReport.FillColor = Color.FromArgb(0, 120, 215);
+            btnFinancialReport.Font = new Font("Segoe UI", 11F, FontStyle.Bold);
+            btnFinancialReport.ForeColor = Color.White;
+            btnFinancialReport.Location = new Point(531, 132);
+            btnFinancialReport.Name = "btnFinancialReport";
+            btnFinancialReport.ShadowDecoration.CustomizableEdges = customizableEdges12;
+            btnFinancialReport.Size = new Size(216, 42);
+            btnFinancialReport.TabIndex = 8;
+            btnFinancialReport.Text = "💰 Финансовый отчёт";
+           
+            // 
+            // dgvResult
+            // 
+            dgvResult.AllowUserToAddRows = false;
+            dgvResult.AllowUserToDeleteRows = false;
+            dataGridViewCellStyle1.BackColor = Color.White;
+            dgvResult.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle1;
+            dgvResult.BackgroundColor = Color.FromArgb(40, 40, 40);
+            dataGridViewCellStyle2.Alignment = DataGridViewContentAlignment.MiddleLeft;
+            dataGridViewCellStyle2.BackColor = Color.FromArgb(100, 88, 255);
+            dataGridViewCellStyle2.Font = new Font("Segoe UI", 9F);
+            dataGridViewCellStyle2.ForeColor = Color.White;
+            dataGridViewCellStyle2.SelectionBackColor = SystemColors.Highlight;
+            dataGridViewCellStyle2.SelectionForeColor = SystemColors.HighlightText;
+            dataGridViewCellStyle2.WrapMode = DataGridViewTriState.True;
+            dgvResult.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle2;
+            dgvResult.ColumnHeadersHeight = 50;
+            dataGridViewCellStyle3.Alignment = DataGridViewContentAlignment.MiddleLeft;
+            dataGridViewCellStyle3.BackColor = Color.White;
+            dataGridViewCellStyle3.Font = new Font("Segoe UI", 9F);
+            dataGridViewCellStyle3.ForeColor = Color.FromArgb(71, 69, 94);
+            dataGridViewCellStyle3.SelectionBackColor = Color.FromArgb(231, 229, 255);
+            dataGridViewCellStyle3.SelectionForeColor = Color.FromArgb(71, 69, 94);
+            dataGridViewCellStyle3.WrapMode = DataGridViewTriState.False;
+            dgvResult.DefaultCellStyle = dataGridViewCellStyle3;
+            dgvResult.GridColor = Color.FromArgb(60, 60, 60);
+            dgvResult.Location = new Point(26, 197);
+            dgvResult.Name = "dgvResult";
+            dgvResult.ReadOnly = true;
+            dgvResult.RowHeadersVisible = false;
+            dgvResult.RowTemplate.Height = 40;
+            dgvResult.Size = new Size(954, 562);
+            dgvResult.TabIndex = 9;
+            dgvResult.ThemeStyle.AlternatingRowsStyle.BackColor = Color.White;
+            dgvResult.ThemeStyle.AlternatingRowsStyle.Font = null;
+            dgvResult.ThemeStyle.AlternatingRowsStyle.ForeColor = Color.Empty;
+            dgvResult.ThemeStyle.AlternatingRowsStyle.SelectionBackColor = Color.Empty;
+            dgvResult.ThemeStyle.AlternatingRowsStyle.SelectionForeColor = Color.Empty;
+            dgvResult.ThemeStyle.BackColor = Color.FromArgb(40, 40, 40);
+            dgvResult.ThemeStyle.GridColor = Color.FromArgb(60, 60, 60);
+            dgvResult.ThemeStyle.HeaderStyle.BackColor = Color.FromArgb(100, 88, 255);
+            dgvResult.ThemeStyle.HeaderStyle.BorderStyle = DataGridViewHeaderBorderStyle.None;
+            dgvResult.ThemeStyle.HeaderStyle.Font = new Font("Segoe UI", 9F);
+            dgvResult.ThemeStyle.HeaderStyle.ForeColor = Color.White;
+            dgvResult.ThemeStyle.HeaderStyle.HeaightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
+            dgvResult.ThemeStyle.HeaderStyle.Height = 50;
+            dgvResult.ThemeStyle.ReadOnly = true;
+            dgvResult.ThemeStyle.RowsStyle.BackColor = Color.White;
+            dgvResult.ThemeStyle.RowsStyle.BorderStyle = DataGridViewCellBorderStyle.SingleHorizontal;
+            dgvResult.ThemeStyle.RowsStyle.Font = new Font("Segoe UI", 9F);
+            dgvResult.ThemeStyle.RowsStyle.ForeColor = Color.FromArgb(71, 69, 94);
+            dgvResult.ThemeStyle.RowsStyle.Height = 40;
+            dgvResult.ThemeStyle.RowsStyle.SelectionBackColor = Color.FromArgb(231, 229, 255);
+            dgvResult.ThemeStyle.RowsStyle.SelectionForeColor = Color.FromArgb(71, 69, 94);
+            // 
+            // AnalyticsForm
+            // 
+            AutoScaleDimensions = new SizeF(7F, 15F);
+            AutoScaleMode = AutoScaleMode.Font;
+            BackColor = Color.FromArgb(30, 30, 30);
+            Controls.Add(panel);
+            Name = "AnalyticsForm";
+            Size = new Size(1006, 797);
+            panel.ResumeLayout(false);
+            panel.PerformLayout();
+            ((System.ComponentModel.ISupportInitialize)dgvResult).EndInit();
+            ResumeLayout(false);
+        }
+    }
+}

+ 228 - 0
ProviderApp/AnalyticsForm.cs

@@ -0,0 +1,228 @@
+using System;
+using System.Data;
+using System.Windows.Forms;
+using Npgsql;
+
+namespace ProviderApp
+{
+    public partial class AnalyticsForm : UserControl
+    {
+        public AnalyticsForm()
+        {
+            InitializeComponent();
+            this.Load += AnalyticsForm_Load;
+            btnShow.Click += BtnShow_Click;
+            btnExport.Click += BtnExport_Click;
+            btnExportRevenue.Click += BtnExportRevenue_Click;
+            btnFinancialReport.Click += BtnFinancialReport_Click;
+            StyleDataGridView();
+        }
+
+        public void RefreshData()
+        {
+            ExecuteAnalytics();
+        }
+
+        private void StyleDataGridView()
+        {
+            dgvResult.ColumnHeadersDefaultCellStyle.BackColor = System.Drawing.Color.FromArgb(230, 92, 0);
+            dgvResult.ColumnHeadersDefaultCellStyle.ForeColor = System.Drawing.Color.White;
+            dgvResult.ColumnHeadersDefaultCellStyle.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Bold);
+            dgvResult.DefaultCellStyle.BackColor = System.Drawing.Color.FromArgb(40, 40, 40);
+            dgvResult.DefaultCellStyle.ForeColor = System.Drawing.Color.White;
+            dgvResult.AlternatingRowsDefaultCellStyle.BackColor = System.Drawing.Color.FromArgb(45, 45, 45);
+            dgvResult.EnableHeadersVisualStyles = false;
+        }
+
+        private void AnalyticsForm_Load(object sender, EventArgs e)
+        {
+            dtpFrom.Value = new DateTime(2024, 1, 1);
+            dtpTo.Value = new DateTime(2024, 12, 31);
+            ExecuteAnalytics();
+
+            btnExport.Visible = AuthHelper.CurrentUser.IsManager;
+            btnExportRevenue.Visible = AuthHelper.CurrentUser.IsManager;
+            btnFinancialReport.Visible = AuthHelper.CurrentUser.IsManager;
+        }
+
+        private void BtnShow_Click(object sender, EventArgs e)
+        {
+            ExecuteAnalytics();
+        }
+
+        private void ExecuteAnalytics()
+        {
+            var conn = DBConnection.GetConnection();
+            DateTime fromDate = dtpFrom.Value.Date;
+            DateTime toDate = dtpTo.Value.Date;
+
+            string callProc = "CALL sizov_ev.get_top_tariffs_by_period(@date_from, @date_to)";
+            var cmd = new NpgsqlCommand(callProc, conn);
+            cmd.Parameters.Add("@date_from", NpgsqlTypes.NpgsqlDbType.Date).Value = fromDate;
+            cmd.Parameters.Add("@date_to", NpgsqlTypes.NpgsqlDbType.Date).Value = toDate;
+            cmd.ExecuteNonQuery();
+            cmd.Dispose();
+
+            string selectSql = @"
+    SELECT 
+        tariff_name as ""Тариф"",
+        monthly_price as ""Цена, руб"",
+        connection_count as ""Количество подключений""
+    FROM sizov_ev.top_tariffs_result
+    ORDER BY connection_count DESC";
+
+            var cmd2 = new NpgsqlCommand(selectSql, conn);
+            DataTable dt = new DataTable();
+            var da = new NpgsqlDataAdapter(cmd2);
+            da.Fill(dt);
+            dgvResult.DataSource = dt;
+
+            da.Dispose();
+            cmd2.Dispose();
+            conn.Dispose();
+        }
+
+        private void BtnFinancialReport_Click(object sender, EventArgs e)
+        {
+            if (!AuthHelper.CheckPermission("manager")) return;
+
+            try
+            {
+                using (var conn = DBConnection.GetConnection())
+                {
+                    string sql = @"
+                        SELECT 
+                            t.tariff_id,
+                            t.name as tariff_name,
+                            t.monthly_price,
+                            COUNT(DISTINCT c.contract_id) AS contracts_count,
+                            COALESCE(SUM(p.amount), 0) AS total_revenue
+                        FROM sizov_ev.tariffs t
+                        LEFT JOIN sizov_ev.contracts c ON t.tariff_id = c.tariff_id
+                        LEFT JOIN sizov_ev.payments p ON c.contract_id = p.contract_id
+                        GROUP BY t.tariff_id, t.name, t.monthly_price
+                        ORDER BY total_revenue DESC";
+
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        DataTable dt = new DataTable();
+                        using (var da = new NpgsqlDataAdapter(cmd))
+                        {
+                            da.Fill(dt);
+                        }
+
+                        dgvResult.DataSource = dt;
+
+                        if (dgvResult.Columns["tariff_id"] != null)
+                            dgvResult.Columns["tariff_id"].HeaderText = "ID";
+                        if (dgvResult.Columns["tariff_name"] != null)
+                            dgvResult.Columns["tariff_name"].HeaderText = "Тариф";
+                        if (dgvResult.Columns["monthly_price"] != null)
+                            dgvResult.Columns["monthly_price"].HeaderText = "Цена, руб";
+                        if (dgvResult.Columns["contracts_count"] != null)
+                            dgvResult.Columns["contracts_count"].HeaderText = "Кол-во договоров";
+                        if (dgvResult.Columns["total_revenue"] != null)
+                            dgvResult.Columns["total_revenue"].HeaderText = "Выручка, руб";
+
+                        AuthHelper.LogAction("Просмотр финансового отчёта", "analytics");
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}");
+            }
+        }
+
+        private void BtnExport_Click(object sender, EventArgs e)
+        {
+            if (!AuthHelper.CheckPermission("manager")) return;
+
+            try
+            {
+                using (var conn = DBConnection.GetConnection())
+                {
+                    DateTime fromDate = dtpFrom.Value.Date;
+                    DateTime toDate = dtpTo.Value.Date;
+
+                    string sql = @"
+                        SELECT 
+                            t.name as ""Тариф"",
+                            t.monthly_price as ""Цена, руб"",
+                            COUNT(c.contract_id) as ""Количество подключений""
+                        FROM sizov_ev.tariffs t
+                        LEFT JOIN sizov_ev.contracts c ON t.tariff_id = c.tariff_id 
+                            AND c.sign_date BETWEEN @date_from AND @date_to
+                        GROUP BY t.tariff_id, t.name, t.monthly_price
+                        ORDER BY COUNT(c.contract_id) DESC";
+
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@date_from", fromDate);
+                        cmd.Parameters.AddWithValue("@date_to", toDate);
+
+                        DataTable dt = new DataTable();
+                        using (var da = new NpgsqlDataAdapter(cmd))
+                        {
+                            da.Fill(dt);
+                        }
+                        AuthHelper.LogAction("Экспорт аналитики в CSV", "analytics");
+                        CsvExporter.ExportToCsv(dt, $"Аналитика_тарифов_{DateTime.Now:yyyyMMdd_HHmmss}.csv");
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка экспорта: {ex.Message}");
+            }
+        }
+
+        private void BtnExportRevenue_Click(object sender, EventArgs e)
+        {
+            if (!AuthHelper.CheckPermission("manager")) return;
+
+            try
+            {
+                using (var conn = DBConnection.GetConnection())
+                {
+                    string sql = @"
+                        SELECT 
+                            t.name as ""Тариф"",
+                            t.monthly_price as ""Цена, руб"",
+                            COUNT(DISTINCT c.contract_id) as ""Кол-во договоров"",
+                            COALESCE(SUM(p.amount), 0) as ""Выручка, руб""
+                        FROM sizov_ev.tariffs t
+                        LEFT JOIN sizov_ev.contracts c ON t.tariff_id = c.tariff_id
+                        LEFT JOIN sizov_ev.payments p ON c.contract_id = p.contract_id
+                        GROUP BY t.tariff_id, t.name, t.monthly_price
+                        ORDER BY SUM(p.amount) DESC";
+
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        DataTable dt = new DataTable();
+                        using (var da = new NpgsqlDataAdapter(cmd))
+                        {
+                            da.Fill(dt);
+                        }
+
+                        decimal totalRevenue = 0;
+                        foreach (DataRow row in dt.Rows)
+                        {
+                            if (row["Выручка, руб"] != DBNull.Value)
+                                totalRevenue += Convert.ToDecimal(row["Выручка, руб"]);
+                        }
+
+                        AuthHelper.LogAction("Экспорт финансового отчёта", "analytics");
+                        CsvExporter.ExportToCsv(dt, $"Финансовый_отчёт_{DateTime.Now:yyyyMMdd_HHmmss}.csv");
+                        MessageBox.Show($"Общая выручка: {totalRevenue:C2}", "Итого",
+                            MessageBoxButtons.OK, MessageBoxIcon.Information);
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}");
+            }
+        }
+    }
+}

+ 120 - 0
ProviderApp/AnalyticsForm.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>

+ 283 - 0
ProviderApp/AuthHelper.cs

@@ -0,0 +1,283 @@
+using System;
+using System.Security.Cryptography;
+using System.Text;
+using System.Windows.Forms;
+using Npgsql;
+
+namespace ProviderApp
+{
+    public static class AuthHelper
+    {
+        public static class CurrentUser
+        {
+            public static int Id { get; set; }
+            public static string Username { get; set; }
+            public static string FullName { get; set; }
+            public static string Role { get; set; }
+            public static bool IsAuthenticated { get; set; }
+            public static bool IsAdmin => Role == "admin";
+            public static bool IsManager => Role == "manager" || Role == "admin";
+        }
+
+        public static string HashMD5(string input)
+        {
+            using (MD5 md5 = MD5.Create())
+            {
+                byte[] inputBytes = Encoding.UTF8.GetBytes(input);
+                byte[] hashBytes = md5.ComputeHash(inputBytes);
+                StringBuilder sb = new StringBuilder();
+                for (int i = 0; i < hashBytes.Length; i++)
+                    sb.Append(hashBytes[i].ToString("x2"));
+                return sb.ToString();
+            }
+        }
+
+        public static bool Login(string username, string password)
+        {
+            try
+            {
+                string passwordHash = HashMD5(password);
+                using (var conn = DBConnection.GetConnection())
+                {
+                    string sql = @"
+                        SELECT u.user_id, u.username, u.full_name, r.role_name 
+                        FROM users u
+                        JOIN roles r ON u.role_id = r.role_id
+                        WHERE u.username = @username AND u.password_hash = @password AND u.is_active = true";
+
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@username", username);
+                        cmd.Parameters.AddWithValue("@password", passwordHash);
+
+                        using (var reader = cmd.ExecuteReader())
+                        {
+                            if (reader.Read())
+                            {
+                                CurrentUser.Id = reader.GetInt32(0);
+                                CurrentUser.Username = reader.GetString(1);
+                                CurrentUser.FullName = reader.IsDBNull(2) ? username : reader.GetString(2);
+                                CurrentUser.Role = reader.GetString(3);
+                                CurrentUser.IsAuthenticated = true;
+
+                                LogAction("Вход в систему", "users", CurrentUser.Id);
+                                return true;
+                            }
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка входа: {ex.Message}");
+            }
+            return false;
+        }
+
+        public static void Logout()
+        {
+            if (CurrentUser.IsAuthenticated)
+            {
+                LogAction("Выход из системы", "users", CurrentUser.Id);
+                CurrentUser.IsAuthenticated = false;
+                CurrentUser.Id = 0;
+                CurrentUser.Username = "";
+                CurrentUser.FullName = "";
+                CurrentUser.Role = "";
+            }
+        }
+
+        public static void LogAction(string action, string tableName, int recordId = 0)
+        {
+            if (!CurrentUser.IsAuthenticated) return;
+
+            try
+            {
+                using (var conn = DBConnection.GetConnection())
+                {
+                    string sql = @"INSERT INTO user_logs (user_id, action, table_name, record_id) 
+                                  VALUES (@user_id, @action, @table_name, @record_id)";
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@user_id", CurrentUser.Id);
+                        cmd.Parameters.AddWithValue("@action", action);
+                        cmd.Parameters.AddWithValue("@table_name", tableName);
+                        cmd.Parameters.AddWithValue("@record_id", recordId);
+                        cmd.ExecuteNonQuery();
+                    }
+                }
+            }
+            catch { }
+        }
+
+        public static bool CheckPermission(string requiredRole)
+        {
+            if (!CurrentUser.IsAuthenticated)
+            {
+                MessageBox.Show("Требуется авторизация!", "Доступ запрещён",
+                    MessageBoxButtons.OK, MessageBoxIcon.Warning);
+                return false;
+            }
+
+            if (requiredRole == "admin" && !CurrentUser.IsAdmin)
+            {
+                MessageBox.Show("Доступ только для администратора!", "Доступ запрещён",
+                    MessageBoxButtons.OK, MessageBoxIcon.Warning);
+                return false;
+            }
+
+            if (requiredRole == "manager" && !CurrentUser.IsManager)
+            {
+                MessageBox.Show("Доступ только для менеджеров!", "Доступ запрещён",
+                    MessageBoxButtons.OK, MessageBoxIcon.Warning);
+                return false;
+            }
+
+            return true;
+        }
+
+        public static string MaskSensitiveData(string data)
+        {
+            if (string.IsNullOrEmpty(data) || data.Length < 3)
+                return "***";
+
+            // Для администратора и менеджера показываем всё
+            if (CurrentUser.IsAdmin || CurrentUser.IsManager)
+                return data;
+
+            // Маскирование паспорта (серия и номер)
+            if (data.Length == 10 && (data.Contains(" ") || (char.IsDigit(data[0]) && char.IsDigit(data[1]))))
+            {
+                string cleanData = data.Replace(" ", "");
+                if (cleanData.Length == 10)
+                {
+                    return "**** " + cleanData.Substring(cleanData.Length - 4);
+                }
+                return "**** ****";
+            }
+
+            // Маскирование ИНН (10 или 12 цифр)
+            if (data.Length == 10 || data.Length == 12)
+            {
+                bool isAllDigits = true;
+                foreach (char c in data)
+                {
+                    if (!char.IsDigit(c))
+                    {
+                        isAllDigits = false;
+                        break;
+                    }
+                }
+                if (isAllDigits)
+                {
+                    return "***" + data.Substring(data.Length - 3);
+                }
+            }
+
+            // Маскирование СНИЛС (11 цифр, формат XXX-XXX-XXX XX)
+            string cleanSnils = data.Replace("-", "").Replace(" ", "");
+            if (cleanSnils.Length == 11)
+            {
+                bool isAllDigits = true;
+                foreach (char c in cleanSnils)
+                {
+                    if (!char.IsDigit(c))
+                    {
+                        isAllDigits = false;
+                        break;
+                    }
+                }
+                if (isAllDigits)
+                {
+                    return "***-***-" + cleanSnils.Substring(cleanSnils.Length - 3);
+                }
+            }
+
+            // Маскирование телефона (11 цифр)
+            string cleanPhone = data.Replace("+", "").Replace("-", "").Replace("(", "").Replace(")", "").Replace(" ", "");
+            if (cleanPhone.Length == 11)
+            {
+                bool isAllDigits = true;
+                foreach (char c in cleanPhone)
+                {
+                    if (!char.IsDigit(c))
+                    {
+                        isAllDigits = false;
+                        break;
+                    }
+                }
+                if (isAllDigits)
+                {
+                    return cleanPhone.Substring(0, 2) + "*****" + cleanPhone.Substring(7);
+                }
+            }
+
+            // Маскирование email
+            if (data.Contains("@"))
+            {
+                int atIndex = data.IndexOf('@');
+                if (atIndex > 0)
+                {
+                    string localPart = data.Substring(0, atIndex);
+                    string domain = data.Substring(atIndex);
+                    if (localPart.Length <= 2)
+                    {
+                        return "***" + domain;
+                    }
+                    return localPart.Substring(0, 2) + "***" + domain;
+                }
+            }
+
+            // Маскирование адреса
+            if (data.Length > 6)
+            {
+                return data.Substring(0, 3) + "***" + data.Substring(data.Length - 3);
+            }
+
+            // Общее маскирование
+            if (data.Length <= 4)
+            {
+                return "***";
+            }
+
+            return data.Substring(0, 2) + "***" + data.Substring(data.Length - 2);
+        }
+
+        public static string MaskPassport(string series, string number)
+        {
+            if (string.IsNullOrEmpty(series) || string.IsNullOrEmpty(number))
+                return "***";
+
+            if (CurrentUser.IsAdmin || CurrentUser.IsManager)
+                return $"{series} {number}";
+
+            return $"**** {number.Substring(Math.Max(0, number.Length - 4))}";
+        }
+
+        public static string GetMaskedPassportFromDb(int clientId)
+        {
+            try
+            {
+                using (var conn = DBConnection.GetConnection())
+                {
+                    string sql = "SELECT passport_series, passport_number FROM clients WHERE client_id = @id";
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@id", clientId);
+                        using (var reader = cmd.ExecuteReader())
+                        {
+                            if (reader.Read())
+                            {
+                                string series = reader["passport_series"]?.ToString();
+                                string number = reader["passport_number"]?.ToString();
+                                return MaskPassport(series, number);
+                            }
+                        }
+                    }
+                }
+            }
+            catch { }
+            return "***";
+        }
+    }
+}

+ 203 - 0
ProviderApp/ClientsForm.Designer.cs

@@ -0,0 +1,203 @@
+using System.Drawing;
+using System.Windows.Forms;
+using Guna.UI2.WinForms;
+
+namespace ProviderApp
+{
+    partial class ClientsForm
+    {
+        private System.ComponentModel.IContainer components = null;
+        private Guna2Panel panel;
+        private Guna2HtmlLabel lblTitle;
+        private Guna2Button btnAdd;
+        private Guna2Button btnEdit;
+        private Guna2Button btnDelete;
+        private Guna2Button btnExport;
+        private Guna2DataGridView dgvClients;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null)) components.Dispose();
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges9 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges10 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges1 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges2 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges3 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges4 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges5 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges6 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges7 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges8 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            DataGridViewCellStyle dataGridViewCellStyle1 = new DataGridViewCellStyle();
+            DataGridViewCellStyle dataGridViewCellStyle2 = new DataGridViewCellStyle();
+            DataGridViewCellStyle dataGridViewCellStyle3 = new DataGridViewCellStyle();
+            panel = new Guna2Panel();
+            lblTitle = new Guna2HtmlLabel();
+            btnAdd = new Guna2Button();
+            btnEdit = new Guna2Button();
+            btnDelete = new Guna2Button();
+            btnExport = new Guna2Button();
+            dgvClients = new Guna2DataGridView();
+            panel.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)dgvClients).BeginInit();
+            SuspendLayout();
+            // 
+            // panel
+            // 
+            panel.BackColor = Color.FromArgb(30, 30, 30);
+            panel.Controls.Add(lblTitle);
+            panel.Controls.Add(btnAdd);
+            panel.Controls.Add(btnEdit);
+            panel.Controls.Add(btnDelete);
+            panel.Controls.Add(btnExport);
+            panel.Controls.Add(dgvClients);
+            panel.CustomizableEdges = customizableEdges9;
+            panel.Dock = DockStyle.Fill;
+            panel.Location = new Point(0, 0);
+            panel.Name = "panel";
+            panel.Padding = new Padding(26, 28, 26, 28);
+            panel.ShadowDecoration.CustomizableEdges = customizableEdges10;
+            panel.Size = new Size(1006, 797);
+            panel.TabIndex = 0;
+            // 
+            // lblTitle
+            // 
+            lblTitle.BackColor = Color.Transparent;
+            lblTitle.Font = new Font("Segoe UI", 20F, FontStyle.Bold);
+            lblTitle.ForeColor = Color.White;
+            lblTitle.Location = new Point(26, 19);
+            lblTitle.Name = "lblTitle";
+            lblTitle.Size = new Size(317, 39);
+            lblTitle.TabIndex = 0;
+            lblTitle.Text = "Управление клиентами";
+            // 
+            // btnAdd
+            // 
+            btnAdd.BorderRadius = 8;
+            btnAdd.CustomizableEdges = customizableEdges1;
+            btnAdd.FillColor = Color.FromArgb(230, 92, 0);
+            btnAdd.Font = new Font("Segoe UI", 11F, FontStyle.Bold);
+            btnAdd.ForeColor = Color.White;
+            btnAdd.Location = new Point(26, 75);
+            btnAdd.Name = "btnAdd";
+            btnAdd.ShadowDecoration.CustomizableEdges = customizableEdges2;
+            btnAdd.Size = new Size(105, 42);
+            btnAdd.TabIndex = 1;
+            btnAdd.Text = "Добавить";
+            // 
+            // btnEdit
+            // 
+            btnEdit.BorderRadius = 8;
+            btnEdit.CustomizableEdges = customizableEdges3;
+            btnEdit.FillColor = Color.FromArgb(230, 92, 0);
+            btnEdit.Font = new Font("Segoe UI", 11F, FontStyle.Bold);
+            btnEdit.ForeColor = Color.White;
+            btnEdit.Location = new Point(144, 75);
+            btnEdit.Name = "btnEdit";
+            btnEdit.ShadowDecoration.CustomizableEdges = customizableEdges4;
+            btnEdit.Size = new Size(105, 42);
+            btnEdit.TabIndex = 2;
+            btnEdit.Text = "Изменить";
+            // 
+            // btnDelete
+            // 
+            btnDelete.BorderRadius = 8;
+            btnDelete.CustomizableEdges = customizableEdges5;
+            btnDelete.FillColor = Color.FromArgb(230, 92, 0);
+            btnDelete.Font = new Font("Segoe UI", 11F, FontStyle.Bold);
+            btnDelete.ForeColor = Color.White;
+            btnDelete.Location = new Point(262, 75);
+            btnDelete.Name = "btnDelete";
+            btnDelete.ShadowDecoration.CustomizableEdges = customizableEdges6;
+            btnDelete.Size = new Size(105, 42);
+            btnDelete.TabIndex = 3;
+            btnDelete.Text = "Удалить";
+            // 
+            // btnExport
+            // 
+            btnExport.BorderRadius = 8;
+            btnExport.CustomizableEdges = customizableEdges7;
+            btnExport.FillColor = Color.FromArgb(0, 120, 215);
+            btnExport.Font = new Font("Segoe UI", 11F, FontStyle.Bold);
+            btnExport.ForeColor = Color.White;
+            btnExport.Location = new Point(381, 75);
+            btnExport.Name = "btnExport";
+            btnExport.ShadowDecoration.CustomizableEdges = customizableEdges8;
+            btnExport.Size = new Size(149, 42);
+            btnExport.TabIndex = 4;
+            btnExport.Text = "Экспорт Excel";
+            // 
+            // dgvClients
+            // 
+            dgvClients.AllowUserToAddRows = false;
+            dgvClients.AllowUserToDeleteRows = false;
+            dataGridViewCellStyle1.BackColor = Color.White;
+            dgvClients.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle1;
+            dgvClients.BackgroundColor = Color.FromArgb(40, 40, 40);
+            dataGridViewCellStyle2.Alignment = DataGridViewContentAlignment.MiddleLeft;
+            dataGridViewCellStyle2.BackColor = Color.FromArgb(100, 88, 255);
+            dataGridViewCellStyle2.Font = new Font("Segoe UI", 9F);
+            dataGridViewCellStyle2.ForeColor = Color.White;
+            dataGridViewCellStyle2.SelectionBackColor = SystemColors.Highlight;
+            dataGridViewCellStyle2.SelectionForeColor = SystemColors.HighlightText;
+            dataGridViewCellStyle2.WrapMode = DataGridViewTriState.True;
+            dgvClients.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle2;
+            dgvClients.ColumnHeadersHeight = 50;
+            dataGridViewCellStyle3.Alignment = DataGridViewContentAlignment.MiddleLeft;
+            dataGridViewCellStyle3.BackColor = Color.White;
+            dataGridViewCellStyle3.Font = new Font("Segoe UI", 9F);
+            dataGridViewCellStyle3.ForeColor = Color.FromArgb(71, 69, 94);
+            dataGridViewCellStyle3.SelectionBackColor = Color.FromArgb(231, 229, 255);
+            dataGridViewCellStyle3.SelectionForeColor = Color.FromArgb(71, 69, 94);
+            dataGridViewCellStyle3.WrapMode = DataGridViewTriState.False;
+            dgvClients.DefaultCellStyle = dataGridViewCellStyle3;
+            dgvClients.GridColor = Color.FromArgb(60, 60, 60);
+            dgvClients.Location = new Point(26, 141);
+            dgvClients.Name = "dgvClients";
+            dgvClients.ReadOnly = true;
+            dgvClients.RowHeadersVisible = false;
+            dgvClients.RowTemplate.Height = 40;
+            dgvClients.Size = new Size(954, 619);
+            dgvClients.TabIndex = 5;
+            dgvClients.ThemeStyle.AlternatingRowsStyle.BackColor = Color.White;
+            dgvClients.ThemeStyle.AlternatingRowsStyle.Font = null;
+            dgvClients.ThemeStyle.AlternatingRowsStyle.ForeColor = Color.Empty;
+            dgvClients.ThemeStyle.AlternatingRowsStyle.SelectionBackColor = Color.Empty;
+            dgvClients.ThemeStyle.AlternatingRowsStyle.SelectionForeColor = Color.Empty;
+            dgvClients.ThemeStyle.BackColor = Color.FromArgb(40, 40, 40);
+            dgvClients.ThemeStyle.GridColor = Color.FromArgb(60, 60, 60);
+            dgvClients.ThemeStyle.HeaderStyle.BackColor = Color.FromArgb(100, 88, 255);
+            dgvClients.ThemeStyle.HeaderStyle.BorderStyle = DataGridViewHeaderBorderStyle.None;
+            dgvClients.ThemeStyle.HeaderStyle.Font = new Font("Segoe UI", 9F);
+            dgvClients.ThemeStyle.HeaderStyle.ForeColor = Color.White;
+            dgvClients.ThemeStyle.HeaderStyle.HeaightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
+            dgvClients.ThemeStyle.HeaderStyle.Height = 50;
+            dgvClients.ThemeStyle.ReadOnly = true;
+            dgvClients.ThemeStyle.RowsStyle.BackColor = Color.White;
+            dgvClients.ThemeStyle.RowsStyle.BorderStyle = DataGridViewCellBorderStyle.SingleHorizontal;
+            dgvClients.ThemeStyle.RowsStyle.Font = new Font("Segoe UI", 9F);
+            dgvClients.ThemeStyle.RowsStyle.ForeColor = Color.FromArgb(71, 69, 94);
+            dgvClients.ThemeStyle.RowsStyle.Height = 40;
+            dgvClients.ThemeStyle.RowsStyle.SelectionBackColor = Color.FromArgb(231, 229, 255);
+            dgvClients.ThemeStyle.RowsStyle.SelectionForeColor = Color.FromArgb(71, 69, 94);
+            // 
+            // ClientsForm
+            // 
+            AutoScaleDimensions = new SizeF(7F, 15F);
+            AutoScaleMode = AutoScaleMode.Font;
+            BackColor = Color.FromArgb(30, 30, 30);
+            Controls.Add(panel);
+            Name = "ClientsForm";
+            Size = new Size(1006, 797);
+            panel.ResumeLayout(false);
+            panel.PerformLayout();
+            ((System.ComponentModel.ISupportInitialize)dgvClients).EndInit();
+            ResumeLayout(false);
+        }
+    }
+}

+ 204 - 0
ProviderApp/ClientsForm.cs

@@ -0,0 +1,204 @@
+using System;
+using System.Data;
+using System.Windows.Forms;
+using Npgsql;
+
+namespace ProviderApp
+{
+    public partial class ClientsForm : UserControl
+    {
+        private DataTable clientsTable;
+
+        public ClientsForm()
+        {
+            InitializeComponent();
+            this.Load += ClientsForm_Load;
+            btnAdd.Click += BtnAdd_Click;
+            btnEdit.Click += BtnEdit_Click;
+            btnDelete.Click += BtnDelete_Click;
+            btnExport.Click += BtnExport_Click;
+            StyleDataGridView();
+        }
+
+        private void StyleDataGridView()
+        {
+            dgvClients.ColumnHeadersDefaultCellStyle.BackColor = System.Drawing.Color.FromArgb(230, 92, 0);
+            dgvClients.ColumnHeadersDefaultCellStyle.ForeColor = System.Drawing.Color.White;
+            dgvClients.ColumnHeadersDefaultCellStyle.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Bold);
+            dgvClients.DefaultCellStyle.BackColor = System.Drawing.Color.FromArgb(40, 40, 40);
+            dgvClients.DefaultCellStyle.ForeColor = System.Drawing.Color.White;
+            dgvClients.AlternatingRowsDefaultCellStyle.BackColor = System.Drawing.Color.FromArgb(45, 45, 45);
+            dgvClients.EnableHeadersVisualStyles = false;
+        }
+        public void RefreshData()
+        {
+            LoadClients();
+        }
+        private void ClientsForm_Load(object sender, EventArgs e)
+        {
+            LoadClients();
+            btnAdd.Visible = AuthHelper.CurrentUser.IsManager;
+            btnEdit.Visible = AuthHelper.CurrentUser.IsManager;
+            btnDelete.Visible = AuthHelper.CurrentUser.IsAdmin;
+            btnExport.Visible = AuthHelper.CurrentUser.IsManager;
+        }
+
+        private void LoadClients()
+        {
+            try
+            {
+                using (var conn = DBConnection.GetConnection())
+                {
+                    string sql;
+
+                    if (!AuthHelper.CurrentUser.IsManager)
+                    {
+                        sql = "SELECT client_id, last_name, first_name, middle_name, phone, address FROM sizov_ev.clients_masked ORDER BY client_id";
+                    }
+                    else
+                    {
+                        sql = "SELECT client_id, last_name, first_name, middle_name, phone, address FROM sizov_ev.clients ORDER BY client_id";
+                    }
+
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        DataTable dt = new DataTable();
+                        using (var da = new NpgsqlDataAdapter(cmd))
+                        {
+                            da.Fill(dt);
+                        }
+
+                        clientsTable = dt;
+                        dgvClients.DataSource = clientsTable;
+
+                        if (dgvClients.Columns["client_id"] != null)
+                            dgvClients.Columns["client_id"].HeaderText = "ID";
+                        if (dgvClients.Columns["last_name"] != null)
+                            dgvClients.Columns["last_name"].HeaderText = "Фамилия";
+                        if (dgvClients.Columns["first_name"] != null)
+                            dgvClients.Columns["first_name"].HeaderText = "Имя";
+                        if (dgvClients.Columns["middle_name"] != null)
+                            dgvClients.Columns["middle_name"].HeaderText = "Отчество";
+                        if (dgvClients.Columns["phone"] != null)
+                            dgvClients.Columns["phone"].HeaderText = "Телефон";
+                        if (dgvClients.Columns["address"] != null)
+                            dgvClients.Columns["address"].HeaderText = "Адрес";
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки клиентов: {ex.Message}");
+            }
+        }
+
+        private void BtnAdd_Click(object sender, EventArgs e)
+        {
+            if (!AuthHelper.CheckPermission("manager")) return;
+            AuthHelper.LogAction("Попытка добавления клиента", "clients");
+
+            using (var form = new AddEditClientForm())
+            {
+                if (form.ShowDialog() == DialogResult.OK)
+                {
+                    AuthHelper.LogAction("Клиент добавлен", "clients");
+                    LoadClients();
+                }
+            }
+        }
+
+        private void BtnEdit_Click(object sender, EventArgs e)
+        {
+            if (!AuthHelper.CheckPermission("manager")) return;
+
+            if (dgvClients.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите клиента.");
+                return;
+            }
+
+            int clientId = Convert.ToInt32(dgvClients.CurrentRow.Cells["client_id"].Value);
+            AuthHelper.LogAction($"Попытка редактирования клиента ID={clientId}", "clients", clientId);
+
+            using (var form = new AddEditClientForm(clientId))
+            {
+                if (form.ShowDialog() == DialogResult.OK)
+                {
+                    AuthHelper.LogAction($"Клиент ID={clientId} отредактирован", "clients", clientId);
+                    LoadClients();
+                }
+            }
+        }
+
+        private void BtnDelete_Click(object sender, EventArgs e)
+        {
+            if (!AuthHelper.CheckPermission("admin")) return;
+
+            if (dgvClients.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите клиента.");
+                return;
+            }
+
+            if (MessageBox.Show("Удалить клиента?", "Подтверждение", MessageBoxButtons.YesNo) == DialogResult.Yes)
+            {
+                int clientId = Convert.ToInt32(dgvClients.CurrentRow.Cells["client_id"].Value);
+
+                try
+                {
+                    using (var conn = DBConnection.GetConnection())
+                    {
+                        string sql = "DELETE FROM sizov_ev.clients WHERE client_id = @id";
+                        using (var cmd = new NpgsqlCommand(sql, conn))
+                        {
+                            cmd.Parameters.AddWithValue("@id", clientId);
+                            cmd.ExecuteNonQuery();
+                        }
+                    }
+                    AuthHelper.LogAction($"Клиент ID={clientId} удалён", "clients", clientId);
+                    LoadClients();
+                    MessageBox.Show("Клиент удалён.");
+                }
+                catch (Exception ex)
+                {
+                    MessageBox.Show($"Ошибка удаления: {ex.Message}");
+                }
+            }
+        }
+
+        private void BtnExport_Click(object sender, EventArgs e)
+        {
+            if (!AuthHelper.CheckPermission("manager")) return;
+
+            try
+            {
+                using (var conn = DBConnection.GetConnection())
+                {
+                    string sql = @"SELECT 
+                        client_id as ""ID"", 
+                        last_name as ""Фамилия"", 
+                        first_name as ""Имя"", 
+                        middle_name as ""Отчество"", 
+                        phone as ""Телефон"", 
+                        address as ""Адрес""
+                        FROM sizov_ev.clients ORDER BY client_id";
+
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        DataTable dt = new DataTable();
+                        using (var da = new NpgsqlDataAdapter(cmd))
+                        {
+                            da.Fill(dt);
+                        }
+                        AuthHelper.LogAction("Экспорт клиентов в CSV", "clients");
+                        CsvExporter.ExportToCsv(dt, $"Клиенты_{DateTime.Now:yyyyMMdd_HHmmss}.csv");
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка экспорта: {ex.Message}");
+            }
+        }
+    }
+}

+ 120 - 0
ProviderApp/ClientsForm.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>

+ 296 - 0
ProviderApp/ContractsForm.Designer.cs

@@ -0,0 +1,296 @@
+using System.Drawing;
+using System.Windows.Forms;
+using Guna.UI2.WinForms;
+
+namespace ProviderApp
+{
+    partial class ContractsForm
+    {
+        private System.ComponentModel.IContainer components = null;
+        private Guna2Panel panel;
+        private Guna2HtmlLabel lblTitle;
+        private Guna2Button btnAdd;
+        private Guna2Button btnEdit;
+        private Guna2Button btnDelete;
+        private Guna2Button btnExecute;
+        private Guna2Button btnExport;
+        private Guna2DataGridView dgvResult;
+        private Guna2DateTimePicker dtpFrom;
+        private Guna2DateTimePicker dtpTo;
+        private Guna2HtmlLabel lblFrom;
+        private Guna2HtmlLabel lblTo;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null)) components.Dispose();
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges15 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges16 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges1 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges2 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges3 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges4 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges5 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges6 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges7 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges8 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges9 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges10 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges11 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges12 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges13 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges14 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            DataGridViewCellStyle dataGridViewCellStyle1 = new DataGridViewCellStyle();
+            DataGridViewCellStyle dataGridViewCellStyle2 = new DataGridViewCellStyle();
+            DataGridViewCellStyle dataGridViewCellStyle3 = new DataGridViewCellStyle();
+            panel = new Guna2Panel();
+            lblTitle = new Guna2HtmlLabel();
+            lblFrom = new Guna2HtmlLabel();
+            dtpFrom = new Guna2DateTimePicker();
+            lblTo = new Guna2HtmlLabel();
+            dtpTo = new Guna2DateTimePicker();
+            btnExecute = new Guna2Button();
+            btnAdd = new Guna2Button();
+            btnEdit = new Guna2Button();
+            btnDelete = new Guna2Button();
+            btnExport = new Guna2Button();
+            dgvResult = new Guna2DataGridView();
+            panel.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)dgvResult).BeginInit();
+            SuspendLayout();
+            // 
+            // panel
+            // 
+            panel.BackColor = Color.FromArgb(30, 30, 30);
+            panel.Controls.Add(lblTitle);
+            panel.Controls.Add(lblFrom);
+            panel.Controls.Add(dtpFrom);
+            panel.Controls.Add(lblTo);
+            panel.Controls.Add(dtpTo);
+            panel.Controls.Add(btnExecute);
+            panel.Controls.Add(btnAdd);
+            panel.Controls.Add(btnEdit);
+            panel.Controls.Add(btnDelete);
+            panel.Controls.Add(btnExport);
+            panel.Controls.Add(dgvResult);
+            panel.CustomizableEdges = customizableEdges15;
+            panel.Dock = DockStyle.Fill;
+            panel.Location = new Point(0, 0);
+            panel.Name = "panel";
+            panel.Padding = new Padding(26, 28, 26, 28);
+            panel.ShadowDecoration.CustomizableEdges = customizableEdges16;
+            panel.Size = new Size(1006, 797);
+            panel.TabIndex = 0;
+            // 
+            // lblTitle
+            // 
+            lblTitle.BackColor = Color.Transparent;
+            lblTitle.Font = new Font("Segoe UI", 20F, FontStyle.Bold);
+            lblTitle.ForeColor = Color.White;
+            lblTitle.Location = new Point(26, 19);
+            lblTitle.Name = "lblTitle";
+            lblTitle.Size = new Size(337, 39);
+            lblTitle.TabIndex = 0;
+            lblTitle.Text = "Управление договорами";
+            // 
+            // lblFrom
+            // 
+            lblFrom.BackColor = Color.Transparent;
+            lblFrom.Font = new Font("Segoe UI", 10F);
+            lblFrom.ForeColor = Color.White;
+            lblFrom.Location = new Point(26, 84);
+            lblFrom.Name = "lblFrom";
+            lblFrom.Size = new Size(46, 19);
+            lblFrom.TabIndex = 1;
+            lblFrom.Text = "С даты:";
+            // 
+            // dtpFrom
+            // 
+            dtpFrom.BorderRadius = 6;
+            dtpFrom.Checked = true;
+            dtpFrom.CustomizableEdges = customizableEdges1;
+            dtpFrom.FillColor = Color.FromArgb(45, 45, 45);
+            dtpFrom.Font = new Font("Segoe UI", 10F);
+            dtpFrom.ForeColor = Color.White;
+            dtpFrom.Format = DateTimePickerFormat.Short;
+            dtpFrom.Location = new Point(79, 80);
+            dtpFrom.MaxDate = new DateTime(9998, 12, 31, 0, 0, 0, 0);
+            dtpFrom.MinDate = new DateTime(1753, 1, 1, 0, 0, 0, 0);
+            dtpFrom.Name = "dtpFrom";
+            dtpFrom.ShadowDecoration.CustomizableEdges = customizableEdges2;
+            dtpFrom.Size = new Size(135, 28);
+            dtpFrom.TabIndex = 2;
+            dtpFrom.Value = new DateTime(2026, 5, 25, 16, 48, 30, 278);
+            // 
+            // lblTo
+            // 
+            lblTo.BackColor = Color.Transparent;
+            lblTo.Font = new Font("Segoe UI", 10F);
+            lblTo.ForeColor = Color.White;
+            lblTo.Location = new Point(220, 84);
+            lblTo.Name = "lblTo";
+            lblTo.Size = new Size(23, 19);
+            lblTo.TabIndex = 3;
+            lblTo.Text = "По:";
+            // 
+            // dtpTo
+            // 
+            dtpTo.BorderRadius = 6;
+            dtpTo.Checked = true;
+            dtpTo.CustomizableEdges = customizableEdges3;
+            dtpTo.FillColor = Color.FromArgb(45, 45, 45);
+            dtpTo.Font = new Font("Segoe UI", 10F);
+            dtpTo.ForeColor = Color.White;
+            dtpTo.Format = DateTimePickerFormat.Short;
+            dtpTo.Location = new Point(249, 80);
+            dtpTo.MaxDate = new DateTime(9998, 12, 31, 0, 0, 0, 0);
+            dtpTo.MinDate = new DateTime(1753, 1, 1, 0, 0, 0, 0);
+            dtpTo.Name = "dtpTo";
+            dtpTo.ShadowDecoration.CustomizableEdges = customizableEdges4;
+            dtpTo.Size = new Size(131, 28);
+            dtpTo.TabIndex = 4;
+            dtpTo.Value = new DateTime(2026, 5, 25, 16, 48, 30, 316);
+            // 
+            // btnExecute
+            // 
+            btnExecute.BorderRadius = 8;
+            btnExecute.CustomizableEdges = customizableEdges5;
+            btnExecute.FillColor = Color.Green;
+            btnExecute.Font = new Font("Segoe UI", 10F, FontStyle.Bold);
+            btnExecute.ForeColor = Color.White;
+            btnExecute.Location = new Point(395, 70);
+            btnExecute.Name = "btnExecute";
+            btnExecute.ShadowDecoration.CustomizableEdges = customizableEdges6;
+            btnExecute.Size = new Size(117, 38);
+            btnExecute.TabIndex = 5;
+            btnExecute.Text = "Показать";
+            // 
+            // btnAdd
+            // 
+            btnAdd.BorderRadius = 8;
+            btnAdd.CustomizableEdges = customizableEdges7;
+            btnAdd.FillColor = Color.FromArgb(230, 92, 0);
+            btnAdd.Font = new Font("Segoe UI", 11F, FontStyle.Bold);
+            btnAdd.ForeColor = Color.White;
+            btnAdd.Location = new Point(26, 131);
+            btnAdd.Name = "btnAdd";
+            btnAdd.ShadowDecoration.CustomizableEdges = customizableEdges8;
+            btnAdd.Size = new Size(105, 42);
+            btnAdd.TabIndex = 6;
+            btnAdd.Text = "Добавить";
+            // 
+            // btnEdit
+            // 
+            btnEdit.BorderRadius = 8;
+            btnEdit.CustomizableEdges = customizableEdges9;
+            btnEdit.FillColor = Color.FromArgb(230, 92, 0);
+            btnEdit.Font = new Font("Segoe UI", 11F, FontStyle.Bold);
+            btnEdit.ForeColor = Color.White;
+            btnEdit.Location = new Point(144, 131);
+            btnEdit.Name = "btnEdit";
+            btnEdit.ShadowDecoration.CustomizableEdges = customizableEdges10;
+            btnEdit.Size = new Size(105, 42);
+            btnEdit.TabIndex = 7;
+            btnEdit.Text = "Изменить";
+            // 
+            // btnDelete
+            // 
+            btnDelete.BorderRadius = 8;
+            btnDelete.CustomizableEdges = customizableEdges11;
+            btnDelete.FillColor = Color.FromArgb(230, 92, 0);
+            btnDelete.Font = new Font("Segoe UI", 11F, FontStyle.Bold);
+            btnDelete.ForeColor = Color.White;
+            btnDelete.Location = new Point(262, 131);
+            btnDelete.Name = "btnDelete";
+            btnDelete.ShadowDecoration.CustomizableEdges = customizableEdges12;
+            btnDelete.Size = new Size(105, 42);
+            btnDelete.TabIndex = 8;
+            btnDelete.Text = "Удалить";
+            // 
+            // btnExport
+            // 
+            btnExport.BorderRadius = 8;
+            btnExport.CustomizableEdges = customizableEdges13;
+            btnExport.FillColor = Color.FromArgb(0, 120, 215);
+            btnExport.Font = new Font("Segoe UI", 11F, FontStyle.Bold);
+            btnExport.ForeColor = Color.White;
+            btnExport.Location = new Point(381, 131);
+            btnExport.Name = "btnExport";
+            btnExport.ShadowDecoration.CustomizableEdges = customizableEdges14;
+            btnExport.Size = new Size(143, 42);
+            btnExport.TabIndex = 9;
+            btnExport.Text = "Экспорт Excel";
+            // 
+            // dgvResult
+            // 
+            dgvResult.AllowUserToAddRows = false;
+            dgvResult.AllowUserToDeleteRows = false;
+            dataGridViewCellStyle1.BackColor = Color.White;
+            dgvResult.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle1;
+            dgvResult.BackgroundColor = Color.FromArgb(40, 40, 40);
+            dataGridViewCellStyle2.Alignment = DataGridViewContentAlignment.MiddleLeft;
+            dataGridViewCellStyle2.BackColor = Color.FromArgb(100, 88, 255);
+            dataGridViewCellStyle2.Font = new Font("Segoe UI", 9F);
+            dataGridViewCellStyle2.ForeColor = Color.White;
+            dataGridViewCellStyle2.SelectionBackColor = SystemColors.Highlight;
+            dataGridViewCellStyle2.SelectionForeColor = SystemColors.HighlightText;
+            dataGridViewCellStyle2.WrapMode = DataGridViewTriState.True;
+            dgvResult.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle2;
+            dgvResult.ColumnHeadersHeight = 50;
+            dataGridViewCellStyle3.Alignment = DataGridViewContentAlignment.MiddleLeft;
+            dataGridViewCellStyle3.BackColor = Color.White;
+            dataGridViewCellStyle3.Font = new Font("Segoe UI", 9F);
+            dataGridViewCellStyle3.ForeColor = Color.FromArgb(71, 69, 94);
+            dataGridViewCellStyle3.SelectionBackColor = Color.FromArgb(231, 229, 255);
+            dataGridViewCellStyle3.SelectionForeColor = Color.FromArgb(71, 69, 94);
+            dataGridViewCellStyle3.WrapMode = DataGridViewTriState.False;
+            dgvResult.DefaultCellStyle = dataGridViewCellStyle3;
+            dgvResult.GridColor = Color.FromArgb(60, 60, 60);
+            dgvResult.Location = new Point(26, 197);
+            dgvResult.Name = "dgvResult";
+            dgvResult.ReadOnly = true;
+            dgvResult.RowHeadersVisible = false;
+            dgvResult.RowTemplate.Height = 40;
+            dgvResult.Size = new Size(954, 562);
+            dgvResult.TabIndex = 10;
+            dgvResult.ThemeStyle.AlternatingRowsStyle.BackColor = Color.White;
+            dgvResult.ThemeStyle.AlternatingRowsStyle.Font = null;
+            dgvResult.ThemeStyle.AlternatingRowsStyle.ForeColor = Color.Empty;
+            dgvResult.ThemeStyle.AlternatingRowsStyle.SelectionBackColor = Color.Empty;
+            dgvResult.ThemeStyle.AlternatingRowsStyle.SelectionForeColor = Color.Empty;
+            dgvResult.ThemeStyle.BackColor = Color.FromArgb(40, 40, 40);
+            dgvResult.ThemeStyle.GridColor = Color.FromArgb(60, 60, 60);
+            dgvResult.ThemeStyle.HeaderStyle.BackColor = Color.FromArgb(100, 88, 255);
+            dgvResult.ThemeStyle.HeaderStyle.BorderStyle = DataGridViewHeaderBorderStyle.None;
+            dgvResult.ThemeStyle.HeaderStyle.Font = new Font("Segoe UI", 9F);
+            dgvResult.ThemeStyle.HeaderStyle.ForeColor = Color.White;
+            dgvResult.ThemeStyle.HeaderStyle.HeaightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
+            dgvResult.ThemeStyle.HeaderStyle.Height = 50;
+            dgvResult.ThemeStyle.ReadOnly = true;
+            dgvResult.ThemeStyle.RowsStyle.BackColor = Color.White;
+            dgvResult.ThemeStyle.RowsStyle.BorderStyle = DataGridViewCellBorderStyle.SingleHorizontal;
+            dgvResult.ThemeStyle.RowsStyle.Font = new Font("Segoe UI", 9F);
+            dgvResult.ThemeStyle.RowsStyle.ForeColor = Color.FromArgb(71, 69, 94);
+            dgvResult.ThemeStyle.RowsStyle.Height = 40;
+            dgvResult.ThemeStyle.RowsStyle.SelectionBackColor = Color.FromArgb(231, 229, 255);
+            dgvResult.ThemeStyle.RowsStyle.SelectionForeColor = Color.FromArgb(71, 69, 94);
+            // 
+            // ContractsForm
+            // 
+            AutoScaleDimensions = new SizeF(7F, 15F);
+            AutoScaleMode = AutoScaleMode.Font;
+            BackColor = Color.FromArgb(30, 30, 30);
+            Controls.Add(panel);
+            Name = "ContractsForm";
+            Size = new Size(1006, 797);
+            panel.ResumeLayout(false);
+            panel.PerformLayout();
+            ((System.ComponentModel.ISupportInitialize)dgvResult).EndInit();
+            ResumeLayout(false);
+        }
+    }
+}

+ 240 - 0
ProviderApp/ContractsForm.cs

@@ -0,0 +1,240 @@
+using System;
+using System.Data;
+using System.Windows.Forms;
+using Npgsql;
+
+namespace ProviderApp
+{
+    public partial class ContractsForm : UserControl
+    {
+        private DataTable contractsTable;
+
+        public ContractsForm()
+        {
+            InitializeComponent();
+            this.Load += ContractsForm_Load;
+            btnExecute.Click += BtnExecute_Click;
+            btnAdd.Click += BtnAdd_Click;
+            btnEdit.Click += BtnEdit_Click;
+            btnDelete.Click += BtnDelete_Click;
+            btnExport.Click += BtnExport_Click;
+            StyleDataGridView();
+        }
+
+        private void StyleDataGridView()
+        {
+            dgvResult.ColumnHeadersDefaultCellStyle.BackColor = System.Drawing.Color.FromArgb(230, 92, 0);
+            dgvResult.ColumnHeadersDefaultCellStyle.ForeColor = System.Drawing.Color.White;
+            dgvResult.ColumnHeadersDefaultCellStyle.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Bold);
+            dgvResult.DefaultCellStyle.BackColor = System.Drawing.Color.FromArgb(40, 40, 40);
+            dgvResult.DefaultCellStyle.ForeColor = System.Drawing.Color.White;
+            dgvResult.AlternatingRowsDefaultCellStyle.BackColor = System.Drawing.Color.FromArgb(45, 45, 45);
+            dgvResult.EnableHeadersVisualStyles = false;
+        }
+
+        private void ContractsForm_Load(object sender, EventArgs e)
+        {
+            dtpFrom.Value = new DateTime(2024, 1, 1);
+            dtpTo.Value = new DateTime(2024, 12, 31);
+            LoadContracts();
+
+            btnAdd.Visible = AuthHelper.CurrentUser.IsManager;
+            btnEdit.Visible = AuthHelper.CurrentUser.IsManager;
+            btnDelete.Visible = AuthHelper.CurrentUser.IsAdmin;
+            btnExport.Visible = AuthHelper.CurrentUser.IsManager;
+        }
+
+        public void RefreshData()
+        {
+            LoadContracts();
+        }
+
+        private void BtnExecute_Click(object sender, EventArgs e)
+        {
+            LoadContracts();
+        }
+
+        private void LoadContracts()
+        {
+            try
+            {
+                using (var conn = DBConnection.GetConnection())
+                {
+                    DateTime fromDate = dtpFrom.Value.Date;
+                    DateTime toDate = dtpTo.Value.Date.AddDays(1).AddSeconds(-1);
+
+                    // РАСЧЁТНЫЙ АТРИБУТ
+                    string sql = @"
+                        SELECT 
+                            c.contract_id,
+                            c.client_name,
+                            c.tariff_name,
+                            c.monthly_price,
+                            c.employee_name,
+                            c.sign_date,
+                            c.status,
+                            COALESCE((
+                                SELECT SUM(p.amount) 
+                                FROM sizov_ev.payments p 
+                                WHERE p.contract_id = c.contract_id
+                            ), 0) AS total_paid
+                        FROM sizov_ev.active_contracts c
+                        WHERE c.sign_date BETWEEN @date_from AND @date_to
+                        ORDER BY c.sign_date DESC";
+
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@date_from", fromDate);
+                        cmd.Parameters.AddWithValue("@date_to", toDate);
+
+                        DataTable dt = new DataTable();
+                        using (var da = new NpgsqlDataAdapter(cmd))
+                        {
+                            da.Fill(dt);
+                        }
+                        contractsTable = dt;
+                        dgvResult.DataSource = contractsTable;
+
+                        if (dgvResult.Columns["contract_id"] != null)
+                            dgvResult.Columns["contract_id"].HeaderText = "ID";
+                        if (dgvResult.Columns["client_name"] != null)
+                            dgvResult.Columns["client_name"].HeaderText = "Клиент";
+                        if (dgvResult.Columns["tariff_name"] != null)
+                            dgvResult.Columns["tariff_name"].HeaderText = "Тариф";
+                        if (dgvResult.Columns["monthly_price"] != null)
+                            dgvResult.Columns["monthly_price"].HeaderText = "Цена, руб";
+                        if (dgvResult.Columns["employee_name"] != null)
+                            dgvResult.Columns["employee_name"].HeaderText = "Сотрудник";
+                        if (dgvResult.Columns["sign_date"] != null)
+                            dgvResult.Columns["sign_date"].HeaderText = "Дата";
+                        if (dgvResult.Columns["status"] != null)
+                            dgvResult.Columns["status"].HeaderText = "Статус";
+                        if (dgvResult.Columns["total_paid"] != null)
+                            dgvResult.Columns["total_paid"].HeaderText = "Оплачено, руб";
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки: {ex.Message}");
+            }
+        }
+
+        private void BtnAdd_Click(object sender, EventArgs e)
+        {
+            if (!AuthHelper.CheckPermission("manager")) return;
+            AuthHelper.LogAction("Попытка добавления договора", "contracts");
+
+            using (var form = new AddEditContractForm())
+            {
+                if (form.ShowDialog() == DialogResult.OK)
+                {
+                    AuthHelper.LogAction("Договор добавлен", "contracts");
+                    LoadContracts();
+                }
+            }
+        }
+
+        private void BtnEdit_Click(object sender, EventArgs e)
+        {
+            if (!AuthHelper.CheckPermission("manager")) return;
+
+            if (dgvResult.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите договор.");
+                return;
+            }
+            int contractId = Convert.ToInt32(dgvResult.CurrentRow.Cells["contract_id"].Value);
+            AuthHelper.LogAction($"Попытка редактирования договора ID={contractId}", "contracts", contractId);
+
+            using (var form = new AddEditContractForm(contractId))
+            {
+                if (form.ShowDialog() == DialogResult.OK)
+                {
+                    AuthHelper.LogAction($"Договор ID={contractId} отредактирован", "contracts", contractId);
+                    LoadContracts();
+                }
+            }
+        }
+
+        private void BtnDelete_Click(object sender, EventArgs e)
+        {
+            if (!AuthHelper.CheckPermission("admin")) return;
+
+            if (dgvResult.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите договор.");
+                return;
+            }
+            if (MessageBox.Show("Удалить договор?", "Подтверждение", MessageBoxButtons.YesNo) == DialogResult.Yes)
+            {
+                int contractId = Convert.ToInt32(dgvResult.CurrentRow.Cells["contract_id"].Value);
+                try
+                {
+                    using (var conn = DBConnection.GetConnection())
+                    {
+                        string sql = "DELETE FROM sizov_ev.contracts WHERE contract_id = @id";
+                        using (var cmd = new NpgsqlCommand(sql, conn))
+                        {
+                            cmd.Parameters.AddWithValue("@id", contractId);
+                            cmd.ExecuteNonQuery();
+                        }
+                    }
+                    AuthHelper.LogAction($"Договор ID={contractId} удалён", "contracts", contractId);
+                    LoadContracts();
+                    MessageBox.Show("Договор удалён.");
+                }
+                catch (Exception ex)
+                {
+                    MessageBox.Show($"Ошибка удаления: {ex.Message}");
+                }
+            }
+        }
+
+        private void BtnExport_Click(object sender, EventArgs e)
+        {
+            if (!AuthHelper.CheckPermission("manager")) return;
+
+            try
+            {
+                using (var conn = DBConnection.GetConnection())
+                {
+                    DateTime fromDate = dtpFrom.Value.Date;
+                    DateTime toDate = dtpTo.Value.Date.AddDays(1).AddSeconds(-1);
+
+                    string sql = @"
+                        SELECT 
+                            c.contract_id as ""ID договора"",
+                            c.client_name as ""Клиент"",
+                            c.tariff_name as ""Тариф"",
+                            c.monthly_price as ""Цена, руб"",
+                            c.employee_name as ""Сотрудник"",
+                            c.sign_date as ""Дата подписания"",
+                            c.status as ""Статус"",
+                            COALESCE((SELECT SUM(p.amount) FROM sizov_ev.payments p WHERE p.contract_id = c.contract_id), 0) as ""Оплачено, руб""
+                        FROM sizov_ev.active_contracts c
+                        WHERE c.sign_date BETWEEN @date_from AND @date_to
+                        ORDER BY c.sign_date DESC";
+
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@date_from", fromDate);
+                        cmd.Parameters.AddWithValue("@date_to", toDate);
+
+                        DataTable dt = new DataTable();
+                        using (var da = new NpgsqlDataAdapter(cmd))
+                        {
+                            da.Fill(dt);
+                        }
+                        AuthHelper.LogAction("Экспорт договоров в CSV", "contracts");
+                        CsvExporter.ExportToCsv(dt, $"Договоры_{DateTime.Now:yyyyMMdd_HHmmss}.csv");
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка экспорта: {ex.Message}");
+            }
+        }
+    }
+}

+ 120 - 0
ProviderApp/ContractsForm.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>

+ 57 - 0
ProviderApp/CsvExporter.cs

@@ -0,0 +1,57 @@
+using System;
+using System.Data;
+using System.Text;
+using System.Windows.Forms;
+using System.IO;
+
+namespace ProviderApp
+{
+    public static class CsvExporter
+    {
+        public static void ExportToCsv(DataTable dataTable, string fileName)
+        {
+            try
+            {
+                var sb = new StringBuilder();
+
+                // Заголовки
+                for (int i = 0; i < dataTable.Columns.Count; i++)
+                {
+                    sb.Append("\"" + dataTable.Columns[i].ColumnName + "\"");
+                    if (i < dataTable.Columns.Count - 1) sb.Append(";");
+                }
+                sb.AppendLine();
+
+                // Данные
+                foreach (DataRow row in dataTable.Rows)
+                {
+                    for (int i = 0; i < dataTable.Columns.Count; i++)
+                    {
+                        sb.Append("\"" + row[i].ToString().Replace("\"", "\"\"") + "\"");
+                        if (i < dataTable.Columns.Count - 1) sb.Append(";");
+                    }
+                    sb.AppendLine();
+                }
+
+                var saveFileDialog = new SaveFileDialog
+                {
+                    Filter = "CSV files (*.csv)|*.csv",
+                    FileName = fileName.Replace(".xlsx", ".csv"),
+                    DefaultExt = "csv"
+                };
+
+                if (saveFileDialog.ShowDialog() == DialogResult.OK)
+                {
+                    File.WriteAllText(saveFileDialog.FileName, sb.ToString(), Encoding.UTF8);
+                    MessageBox.Show($"Экспорт в CSV завершён!\nФайл сохранён: {saveFileDialog.FileName}", "Успех",
+                        MessageBoxButtons.OK, MessageBoxIcon.Information);
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка экспорта: {ex.Message}", "Ошибка",
+                    MessageBoxButtons.OK, MessageBoxIcon.Error);
+            }
+        }
+    }
+}

+ 23 - 0
ProviderApp/DBConnection.cs

@@ -0,0 +1,23 @@
+using Npgsql;
+
+namespace ProviderApp
+{
+    public static class DBConnection
+    {
+        private const string Host = "edu-pg.itiscaf.ru";
+        private const int Port = 5432;
+        private const string Database = "dbwork";
+        private const string Username = "sizov_ev";
+        private const string Password = "#%e6&R0v&%&";
+
+        // Вариант 1: Просто отключить SSL
+        public static string ConnectionString =>
+            $"Host={Host};Port={Port};Database={Database};Username={Username};Password={Password};SSL Mode=Disable;";
+        public static NpgsqlConnection GetConnection()
+        {
+            NpgsqlConnection conn = new NpgsqlConnection(ConnectionString);
+            conn.Open();
+            return conn;
+        }
+    }
+}

+ 285 - 0
ProviderApp/DashboardForm.Designer.cs

@@ -0,0 +1,285 @@
+using System.Drawing;
+using System.Windows.Forms;
+using Guna.UI2.WinForms;
+
+namespace ProviderApp
+{
+    partial class DashboardForm
+    {
+        private System.ComponentModel.IContainer components = null;
+        private Guna2Panel panel;
+        private Guna2HtmlLabel lblTitle;
+        private Guna2HtmlLabel lblWelcome;
+        private Guna2Panel panelStats;
+        private Guna2Panel panelClients;
+        private Guna2Panel panelContracts;
+        private Guna2Panel panelRevenue;
+        private Guna2Panel panelPopularTariff;
+        private Guna2HtmlLabel lblClientsCount;
+        private Guna2HtmlLabel lblClientsTitle;
+        private Guna2HtmlLabel lblContractsCount;
+        private Guna2HtmlLabel lblContractsTitle;
+        private Guna2HtmlLabel lblRevenueAmount;
+        private Guna2HtmlLabel lblRevenueTitle;
+        private Guna2HtmlLabel lblPopularTariffName;
+        private Guna2HtmlLabel lblPopularTariffTitle;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+                components.Dispose();
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges11 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges12 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges9 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges10 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges1 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges2 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges3 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges4 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges5 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges6 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges7 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges8 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            panel = new Guna2Panel();
+            lblTitle = new Guna2HtmlLabel();
+            lblWelcome = new Guna2HtmlLabel();
+            panelStats = new Guna2Panel();
+            panelClients = new Guna2Panel();
+            lblClientsCount = new Guna2HtmlLabel();
+            lblClientsTitle = new Guna2HtmlLabel();
+            panelContracts = new Guna2Panel();
+            lblContractsCount = new Guna2HtmlLabel();
+            lblContractsTitle = new Guna2HtmlLabel();
+            panelRevenue = new Guna2Panel();
+            lblRevenueAmount = new Guna2HtmlLabel();
+            lblRevenueTitle = new Guna2HtmlLabel();
+            panelPopularTariff = new Guna2Panel();
+            lblPopularTariffName = new Guna2HtmlLabel();
+            lblPopularTariffTitle = new Guna2HtmlLabel();
+            panel.SuspendLayout();
+            panelStats.SuspendLayout();
+            panelClients.SuspendLayout();
+            panelContracts.SuspendLayout();
+            panelRevenue.SuspendLayout();
+            panelPopularTariff.SuspendLayout();
+            SuspendLayout();
+            // 
+            // panel
+            // 
+            panel.BackColor = Color.FromArgb(30, 30, 30);
+            panel.Controls.Add(panelStats);
+            panel.Controls.Add(lblTitle);
+            panel.Controls.Add(lblWelcome);
+            panel.CustomizableEdges = customizableEdges11;
+            panel.Dock = DockStyle.Fill;
+            panel.Location = new Point(0, 0);
+            panel.Name = "panel";
+            panel.Padding = new Padding(26, 28, 26, 28);
+            panel.ShadowDecoration.CustomizableEdges = customizableEdges12;
+            panel.Size = new Size(1006, 797);
+            panel.TabIndex = 0;
+            // 
+            // lblTitle
+            // 
+            lblTitle.BackColor = Color.Transparent;
+            lblTitle.Font = new Font("Segoe UI", 22F, FontStyle.Bold);
+            lblTitle.ForeColor = Color.White;
+            lblTitle.Location = new Point(26, 19);
+            lblTitle.Name = "lblTitle";
+            lblTitle.Size = new Size(227, 42);
+            lblTitle.TabIndex = 0;
+            lblTitle.Text = "Главная панель";
+            // 
+            // lblWelcome
+            // 
+            lblWelcome.BackColor = Color.Transparent;
+            lblWelcome.Font = new Font("Segoe UI", 12F);
+            lblWelcome.ForeColor = Color.FromArgb(200, 200, 200);
+            lblWelcome.Location = new Point(26, 66);
+            lblWelcome.Name = "lblWelcome";
+            lblWelcome.Size = new Size(218, 23);
+            lblWelcome.TabIndex = 1;
+            lblWelcome.Text = "Добро пожаловать в систему!";
+            // 
+            // panelStats
+            // 
+            panelStats.BackColor = Color.Transparent;
+            panelStats.Controls.Add(panelClients);
+            panelStats.Controls.Add(panelContracts);
+            panelStats.Controls.Add(panelRevenue);
+            panelStats.Controls.Add(panelPopularTariff);
+            panelStats.CustomizableEdges = customizableEdges9;
+            panelStats.Location = new Point(26, 103);
+            panelStats.Name = "panelStats";
+            panelStats.ShadowDecoration.CustomizableEdges = customizableEdges10;
+            panelStats.Size = new Size(945, 262);
+            panelStats.TabIndex = 1;
+            // 
+            // panelClients
+            // 
+            panelClients.BackColor = Color.FromArgb(40, 40, 40);
+            panelClients.BorderRadius = 12;
+            panelClients.Controls.Add(lblClientsCount);
+            panelClients.Controls.Add(lblClientsTitle);
+            panelClients.CustomizableEdges = customizableEdges1;
+            panelClients.Location = new Point(0, 0);
+            panelClients.Name = "panelClients";
+            panelClients.ShadowDecoration.CustomizableEdges = customizableEdges2;
+            panelClients.Size = new Size(219, 112);
+            panelClients.TabIndex = 0;
+            // 
+            // lblClientsCount
+            // 
+            lblClientsCount.BackColor = Color.Transparent;
+            lblClientsCount.Font = new Font("Segoe UI", 28F, FontStyle.Bold);
+            lblClientsCount.ForeColor = Color.FromArgb(230, 92, 0);
+            lblClientsCount.Location = new Point(18, 33);
+            lblClientsCount.Name = "lblClientsCount";
+            lblClientsCount.Size = new Size(24, 52);
+            lblClientsCount.TabIndex = 0;
+            lblClientsCount.Text = "0";
+            // 
+            // lblClientsTitle
+            // 
+            lblClientsTitle.BackColor = Color.Transparent;
+            lblClientsTitle.Font = new Font("Segoe UI", 11F);
+            lblClientsTitle.ForeColor = Color.FromArgb(180, 180, 180);
+            lblClientsTitle.Location = new Point(18, 84);
+            lblClientsTitle.Name = "lblClientsTitle";
+            lblClientsTitle.Size = new Size(110, 22);
+            lblClientsTitle.TabIndex = 1;
+            lblClientsTitle.Text = "Всего клиентов";
+            // 
+            // panelContracts
+            // 
+            panelContracts.BackColor = Color.FromArgb(40, 40, 40);
+            panelContracts.BorderRadius = 12;
+            panelContracts.Controls.Add(lblContractsCount);
+            panelContracts.Controls.Add(lblContractsTitle);
+            panelContracts.CustomizableEdges = customizableEdges3;
+            panelContracts.Location = new Point(236, 0);
+            panelContracts.Name = "panelContracts";
+            panelContracts.ShadowDecoration.CustomizableEdges = customizableEdges4;
+            panelContracts.Size = new Size(219, 112);
+            panelContracts.TabIndex = 1;
+            // 
+            // lblContractsCount
+            // 
+            lblContractsCount.BackColor = Color.Transparent;
+            lblContractsCount.Font = new Font("Segoe UI", 28F, FontStyle.Bold);
+            lblContractsCount.ForeColor = Color.FromArgb(230, 92, 0);
+            lblContractsCount.Location = new Point(18, 33);
+            lblContractsCount.Name = "lblContractsCount";
+            lblContractsCount.Size = new Size(24, 52);
+            lblContractsCount.TabIndex = 0;
+            lblContractsCount.Text = "0";
+            // 
+            // lblContractsTitle
+            // 
+            lblContractsTitle.BackColor = Color.Transparent;
+            lblContractsTitle.Font = new Font("Segoe UI", 11F);
+            lblContractsTitle.ForeColor = Color.FromArgb(180, 180, 180);
+            lblContractsTitle.Location = new Point(18, 84);
+            lblContractsTitle.Name = "lblContractsTitle";
+            lblContractsTitle.Size = new Size(149, 22);
+            lblContractsTitle.TabIndex = 1;
+            lblContractsTitle.Text = "Активных договоров";
+            // 
+            // panelRevenue
+            // 
+            panelRevenue.BackColor = Color.FromArgb(40, 40, 40);
+            panelRevenue.BorderRadius = 12;
+            panelRevenue.Controls.Add(lblRevenueAmount);
+            panelRevenue.Controls.Add(lblRevenueTitle);
+            panelRevenue.CustomizableEdges = customizableEdges5;
+            panelRevenue.Location = new Point(472, 0);
+            panelRevenue.Name = "panelRevenue";
+            panelRevenue.ShadowDecoration.CustomizableEdges = customizableEdges6;
+            panelRevenue.Size = new Size(219, 112);
+            panelRevenue.TabIndex = 2;
+            // 
+            // lblRevenueAmount
+            // 
+            lblRevenueAmount.BackColor = Color.Transparent;
+            lblRevenueAmount.Font = new Font("Segoe UI", 22F, FontStyle.Bold);
+            lblRevenueAmount.ForeColor = Color.FromArgb(230, 92, 0);
+            lblRevenueAmount.Location = new Point(18, 33);
+            lblRevenueAmount.Name = "lblRevenueAmount";
+            lblRevenueAmount.Size = new Size(45, 42);
+            lblRevenueAmount.TabIndex = 0;
+            lblRevenueAmount.Text = "0 ₽";
+            // 
+            // lblRevenueTitle
+            // 
+            lblRevenueTitle.BackColor = Color.Transparent;
+            lblRevenueTitle.Font = new Font("Segoe UI", 11F);
+            lblRevenueTitle.ForeColor = Color.FromArgb(180, 180, 180);
+            lblRevenueTitle.Location = new Point(18, 84);
+            lblRevenueTitle.Name = "lblRevenueTitle";
+            lblRevenueTitle.Size = new Size(113, 22);
+            lblRevenueTitle.TabIndex = 1;
+            lblRevenueTitle.Text = "Общая выручка";
+            // 
+            // panelPopularTariff
+            // 
+            panelPopularTariff.BackColor = Color.FromArgb(40, 40, 40);
+            panelPopularTariff.BorderRadius = 12;
+            panelPopularTariff.Controls.Add(lblPopularTariffName);
+            panelPopularTariff.Controls.Add(lblPopularTariffTitle);
+            panelPopularTariff.CustomizableEdges = customizableEdges7;
+            panelPopularTariff.Location = new Point(0, 131);
+            panelPopularTariff.Name = "panelPopularTariff";
+            panelPopularTariff.ShadowDecoration.CustomizableEdges = customizableEdges8;
+            panelPopularTariff.Size = new Size(691, 94);
+            panelPopularTariff.TabIndex = 3;
+            // 
+            // lblPopularTariffName
+            // 
+            lblPopularTariffName.BackColor = Color.Transparent;
+            lblPopularTariffName.Font = new Font("Segoe UI", 16F, FontStyle.Bold);
+            lblPopularTariffName.ForeColor = Color.FromArgb(230, 92, 0);
+            lblPopularTariffName.Location = new Point(18, 23);
+            lblPopularTariffName.Name = "lblPopularTariffName";
+            lblPopularTariffName.Size = new Size(110, 32);
+            lblPopularTariffName.TabIndex = 0;
+            lblPopularTariffName.Text = "Загрузка...";
+            // 
+            // lblPopularTariffTitle
+            // 
+            lblPopularTariffTitle.BackColor = Color.Transparent;
+            lblPopularTariffTitle.Font = new Font("Segoe UI", 11F);
+            lblPopularTariffTitle.ForeColor = Color.FromArgb(180, 180, 180);
+            lblPopularTariffTitle.Location = new Point(18, 61);
+            lblPopularTariffTitle.Name = "lblPopularTariffTitle";
+            lblPopularTariffTitle.Size = new Size(189, 22);
+            lblPopularTariffTitle.TabIndex = 1;
+            lblPopularTariffTitle.Text = "Самый популярный тариф";
+            // 
+            // DashboardForm
+            // 
+            AutoScaleDimensions = new SizeF(7F, 15F);
+            AutoScaleMode = AutoScaleMode.Font;
+            BackColor = Color.FromArgb(30, 30, 30);
+            Controls.Add(panel);
+            Name = "DashboardForm";
+            Size = new Size(1006, 797);
+            panel.ResumeLayout(false);
+            panel.PerformLayout();
+            panelStats.ResumeLayout(false);
+            panelClients.ResumeLayout(false);
+            panelClients.PerformLayout();
+            panelContracts.ResumeLayout(false);
+            panelContracts.PerformLayout();
+            panelRevenue.ResumeLayout(false);
+            panelRevenue.PerformLayout();
+            panelPopularTariff.ResumeLayout(false);
+            panelPopularTariff.PerformLayout();
+            ResumeLayout(false);
+        }
+    }
+}

+ 102 - 0
ProviderApp/DashboardForm.cs

@@ -0,0 +1,102 @@
+using System;
+using System.Data;
+using System.Windows.Forms;
+using Npgsql;
+
+namespace ProviderApp
+{
+    public partial class DashboardForm : UserControl
+    {
+        public DashboardForm()
+        {
+            InitializeComponent();
+            this.Load += DashboardForm_Load;
+            LoadDashboardData();
+        }
+
+        private void DashboardForm_Load(object sender, EventArgs e)
+        {
+            LoadDashboardData();
+        }
+
+        public void RefreshData()
+        {
+            LoadDashboardData();
+        }
+
+        private void LoadDashboardData()
+        {
+            try
+            {
+                using (var conn = DBConnection.GetConnection())
+                {
+                    // 1. Количество клиентов
+                    string sqlClients = "SELECT COUNT(*) FROM sizov_ev.clients";
+                    using (var cmd = new NpgsqlCommand(sqlClients, conn))
+                    {
+                        int clientsCount = Convert.ToInt32(cmd.ExecuteScalar());
+                        lblClientsCount.Text = clientsCount.ToString();
+                    }
+
+                    // 2. Количество активных договоров
+                    string sqlActiveContracts = "SELECT COUNT(*) FROM sizov_ev.contracts WHERE status = 'active'";
+                    using (var cmd = new NpgsqlCommand(sqlActiveContracts, conn))
+                    {
+                        int activeContracts = Convert.ToInt32(cmd.ExecuteScalar());
+                        lblContractsCount.Text = activeContracts.ToString();
+                    }
+
+                    // 3. Общая выручка (сумма всех платежей)
+                    string sqlRevenue = "SELECT COALESCE(SUM(amount), 0) FROM sizov_ev.payments";
+                    using (var cmd = new NpgsqlCommand(sqlRevenue, conn))
+                    {
+                        decimal revenue = Convert.ToDecimal(cmd.ExecuteScalar());
+                        lblRevenueAmount.Text = $"{revenue:N0} ₽";
+                    }
+
+                    // 4. Самый популярный тариф
+                    string sqlPopularTariff = @"
+                        SELECT 
+                            t.name,
+                            COUNT(c.contract_id) as cnt
+                        FROM sizov_ev.tariffs t
+                        LEFT JOIN sizov_ev.contracts c ON t.tariff_id = c.tariff_id
+                        GROUP BY t.tariff_id, t.name
+                        ORDER BY cnt DESC
+                        LIMIT 1";
+
+                    using (var cmd = new NpgsqlCommand(sqlPopularTariff, conn))
+                    {
+                        using (var reader = cmd.ExecuteReader())
+                        {
+                            if (reader.Read())
+                            {
+                                string tariffName = reader["name"].ToString();
+                                int count = Convert.ToInt32(reader["cnt"]);
+                                lblPopularTariffName.Text = $"{tariffName} ({count} подкл.)";
+                            }
+                            else
+                            {
+                                lblPopularTariffName.Text = "Нет данных";
+                            }
+                        }
+                    }
+
+                    // Обновляем приветствие с именем пользователя
+                    if (AuthHelper.CurrentUser.IsAuthenticated)
+                    {
+                        lblWelcome.Text = $"Добро пожаловать, {AuthHelper.CurrentUser.FullName}!";
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки данных: {ex.Message}");
+                lblClientsCount.Text = "Ошибка";
+                lblContractsCount.Text = "Ошибка";
+                lblRevenueAmount.Text = "Ошибка";
+                lblPopularTariffName.Text = "Ошибка";
+            }
+        }
+    }
+}

+ 120 - 0
ProviderApp/DashboardForm.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>

+ 51 - 0
ProviderApp/DatabaseHelper.cs

@@ -0,0 +1,51 @@
+using System;
+using Npgsql;
+
+namespace ProviderApp
+{
+    public static class DatabaseHelper
+    {
+        /// <summary>
+        /// Выполняет INSERT и возвращает сгенерированный ID
+        /// </summary>
+        public static int InsertAndGetId(string insertSql, Action<NpgsqlCommand> addParameters)
+        {
+            using (var conn = DBConnection.GetConnection())
+            {
+                // Убираем точку с запятой если есть и добавляем RETURNING
+                string sql = insertSql.TrimEnd(';') + " RETURNING *id*";
+
+                // Определяем какое поле ID нужно вернуть
+                if (insertSql.Contains("clients"))
+                    sql = sql.Replace("*id*", "client_id");
+                else if (insertSql.Contains("tariffs"))
+                    sql = sql.Replace("*id*", "tariff_id");
+                else if (insertSql.Contains("contracts"))
+                    sql = sql.Replace("*id*", "contract_id");
+                else if (insertSql.Contains("employees"))
+                    sql = sql.Replace("*id*", "employee_id");
+                else
+                    sql = sql.Replace("*id*", "id");
+
+                using (var cmd = new NpgsqlCommand(sql, conn))
+                {
+                    addParameters(cmd);
+                    return Convert.ToInt32(cmd.ExecuteScalar());
+                }
+            }
+        }
+
+        /// <summary>
+        /// Выполняет UPDATE
+        /// </summary>
+        public static void Update(string updateSql, Action<NpgsqlCommand> addParameters)
+        {
+            using (var conn = DBConnection.GetConnection())
+            using (var cmd = new NpgsqlCommand(updateSql, conn))
+            {
+                addParameters(cmd);
+                cmd.ExecuteNonQuery();
+            }
+        }
+    }
+}

+ 67 - 0
ProviderApp/ExcelExporter.cs

@@ -0,0 +1,67 @@
+using System;
+using System.Data;
+using System.Windows.Forms;
+using OfficeOpenXml;
+using System.IO;
+
+namespace ProviderApp
+{
+    public static class ExcelExporter
+    {
+        static ExcelExporter()
+        {
+            // Устанавливаем лицензию (для бесплатного использования)
+            ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
+        }
+
+        public static void ExportToExcel(DataTable dataTable, string sheetName, string fileName)
+        {
+            try
+            {
+                using (var package = new ExcelPackage())
+                {
+                    var worksheet = package.Workbook.Worksheets.Add(sheetName);
+
+                    // Заголовки
+                    for (int i = 0; i < dataTable.Columns.Count; i++)
+                    {
+                        worksheet.Cells[1, i + 1].Value = dataTable.Columns[i].ColumnName;
+                        worksheet.Cells[1, i + 1].Style.Font.Bold = true;
+                        worksheet.Cells[1, i + 1].Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
+                        worksheet.Cells[1, i + 1].Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightGray);
+                    }
+
+                    // Данные
+                    for (int row = 0; row < dataTable.Rows.Count; row++)
+                    {
+                        for (int col = 0; col < dataTable.Columns.Count; col++)
+                        {
+                            worksheet.Cells[row + 2, col + 1].Value = dataTable.Rows[row][col].ToString();
+                        }
+                    }
+
+                    worksheet.Cells.AutoFitColumns();
+
+                    var saveFileDialog = new SaveFileDialog
+                    {
+                        Filter = "Excel files (*.xlsx)|*.xlsx",
+                        FileName = fileName,
+                        DefaultExt = "xlsx"
+                    };
+
+                    if (saveFileDialog.ShowDialog() == DialogResult.OK)
+                    {
+                        File.WriteAllBytes(saveFileDialog.FileName, package.GetAsByteArray());
+                        MessageBox.Show($"Экспорт завершён!\nФайл сохранён: {saveFileDialog.FileName}", "Успех",
+                            MessageBoxButtons.OK, MessageBoxIcon.Information);
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка экспорта: {ex.Message}\n\n{ex.InnerException?.Message}", "Ошибка",
+                    MessageBoxButtons.OK, MessageBoxIcon.Error);
+            }
+        }
+    }
+}

+ 176 - 0
ProviderApp/LoginForm.Designer.cs

@@ -0,0 +1,176 @@
+using System.Drawing;
+using System.Windows.Forms;
+using Guna.UI2.WinForms;
+
+namespace ProviderApp
+{
+    partial class LoginForm
+    {
+        private System.ComponentModel.IContainer components = null;
+        private Guna2TextBox txtUsername;
+        private Guna2TextBox txtPassword;
+        private Guna2Button btnLogin;
+        private Guna2Button btnExit;
+        private Label lblTitle;
+        private Label lblError;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+                components.Dispose();
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges1 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges2 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges3 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges4 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges5 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges6 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges7 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges8 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            lblTitle = new Label();
+            txtUsername = new Guna2TextBox();
+            txtPassword = new Guna2TextBox();
+            btnLogin = new Guna2Button();
+            btnExit = new Guna2Button();
+            lblError = new Label();
+            lblUser = new Label();
+            lblPass = new Label();
+            SuspendLayout();
+            // 
+            // lblTitle
+            // 
+            lblTitle.AutoSize = true;
+            lblTitle.BackColor = Color.Transparent;
+            lblTitle.Font = new Font("Segoe UI", 18F, FontStyle.Bold);
+            lblTitle.ForeColor = Color.FromArgb(230, 92, 0);
+            lblTitle.Location = new Point(21, 18);
+            lblTitle.Name = "lblTitle";
+            lblTitle.Size = new Size(358, 32);
+            lblTitle.TabIndex = 0;
+            lblTitle.Text = "ProviderNet - Вход в систему";
+            // 
+            // txtUsername
+            // 
+            txtUsername.BorderRadius = 8;
+            txtUsername.CustomizableEdges = customizableEdges1;
+            txtUsername.DefaultText = "";
+            txtUsername.FillColor = Color.FromArgb(45, 45, 45);
+            txtUsername.Font = new Font("Segoe UI", 11F);
+            txtUsername.ForeColor = Color.White;
+            txtUsername.Location = new Point(131, 103);
+            txtUsername.Name = "txtUsername";
+            txtUsername.PlaceholderText = "Введите логин";
+            txtUsername.SelectedText = "";
+            txtUsername.ShadowDecoration.CustomizableEdges = customizableEdges2;
+            txtUsername.Size = new Size(210, 33);
+            txtUsername.TabIndex = 1;
+            // 
+            // txtPassword
+            // 
+            txtPassword.BorderRadius = 8;
+            txtPassword.CustomizableEdges = customizableEdges3;
+            txtPassword.DefaultText = "";
+            txtPassword.FillColor = Color.FromArgb(45, 45, 45);
+            txtPassword.Font = new Font("Segoe UI", 11F);
+            txtPassword.ForeColor = Color.White;
+            txtPassword.Location = new Point(131, 155);
+            txtPassword.Name = "txtPassword";
+            txtPassword.PasswordChar = '●';
+            txtPassword.PlaceholderText = "Введите пароль";
+            txtPassword.SelectedText = "";
+            txtPassword.ShadowDecoration.CustomizableEdges = customizableEdges4;
+            txtPassword.Size = new Size(210, 33);
+            txtPassword.TabIndex = 2;
+            // 
+            // btnLogin
+            // 
+            btnLogin.BorderRadius = 8;
+            btnLogin.CustomizableEdges = customizableEdges5;
+            btnLogin.FillColor = Color.FromArgb(230, 92, 0);
+            btnLogin.Font = new Font("Segoe UI", 11F, FontStyle.Bold);
+            btnLogin.ForeColor = Color.White;
+            btnLogin.Location = new Point(88, 225);
+            btnLogin.Name = "btnLogin";
+            btnLogin.ShadowDecoration.CustomizableEdges = customizableEdges6;
+            btnLogin.Size = new Size(105, 38);
+            btnLogin.TabIndex = 3;
+            btnLogin.Text = "Войти";
+            // 
+            // btnExit
+            // 
+            btnExit.BorderRadius = 8;
+            btnExit.CustomizableEdges = customizableEdges7;
+            btnExit.FillColor = Color.FromArgb(80, 80, 80);
+            btnExit.Font = new Font("Segoe UI", 11F, FontStyle.Bold);
+            btnExit.ForeColor = Color.White;
+            btnExit.Location = new Point(210, 225);
+            btnExit.Name = "btnExit";
+            btnExit.ShadowDecoration.CustomizableEdges = customizableEdges8;
+            btnExit.Size = new Size(105, 38);
+            btnExit.TabIndex = 4;
+            btnExit.Text = "Выход";
+            // 
+            // lblError
+            // 
+            lblError.BackColor = Color.Transparent;
+            lblError.Font = new Font("Segoe UI", 9F);
+            lblError.ForeColor = Color.Red;
+            lblError.Location = new Point(44, 291);
+            lblError.Name = "lblError";
+            lblError.Size = new Size(306, 28);
+            lblError.TabIndex = 5;
+            lblError.TextAlign = ContentAlignment.MiddleCenter;
+            // 
+            // lblUser
+            // 
+            lblUser.BackColor = Color.Transparent;
+            lblUser.Font = new Font("Segoe UI", 11F);
+            lblUser.ForeColor = Color.White;
+            lblUser.Location = new Point(44, 108);
+            lblUser.Name = "lblUser";
+            lblUser.Size = new Size(70, 23);
+            lblUser.TabIndex = 0;
+            lblUser.Text = "Логин:";
+            // 
+            // lblPass
+            // 
+            lblPass.BackColor = Color.Transparent;
+            lblPass.Font = new Font("Segoe UI", 11F);
+            lblPass.ForeColor = Color.White;
+            lblPass.Location = new Point(44, 159);
+            lblPass.Name = "lblPass";
+            lblPass.Size = new Size(70, 23);
+            lblPass.TabIndex = 1;
+            lblPass.Text = "Пароль:";
+            // 
+            // LoginForm
+            // 
+            AutoScaleDimensions = new SizeF(7F, 15F);
+            AutoScaleMode = AutoScaleMode.Font;
+            BackColor = Color.FromArgb(30, 30, 30);
+            ClientSize = new Size(402, 356);
+            Controls.Add(lblUser);
+            Controls.Add(lblPass);
+            Controls.Add(lblTitle);
+            Controls.Add(txtUsername);
+            Controls.Add(txtPassword);
+            Controls.Add(btnLogin);
+            Controls.Add(btnExit);
+            Controls.Add(lblError);
+            FormBorderStyle = FormBorderStyle.FixedDialog;
+            MaximizeBox = false;
+            Name = "LoginForm";
+            StartPosition = FormStartPosition.CenterScreen;
+            Text = "Авторизация";
+            ResumeLayout(false);
+            PerformLayout();
+        }
+
+        private Label lblUser;
+        private Label lblPass;
+    }
+}

+ 54 - 0
ProviderApp/LoginForm.cs

@@ -0,0 +1,54 @@
+using System;
+using System.Windows.Forms;
+
+namespace ProviderApp
+{
+    public partial class LoginForm : Form
+    {
+        public LoginForm()
+        {
+            InitializeComponent();
+
+            // Привязываем события к кнопкам
+            this.btnLogin.Click += BtnLogin_Click;
+            this.btnExit.Click += BtnExit_Click;
+
+            // Настройка Enter для быстрого входа
+            this.txtPassword.KeyPress += TxtPassword_KeyPress;
+        }
+
+        private void BtnLogin_Click(object sender, EventArgs e)
+        {
+            if (string.IsNullOrWhiteSpace(txtUsername.Text) || string.IsNullOrWhiteSpace(txtPassword.Text))
+            {
+                lblError.Text = "Заполните логин и пароль!";
+                return;
+            }
+
+            if (AuthHelper.Login(txtUsername.Text, txtPassword.Text))
+            {
+                this.DialogResult = DialogResult.OK;
+                this.Close();
+            }
+            else
+            {
+                lblError.Text = "Неверный логин или пароль!";
+                txtPassword.Clear();
+                txtPassword.Focus();
+            }
+        }
+
+        private void BtnExit_Click(object sender, EventArgs e)
+        {
+            Application.Exit();
+        }
+
+        private void TxtPassword_KeyPress(object sender, KeyPressEventArgs e)
+        {
+            if (e.KeyChar == (char)Keys.Enter)
+            {
+                BtnLogin_Click(null, null);
+            }
+        }
+    }
+}

+ 120 - 0
ProviderApp/LoginForm.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>

+ 197 - 0
ProviderApp/MainForm.Designer.cs

@@ -0,0 +1,197 @@
+using System.Drawing;
+using System.Windows.Forms;
+using Guna.UI2.WinForms;
+
+namespace ProviderApp
+{
+    partial class MainForm
+    {
+        private System.ComponentModel.IContainer components = null;
+        private Guna2Panel panelMenu;
+        private Guna2Panel panelContent;
+        private Guna2Button btnTariffs;
+        private Guna2Button btnClients;
+        private Guna2Button btnContracts;
+        private Guna2Button btnAnalytics;
+        private Guna2Button btnLogout;
+        private Guna2Button btnRefresh;
+        private Guna2HtmlLabel lblLogo;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+                components.Dispose();
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges13 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges14 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges1 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges2 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges3 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges4 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges5 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges6 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges7 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges8 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges9 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges10 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges11 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges12 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges15 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            Guna.UI2.WinForms.Suite.CustomizableEdges customizableEdges16 = new Guna.UI2.WinForms.Suite.CustomizableEdges();
+            panelMenu = new Guna2Panel();
+            lblLogo = new Guna2HtmlLabel();
+            btnTariffs = new Guna2Button();
+            btnClients = new Guna2Button();
+            btnContracts = new Guna2Button();
+            btnAnalytics = new Guna2Button();
+            btnRefresh = new Guna2Button();
+            btnLogout = new Guna2Button();
+            panelContent = new Guna2Panel();
+            panelMenu.SuspendLayout();
+            SuspendLayout();
+            // 
+            // panelMenu
+            // 
+            panelMenu.BackColor = Color.FromArgb(28, 28, 28);
+            panelMenu.Controls.Add(lblLogo);
+            panelMenu.Controls.Add(btnTariffs);
+            panelMenu.Controls.Add(btnClients);
+            panelMenu.Controls.Add(btnContracts);
+            panelMenu.Controls.Add(btnAnalytics);
+            panelMenu.Controls.Add(btnRefresh);
+            panelMenu.Controls.Add(btnLogout);
+            panelMenu.CustomizableEdges = customizableEdges13;
+            panelMenu.Dock = DockStyle.Left;
+            panelMenu.Location = new Point(0, 0);
+            panelMenu.Name = "panelMenu";
+            panelMenu.ShadowDecoration.CustomizableEdges = customizableEdges14;
+            panelMenu.Size = new Size(219, 797);
+            panelMenu.TabIndex = 0;
+            // 
+            // lblLogo
+            // 
+            lblLogo.BackColor = Color.Transparent;
+            lblLogo.Font = new Font("Segoe UI", 16F, FontStyle.Bold);
+            lblLogo.ForeColor = Color.FromArgb(230, 92, 0);
+            lblLogo.Location = new Point(22, 23);
+            lblLogo.Name = "lblLogo";
+            lblLogo.Size = new Size(122, 32);
+            lblLogo.TabIndex = 0;
+            lblLogo.Text = "ProviderNet";
+            // 
+            // btnTariffs
+            // 
+            btnTariffs.BorderRadius = 8;
+            btnTariffs.CustomizableEdges = customizableEdges1;
+            btnTariffs.FillColor = Color.FromArgb(40, 40, 40);
+            btnTariffs.Font = new Font("Segoe UI", 12F, FontStyle.Bold);
+            btnTariffs.ForeColor = Color.White;
+            btnTariffs.Location = new Point(13, 94);
+            btnTariffs.Name = "btnTariffs";
+            btnTariffs.ShadowDecoration.CustomizableEdges = customizableEdges2;
+            btnTariffs.Size = new Size(192, 52);
+            btnTariffs.TabIndex = 1;
+            btnTariffs.Text = "📡 Тарифы";
+            // 
+            // btnClients
+            // 
+            btnClients.BorderRadius = 8;
+            btnClients.CustomizableEdges = customizableEdges3;
+            btnClients.FillColor = Color.FromArgb(40, 40, 40);
+            btnClients.Font = new Font("Segoe UI", 12F, FontStyle.Bold);
+            btnClients.ForeColor = Color.White;
+            btnClients.Location = new Point(13, 159);
+            btnClients.Name = "btnClients";
+            btnClients.ShadowDecoration.CustomizableEdges = customizableEdges4;
+            btnClients.Size = new Size(192, 52);
+            btnClients.TabIndex = 2;
+            btnClients.Text = "👥 Клиенты";
+            // 
+            // btnContracts
+            // 
+            btnContracts.BorderRadius = 8;
+            btnContracts.CustomizableEdges = customizableEdges5;
+            btnContracts.FillColor = Color.FromArgb(40, 40, 40);
+            btnContracts.Font = new Font("Segoe UI", 12F, FontStyle.Bold);
+            btnContracts.ForeColor = Color.White;
+            btnContracts.Location = new Point(13, 225);
+            btnContracts.Name = "btnContracts";
+            btnContracts.ShadowDecoration.CustomizableEdges = customizableEdges6;
+            btnContracts.Size = new Size(192, 52);
+            btnContracts.TabIndex = 3;
+            btnContracts.Text = "📄 Договоры";
+            // 
+            // btnAnalytics
+            // 
+            btnAnalytics.BorderRadius = 8;
+            btnAnalytics.CustomizableEdges = customizableEdges7;
+            btnAnalytics.FillColor = Color.FromArgb(40, 40, 40);
+            btnAnalytics.Font = new Font("Segoe UI", 12F, FontStyle.Bold);
+            btnAnalytics.ForeColor = Color.White;
+            btnAnalytics.Location = new Point(13, 291);
+            btnAnalytics.Name = "btnAnalytics";
+            btnAnalytics.ShadowDecoration.CustomizableEdges = customizableEdges8;
+            btnAnalytics.Size = new Size(192, 52);
+            btnAnalytics.TabIndex = 4;
+            btnAnalytics.Text = "📊 Аналитика";
+            // 
+            // btnRefresh
+            // 
+            btnRefresh.BorderRadius = 8;
+            btnRefresh.CustomizableEdges = customizableEdges9;
+            btnRefresh.FillColor = Color.FromArgb(0, 120, 215);
+            btnRefresh.Font = new Font("Segoe UI", 11F, FontStyle.Bold);
+            btnRefresh.ForeColor = Color.White;
+            btnRefresh.Location = new Point(13, 422);
+            btnRefresh.Name = "btnRefresh";
+            btnRefresh.ShadowDecoration.CustomizableEdges = customizableEdges10;
+            btnRefresh.Size = new Size(192, 42);
+            btnRefresh.TabIndex = 5;
+            btnRefresh.Text = "🔄 Обновить";
+            // 
+            // btnLogout
+            // 
+            btnLogout.BorderRadius = 8;
+            btnLogout.CustomizableEdges = customizableEdges11;
+            btnLogout.FillColor = Color.FromArgb(220, 53, 69);
+            btnLogout.Font = new Font("Segoe UI", 11F, FontStyle.Bold);
+            btnLogout.ForeColor = Color.White;
+            btnLogout.Location = new Point(13, 478);
+            btnLogout.Name = "btnLogout";
+            btnLogout.ShadowDecoration.CustomizableEdges = customizableEdges12;
+            btnLogout.Size = new Size(192, 42);
+            btnLogout.TabIndex = 6;
+            btnLogout.Text = "🚪 Выход";
+            // 
+            // panelContent
+            // 
+            panelContent.BackColor = Color.FromArgb(30, 30, 30);
+            panelContent.CustomizableEdges = customizableEdges15;
+            panelContent.Dock = DockStyle.Fill;
+            panelContent.Location = new Point(219, 0);
+            panelContent.Name = "panelContent";
+            panelContent.ShadowDecoration.CustomizableEdges = customizableEdges16;
+            panelContent.Size = new Size(1006, 797);
+            panelContent.TabIndex = 1;
+            // 
+            // MainForm
+            // 
+            AutoScaleDimensions = new SizeF(7F, 15F);
+            AutoScaleMode = AutoScaleMode.Font;
+            BackColor = Color.FromArgb(28, 28, 28);
+            ClientSize = new Size(1225, 797);
+            Controls.Add(panelContent);
+            Controls.Add(panelMenu);
+            Name = "MainForm";
+            StartPosition = FormStartPosition.CenterScreen;
+            Text = "ProviderNet - Интернет провайдер";
+            panelMenu.ResumeLayout(false);
+            panelMenu.PerformLayout();
+            ResumeLayout(false);
+        }
+    }
+}

+ 61 - 0
ProviderApp/MainForm.cs

@@ -0,0 +1,61 @@
+using System;
+using System.Windows.Forms;
+
+namespace ProviderApp
+{
+    public partial class MainForm : Form
+    {
+        private UserControl currentControl = null;
+
+        public MainForm()
+        {
+            InitializeComponent();
+            btnTariffs.Click += (s, e) => LoadControl(new TariffsForm(), "Тарифы");
+            btnClients.Click += (s, e) => LoadControl(new ClientsForm(), "Клиенты");
+            btnContracts.Click += (s, e) => LoadControl(new ContractsForm(), "Договоры");
+            btnAnalytics.Click += (s, e) => LoadControl(new AnalyticsForm(), "Аналитика");
+            btnRefresh.Click += BtnRefresh_Click;
+            btnLogout.Click += BtnLogout_Click;
+
+            // Загружаем Dashboard стартовой страницей
+            LoadControl(new DashboardForm(), "Главная");
+        }
+
+        private void LoadControl(UserControl control, string title)
+        {
+            if (currentControl != null)
+            {
+                panelContent.Controls.Remove(currentControl);
+                currentControl.Dispose();
+            }
+            currentControl = control;
+            control.Dock = DockStyle.Fill;
+            panelContent.Controls.Add(control);
+            control.BringToFront();
+        }
+
+        private void BtnRefresh_Click(object sender, EventArgs e)
+        {
+            // Обновляем текущую форму
+            if (currentControl != null)
+            {
+                if (currentControl is DashboardForm)
+                    ((DashboardForm)currentControl).RefreshData();
+                else if (currentControl is TariffsForm)
+                    ((TariffsForm)currentControl).RefreshData();
+                else if (currentControl is ClientsForm)
+                    ((ClientsForm)currentControl).RefreshData();
+                else if (currentControl is ContractsForm)
+                    ((ContractsForm)currentControl).RefreshData();
+                else if (currentControl is AnalyticsForm)
+                    ((AnalyticsForm)currentControl).RefreshData();
+            }
+        }
+
+        private void BtnLogout_Click(object sender, EventArgs e)
+        {
+            AuthHelper.Logout();
+            Application.Restart();
+        }
+    }
+}

+ 120 - 0
ProviderApp/MainForm.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>

+ 26 - 0
ProviderApp/Program.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Windows.Forms;
+
+namespace ProviderApp
+{
+    internal static class Program
+    {
+        [STAThread]
+        static void Main()
+        {
+            Application.EnableVisualStyles();
+            Application.SetCompatibleTextRenderingDefault(false);
+
+            // Показываем форму авторизации
+            using (var loginForm = new LoginForm())
+            {
+                if (loginForm.ShowDialog() != DialogResult.OK)
+                {
+                    return; // Выход, если авторизация не пройдена
+                }
+            }
+
+            Application.Run(new MainForm());
+        }
+    }
+}

+ 16 - 0
ProviderApp/Properties/PublishProfiles/FolderProfile.pubxml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
+<Project>
+  <PropertyGroup>
+    <Configuration>Release</Configuration>
+    <Platform>Any CPU</Platform>
+    <PublishDir>C:\Users\user\Desktop\Desk</PublishDir>
+    <PublishProtocol>FileSystem</PublishProtocol>
+    <_TargetId>Folder</_TargetId>
+    <TargetFramework>net10.0-windows</TargetFramework>
+    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
+    <SelfContained>true</SelfContained>
+    <PublishSingleFile>true</PublishSingleFile>
+    <PublishReadyToRun>false</PublishReadyToRun>
+  </PropertyGroup>
+</Project>

+ 8 - 0
ProviderApp/Properties/PublishProfiles/FolderProfile.pubxml.user

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
+<Project>
+  <PropertyGroup>
+    <History>True|2026-06-02T15:01:44.7418047Z||;True|2026-06-02T18:00:48.9510528+03:00||;True|2026-06-02T14:33:03.1332826+03:00||;False|2026-06-02T14:32:08.8470194+03:00||;</History>
+    <LastFailureDetails />
+  </PropertyGroup>
+</Project>

+ 18 - 0
ProviderApp/ProviderApp.csproj

@@ -0,0 +1,18 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>WinExe</OutputType>
+    <TargetFramework>net10.0-windows</TargetFramework>
+    <Nullable>disable</Nullable>
+    <UseWindowsForms>true</UseWindowsForms>
+    <ImplicitUsings>enable</ImplicitUsings>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="EPPlus" Version="8.5.4" />
+    <PackageReference Include="Guna.UI2.WinForms" Version="2.0.4.7" />
+    <PackageReference Include="Npgsql" Version="10.0.2" />
+    <PackageReference Include="OxyPlot.WindowsForms" Version="2.2.0" />
+  </ItemGroup>
+
+</Project>

+ 35 - 0
ProviderApp/ProviderApp.csproj.user

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <_LastSelectedProfileId>C:\Users\user\source\repos\ProviderApp\ProviderApp\Properties\PublishProfiles\FolderProfile.pubxml</_LastSelectedProfileId>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Update="AddEditContractForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Update="AddEditTariffForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Update="AnalyticsForm.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Update="ClientsForm.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Update="ContractsForm.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Update="DashboardForm.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Update="LoginForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Update="MainForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Update="TariffsForm.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+  </ItemGroup>
+</Project>

+ 121 - 0
ProviderApp/TariffsForm.Designer.cs

@@ -0,0 +1,121 @@
+using System.Drawing;
+using System.Windows.Forms;
+using Guna.UI2.WinForms;
+
+namespace ProviderApp
+{
+    partial class TariffsForm
+    {
+        private System.ComponentModel.IContainer components = null;
+        private Guna2Panel panel;
+        private Guna2HtmlLabel lblTitle;
+        private Guna2Button btnAdd;
+        private Guna2Button btnEdit;
+        private Guna2Button btnDelete;
+        private Guna2Button btnExport;
+        private Guna2DataGridView dgvTariffs;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null)) components.Dispose();
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.panel = new Guna2Panel();
+            this.lblTitle = new Guna2HtmlLabel();
+            this.btnAdd = new Guna2Button();
+            this.btnEdit = new Guna2Button();
+            this.btnDelete = new Guna2Button();
+            this.btnExport = new Guna2Button();
+            this.dgvTariffs = new Guna2DataGridView();
+            this.panel.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvTariffs)).BeginInit();
+            this.SuspendLayout();
+
+            this.panel.BackColor = Color.FromArgb(30, 30, 30);
+            this.panel.Controls.Add(this.lblTitle);
+            this.panel.Controls.Add(this.btnAdd);
+            this.panel.Controls.Add(this.btnEdit);
+            this.panel.Controls.Add(this.btnDelete);
+            this.panel.Controls.Add(this.btnExport);
+            this.panel.Controls.Add(this.dgvTariffs);
+            this.panel.Dock = DockStyle.Fill;
+            this.panel.Location = new Point(0, 0);
+            this.panel.Name = "panel";
+            this.panel.Padding = new Padding(30);
+            this.panel.Size = new Size(1150, 850);
+            this.panel.TabIndex = 0;
+
+            this.lblTitle.BackColor = Color.Transparent;
+            this.lblTitle.Font = new Font("Segoe UI", 20F, FontStyle.Bold);
+            this.lblTitle.ForeColor = Color.White;
+            this.lblTitle.Location = new Point(30, 20);
+            this.lblTitle.Name = "lblTitle";
+            this.lblTitle.Size = new Size(250, 38);
+            this.lblTitle.Text = "Управление тарифами";
+
+            this.btnAdd.BorderRadius = 8;
+            this.btnAdd.FillColor = Color.FromArgb(230, 92, 0);
+            this.btnAdd.Font = new Font("Segoe UI", 11F, FontStyle.Bold);
+            this.btnAdd.ForeColor = Color.White;
+            this.btnAdd.Location = new Point(30, 80);
+            this.btnAdd.Name = "btnAdd";
+            this.btnAdd.Size = new Size(120, 45);
+            this.btnAdd.Text = "Добавить";
+
+            this.btnEdit.BorderRadius = 8;
+            this.btnEdit.FillColor = Color.FromArgb(230, 92, 0);
+            this.btnEdit.Font = new Font("Segoe UI", 11F, FontStyle.Bold);
+            this.btnEdit.ForeColor = Color.White;
+            this.btnEdit.Location = new Point(165, 80);
+            this.btnEdit.Name = "btnEdit";
+            this.btnEdit.Size = new Size(120, 45);
+            this.btnEdit.Text = "Изменить";
+
+            this.btnDelete.BorderRadius = 8;
+            this.btnDelete.FillColor = Color.FromArgb(230, 92, 0);
+            this.btnDelete.Font = new Font("Segoe UI", 11F, FontStyle.Bold);
+            this.btnDelete.ForeColor = Color.White;
+            this.btnDelete.Location = new Point(300, 80);
+            this.btnDelete.Name = "btnDelete";
+            this.btnDelete.Size = new Size(120, 45);
+            this.btnDelete.Text = "Удалить";
+
+            this.btnExport.BorderRadius = 8;
+            this.btnExport.FillColor = Color.FromArgb(0, 120, 215);
+            this.btnExport.Font = new Font("Segoe UI", 11F, FontStyle.Bold);
+            this.btnExport.ForeColor = Color.White;
+            this.btnExport.Location = new Point(435, 80);
+            this.btnExport.Name = "btnExport";
+            this.btnExport.Size = new Size(140, 45);
+            this.btnExport.Text = "Экспорт Excel";
+
+            this.dgvTariffs.AllowUserToAddRows = false;
+            this.dgvTariffs.AllowUserToDeleteRows = false;
+            this.dgvTariffs.BackgroundColor = Color.FromArgb(40, 40, 40);
+            this.dgvTariffs.BorderStyle = BorderStyle.None;
+            this.dgvTariffs.CellBorderStyle = DataGridViewCellBorderStyle.SingleHorizontal;
+            this.dgvTariffs.ColumnHeadersHeight = 50;
+            this.dgvTariffs.GridColor = Color.FromArgb(60, 60, 60);
+            this.dgvTariffs.Location = new Point(30, 150);
+            this.dgvTariffs.Name = "dgvTariffs";
+            this.dgvTariffs.RowHeadersVisible = false;
+            this.dgvTariffs.RowTemplate.Height = 40;
+            this.dgvTariffs.Size = new Size(1090, 660);
+            this.dgvTariffs.AutoGenerateColumns = true;
+            this.dgvTariffs.ReadOnly = true;
+
+            this.AutoScaleDimensions = new SizeF(8F, 16F);
+            this.AutoScaleMode = AutoScaleMode.Font;
+            this.BackColor = Color.FromArgb(30, 30, 30);
+            this.Controls.Add(this.panel);
+            this.Name = "TariffsForm";
+            this.Size = new Size(1150, 850);
+            this.panel.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.dgvTariffs)).EndInit();
+            this.ResumeLayout(false);
+        }
+    }
+}

+ 183 - 0
ProviderApp/TariffsForm.cs

@@ -0,0 +1,183 @@
+using System;
+using System.Data;
+using System.Windows.Forms;
+using Npgsql;
+
+namespace ProviderApp
+{
+    public partial class TariffsForm : UserControl
+    {
+        private DataTable tariffsTable;
+
+        public TariffsForm()
+        {
+            InitializeComponent();
+            this.Load += TariffsForm_Load;
+            btnAdd.Click += BtnAdd_Click;
+            btnEdit.Click += BtnEdit_Click;
+            btnDelete.Click += BtnDelete_Click;
+            btnExport.Click += BtnExport_Click;
+            StyleDataGridView();
+        }
+
+
+        private void StyleDataGridView()
+        {
+            dgvTariffs.ColumnHeadersDefaultCellStyle.BackColor = System.Drawing.Color.FromArgb(230, 92, 0);
+            dgvTariffs.ColumnHeadersDefaultCellStyle.ForeColor = System.Drawing.Color.White;
+            dgvTariffs.ColumnHeadersDefaultCellStyle.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Bold);
+            dgvTariffs.DefaultCellStyle.BackColor = System.Drawing.Color.FromArgb(40, 40, 40);
+            dgvTariffs.DefaultCellStyle.ForeColor = System.Drawing.Color.White;
+            dgvTariffs.AlternatingRowsDefaultCellStyle.BackColor = System.Drawing.Color.FromArgb(45, 45, 45);
+            dgvTariffs.EnableHeadersVisualStyles = false;
+        }
+
+        private void TariffsForm_Load(object sender, EventArgs e)
+        {
+            LoadTariffs();
+            btnAdd.Visible = AuthHelper.CurrentUser.IsManager;
+            btnEdit.Visible = AuthHelper.CurrentUser.IsManager;
+            btnDelete.Visible = AuthHelper.CurrentUser.IsAdmin;
+            btnExport.Visible = AuthHelper.CurrentUser.IsManager;
+        }
+        public void RefreshData()
+        {
+            LoadTariffs();
+        }
+        private void LoadTariffs()
+        {
+            try
+            {
+                using (var conn = DBConnection.GetConnection())
+                {
+                    string sql = "SELECT tariff_id, name, monthly_price, speed_mbit FROM tariffs ORDER BY tariff_id";
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        DataTable dt = new DataTable();
+                        using (var da = new NpgsqlDataAdapter(cmd))
+                        {
+                            da.Fill(dt);
+                        }
+                        tariffsTable = dt;
+                        dgvTariffs.DataSource = tariffsTable;
+
+                        if (dgvTariffs.Columns["tariff_id"] != null)
+                            dgvTariffs.Columns["tariff_id"].HeaderText = "ID";
+                        if (dgvTariffs.Columns["name"] != null)
+                            dgvTariffs.Columns["name"].HeaderText = "Название";
+                        if (dgvTariffs.Columns["monthly_price"] != null)
+                            dgvTariffs.Columns["monthly_price"].HeaderText = "Цена, руб";
+                        if (dgvTariffs.Columns["speed_mbit"] != null)
+                            dgvTariffs.Columns["speed_mbit"].HeaderText = "Скорость, Мбит/с";
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки тарифов: {ex.Message}");
+            }
+        }
+
+        private void BtnAdd_Click(object sender, EventArgs e)
+        {
+            if (!AuthHelper.CheckPermission("manager")) return;
+            AuthHelper.LogAction("Попытка добавления тарифа", "tariffs");
+
+            using (var form = new AddEditTariffForm())
+            {
+                if (form.ShowDialog() == DialogResult.OK)
+                {
+                    AuthHelper.LogAction("Тариф добавлен", "tariffs");
+                    LoadTariffs();
+                }
+            }
+        }
+
+        private void BtnEdit_Click(object sender, EventArgs e)
+        {
+            if (!AuthHelper.CheckPermission("manager")) return;
+
+            if (dgvTariffs.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите тариф.");
+                return;
+            }
+            int tariffId = Convert.ToInt32(dgvTariffs.CurrentRow.Cells["tariff_id"].Value);
+            AuthHelper.LogAction($"Попытка редактирования тарифа ID={tariffId}", "tariffs", tariffId);
+
+            using (var form = new AddEditTariffForm(tariffId))
+            {
+                if (form.ShowDialog() == DialogResult.OK)
+                {
+                    AuthHelper.LogAction($"Тариф ID={tariffId} отредактирован", "tariffs", tariffId);
+                    LoadTariffs();
+                }
+            }
+        }
+
+        private void BtnDelete_Click(object sender, EventArgs e)
+        {
+            if (!AuthHelper.CheckPermission("admin")) return;
+
+            if (dgvTariffs.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите тариф.");
+                return;
+            }
+            if (MessageBox.Show("Удалить тариф?", "Подтверждение", MessageBoxButtons.YesNo) == DialogResult.Yes)
+            {
+                int tariffId = Convert.ToInt32(dgvTariffs.CurrentRow.Cells["tariff_id"].Value);
+                try
+                {
+                    using (var conn = DBConnection.GetConnection())
+                    {
+                        string sql = "DELETE FROM tariffs WHERE tariff_id = @id";
+                        using (var cmd = new NpgsqlCommand(sql, conn))
+                        {
+                            cmd.Parameters.AddWithValue("@id", tariffId);
+                            cmd.ExecuteNonQuery();
+                        }
+                    }
+                    AuthHelper.LogAction($"Тариф ID={tariffId} удалён", "tariffs", tariffId);
+                    LoadTariffs();
+                    MessageBox.Show("Тариф удалён.");
+                }
+                catch (PostgresException ex) when (ex.SqlState == "23503")
+                {
+                    MessageBox.Show("Нельзя удалить тариф, так как он используется в договорах.");
+                }
+                catch (Exception ex)
+                {
+                    MessageBox.Show($"Ошибка удаления: {ex.Message}");
+                }
+            }
+        }
+
+        private void BtnExport_Click(object sender, EventArgs e)
+        {
+            if (!AuthHelper.CheckPermission("manager")) return;
+
+            try
+            {
+                using (var conn = DBConnection.GetConnection())
+                {
+                    string sql = "SELECT tariff_id as \"ID\", name as \"Название\", monthly_price as \"Цена, руб\", speed_mbit as \"Скорость, Мбит/с\" FROM tariffs ORDER BY tariff_id";
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        DataTable dt = new DataTable();
+                        using (var da = new NpgsqlDataAdapter(cmd))
+                        {
+                            da.Fill(dt);
+                        }
+                        AuthHelper.LogAction("Экспорт тарифов в CSV", "tariffs");
+                        CsvExporter.ExportToCsv(dt, $"Тарифы_{DateTime.Now:yyyyMMdd_HHmmss}.csv");
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка экспорта: {ex.Message}");
+            }
+        }
+    }
+}

+ 120 - 0
ProviderApp/TariffsForm.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>

BIN
ProviderApp/bin/Debug/net10.0-windows/EPPlus.Interfaces.dll


BIN
ProviderApp/bin/Debug/net10.0-windows/EPPlus.dll


BIN
ProviderApp/bin/Debug/net10.0-windows/Guna.UI2.dll


BIN
ProviderApp/bin/Debug/net10.0-windows/Microsoft.Extensions.Configuration.Abstractions.dll


BIN
ProviderApp/bin/Debug/net10.0-windows/Microsoft.Extensions.Configuration.FileExtensions.dll


BIN
ProviderApp/bin/Debug/net10.0-windows/Microsoft.Extensions.Configuration.Json.dll


BIN
ProviderApp/bin/Debug/net10.0-windows/Microsoft.Extensions.Configuration.dll


BIN
ProviderApp/bin/Debug/net10.0-windows/Microsoft.Extensions.DependencyInjection.Abstractions.dll


BIN
ProviderApp/bin/Debug/net10.0-windows/Microsoft.Extensions.FileProviders.Abstractions.dll


BIN
ProviderApp/bin/Debug/net10.0-windows/Microsoft.Extensions.FileProviders.Physical.dll


BIN
ProviderApp/bin/Debug/net10.0-windows/Microsoft.Extensions.FileSystemGlobbing.dll


BIN
ProviderApp/bin/Debug/net10.0-windows/Microsoft.Extensions.Logging.Abstractions.dll


BIN
ProviderApp/bin/Debug/net10.0-windows/Microsoft.Extensions.Primitives.dll


BIN
ProviderApp/bin/Debug/net10.0-windows/Microsoft.IO.RecyclableMemoryStream.dll


BIN
ProviderApp/bin/Debug/net10.0-windows/Npgsql.dll


BIN
ProviderApp/bin/Debug/net10.0-windows/OxyPlot.WindowsForms.dll


BIN
ProviderApp/bin/Debug/net10.0-windows/OxyPlot.dll


+ 352 - 0
ProviderApp/bin/Debug/net10.0-windows/ProviderApp.deps.json

@@ -0,0 +1,352 @@
+{
+  "runtimeTarget": {
+    "name": ".NETCoreApp,Version=v10.0",
+    "signature": ""
+  },
+  "compilationOptions": {},
+  "targets": {
+    ".NETCoreApp,Version=v10.0": {
+      "ProviderApp/1.0.0": {
+        "dependencies": {
+          "EPPlus": "8.5.4",
+          "Guna.UI2.WinForms": "2.0.4.7",
+          "Npgsql": "10.0.2",
+          "OxyPlot.WindowsForms": "2.2.0"
+        },
+        "runtime": {
+          "ProviderApp.dll": {}
+        }
+      },
+      "EPPlus/8.5.4": {
+        "dependencies": {
+          "EPPlus.Interfaces": "8.4.0",
+          "Microsoft.Extensions.Configuration.Json": "10.0.7",
+          "Microsoft.IO.RecyclableMemoryStream": "3.0.1"
+        },
+        "runtime": {
+          "lib/net10.0/EPPlus.dll": {
+            "assemblyVersion": "8.5.4.0",
+            "fileVersion": "8.5.4.0"
+          }
+        }
+      },
+      "EPPlus.Interfaces/8.4.0": {
+        "runtime": {
+          "lib/net10.0/EPPlus.Interfaces.dll": {
+            "assemblyVersion": "8.4.0.0",
+            "fileVersion": "8.4.0.0"
+          }
+        }
+      },
+      "Guna.UI2.WinForms/2.0.4.7": {
+        "dependencies": {
+          "System.Management": "7.0.0"
+        },
+        "runtime": {
+          "lib/net7.0-windows7.0/Guna.UI2.dll": {
+            "assemblyVersion": "2.0.4.7",
+            "fileVersion": "2.0.4.7"
+          }
+        }
+      },
+      "Microsoft.Extensions.Configuration/10.0.7": {
+        "dependencies": {
+          "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+          "Microsoft.Extensions.Primitives": "10.0.7"
+        },
+        "runtime": {
+          "lib/net10.0/Microsoft.Extensions.Configuration.dll": {
+            "assemblyVersion": "10.0.0.0",
+            "fileVersion": "10.0.726.21808"
+          }
+        }
+      },
+      "Microsoft.Extensions.Configuration.Abstractions/10.0.7": {
+        "dependencies": {
+          "Microsoft.Extensions.Primitives": "10.0.7"
+        },
+        "runtime": {
+          "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll": {
+            "assemblyVersion": "10.0.0.0",
+            "fileVersion": "10.0.726.21808"
+          }
+        }
+      },
+      "Microsoft.Extensions.Configuration.FileExtensions/10.0.7": {
+        "dependencies": {
+          "Microsoft.Extensions.Configuration": "10.0.7",
+          "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+          "Microsoft.Extensions.FileProviders.Abstractions": "10.0.7",
+          "Microsoft.Extensions.FileProviders.Physical": "10.0.7",
+          "Microsoft.Extensions.Primitives": "10.0.7"
+        },
+        "runtime": {
+          "lib/net10.0/Microsoft.Extensions.Configuration.FileExtensions.dll": {
+            "assemblyVersion": "10.0.0.0",
+            "fileVersion": "10.0.726.21808"
+          }
+        }
+      },
+      "Microsoft.Extensions.Configuration.Json/10.0.7": {
+        "dependencies": {
+          "Microsoft.Extensions.Configuration": "10.0.7",
+          "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+          "Microsoft.Extensions.Configuration.FileExtensions": "10.0.7",
+          "Microsoft.Extensions.FileProviders.Abstractions": "10.0.7"
+        },
+        "runtime": {
+          "lib/net10.0/Microsoft.Extensions.Configuration.Json.dll": {
+            "assemblyVersion": "10.0.0.0",
+            "fileVersion": "10.0.726.21808"
+          }
+        }
+      },
+      "Microsoft.Extensions.DependencyInjection.Abstractions/10.0.0": {
+        "runtime": {
+          "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": {
+            "assemblyVersion": "10.0.0.0",
+            "fileVersion": "10.0.25.52411"
+          }
+        }
+      },
+      "Microsoft.Extensions.FileProviders.Abstractions/10.0.7": {
+        "dependencies": {
+          "Microsoft.Extensions.Primitives": "10.0.7"
+        },
+        "runtime": {
+          "lib/net10.0/Microsoft.Extensions.FileProviders.Abstractions.dll": {
+            "assemblyVersion": "10.0.0.0",
+            "fileVersion": "10.0.726.21808"
+          }
+        }
+      },
+      "Microsoft.Extensions.FileProviders.Physical/10.0.7": {
+        "dependencies": {
+          "Microsoft.Extensions.FileProviders.Abstractions": "10.0.7",
+          "Microsoft.Extensions.FileSystemGlobbing": "10.0.7",
+          "Microsoft.Extensions.Primitives": "10.0.7"
+        },
+        "runtime": {
+          "lib/net10.0/Microsoft.Extensions.FileProviders.Physical.dll": {
+            "assemblyVersion": "10.0.0.0",
+            "fileVersion": "10.0.726.21808"
+          }
+        }
+      },
+      "Microsoft.Extensions.FileSystemGlobbing/10.0.7": {
+        "runtime": {
+          "lib/net10.0/Microsoft.Extensions.FileSystemGlobbing.dll": {
+            "assemblyVersion": "10.0.0.0",
+            "fileVersion": "10.0.726.21808"
+          }
+        }
+      },
+      "Microsoft.Extensions.Logging.Abstractions/10.0.0": {
+        "dependencies": {
+          "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0"
+        },
+        "runtime": {
+          "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll": {
+            "assemblyVersion": "10.0.0.0",
+            "fileVersion": "10.0.25.52411"
+          }
+        }
+      },
+      "Microsoft.Extensions.Primitives/10.0.7": {
+        "runtime": {
+          "lib/net10.0/Microsoft.Extensions.Primitives.dll": {
+            "assemblyVersion": "10.0.0.0",
+            "fileVersion": "10.0.726.21808"
+          }
+        }
+      },
+      "Microsoft.IO.RecyclableMemoryStream/3.0.1": {
+        "runtime": {
+          "lib/net6.0/Microsoft.IO.RecyclableMemoryStream.dll": {
+            "assemblyVersion": "3.0.1.0",
+            "fileVersion": "3.0.1.0"
+          }
+        }
+      },
+      "Npgsql/10.0.2": {
+        "dependencies": {
+          "Microsoft.Extensions.Logging.Abstractions": "10.0.0"
+        },
+        "runtime": {
+          "lib/net10.0/Npgsql.dll": {
+            "assemblyVersion": "10.0.2.0",
+            "fileVersion": "10.0.2.0"
+          }
+        }
+      },
+      "OxyPlot.Core/2.2.0": {
+        "runtime": {
+          "lib/net8.0/OxyPlot.dll": {
+            "assemblyVersion": "2.2.0.0",
+            "fileVersion": "2.2.0.0"
+          }
+        }
+      },
+      "OxyPlot.WindowsForms/2.2.0": {
+        "dependencies": {
+          "OxyPlot.Core": "2.2.0"
+        },
+        "runtime": {
+          "lib/net8.0-windows7.0/OxyPlot.WindowsForms.dll": {
+            "assemblyVersion": "2.2.0.0",
+            "fileVersion": "2.2.0.0"
+          }
+        }
+      },
+      "System.Management/7.0.0": {
+        "runtime": {
+          "lib/net7.0/System.Management.dll": {
+            "assemblyVersion": "4.0.0.0",
+            "fileVersion": "7.0.22.51805"
+          }
+        },
+        "runtimeTargets": {
+          "runtimes/win/lib/net7.0/System.Management.dll": {
+            "rid": "win",
+            "assetType": "runtime",
+            "assemblyVersion": "4.0.0.0",
+            "fileVersion": "7.0.22.51805"
+          }
+        }
+      }
+    }
+  },
+  "libraries": {
+    "ProviderApp/1.0.0": {
+      "type": "project",
+      "serviceable": false,
+      "sha512": ""
+    },
+    "EPPlus/8.5.4": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-dHIYNqU4MDbkZPNyCF76bcQPSbx7/WhgDZw5hmDgGzRB1wn9+q/TMy4LC5gw00DamlqiWRnw4AVUXMdV1mGtOg==",
+      "path": "epplus/8.5.4",
+      "hashPath": "epplus.8.5.4.nupkg.sha512"
+    },
+    "EPPlus.Interfaces/8.4.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-QaEzDFG2vcQMPqUmoWv0vBTObtH8hffQmyDr2WkqRYF/nFSn2pkdBB4vudcd/EIv0N8M3P1fzzkPQgEWsFnUmA==",
+      "path": "epplus.interfaces/8.4.0",
+      "hashPath": "epplus.interfaces.8.4.0.nupkg.sha512"
+    },
+    "Guna.UI2.WinForms/2.0.4.7": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-8BdBYsfnntuICbFMHIZsd5morwURN8Ucsvq7wJeyLV1Z1ze6CAIxVKEirufgjGIiXfF0M0sCr9EVFloZSazWew==",
+      "path": "guna.ui2.winforms/2.0.4.7",
+      "hashPath": "guna.ui2.winforms.2.0.4.7.nupkg.sha512"
+    },
+    "Microsoft.Extensions.Configuration/10.0.7": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-wZbGh7J8R1vXN525O6d8dlcDTxhRTnd5MyW4LdfP5S0tSnTwTCseYSrq6g0Mxh7W9xn8P/2xPuf0D/m6k2dy2w==",
+      "path": "microsoft.extensions.configuration/10.0.7",
+      "hashPath": "microsoft.extensions.configuration.10.0.7.nupkg.sha512"
+    },
+    "Microsoft.Extensions.Configuration.Abstractions/10.0.7": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-t56nEgvECcyLPojZIUFWJknQQDAbgfTf9J+QMYJE1YYvVgz69vN6B/AKL8Grvj3Lcnp8kTpNqwmwFhb3YLJmtQ==",
+      "path": "microsoft.extensions.configuration.abstractions/10.0.7",
+      "hashPath": "microsoft.extensions.configuration.abstractions.10.0.7.nupkg.sha512"
+    },
+    "Microsoft.Extensions.Configuration.FileExtensions/10.0.7": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-qbZLvLsoTdArSloEnSxs21P781YUmwVmHc5NJPQD/ezAreQ7884z+6QfAZVKi86WAZtzx83jK2uC4itxOM44gQ==",
+      "path": "microsoft.extensions.configuration.fileextensions/10.0.7",
+      "hashPath": "microsoft.extensions.configuration.fileextensions.10.0.7.nupkg.sha512"
+    },
+    "Microsoft.Extensions.Configuration.Json/10.0.7": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-64dimvyyKk0dbUbrLg/YCv4ugJ4sVz2aXLwfvZwR1EC4tJqW9ru/oVRcXwoJRa2lQGXtYtlpk4maWOeIb48tQw==",
+      "path": "microsoft.extensions.configuration.json/10.0.7",
+      "hashPath": "microsoft.extensions.configuration.json.10.0.7.nupkg.sha512"
+    },
+    "Microsoft.Extensions.DependencyInjection.Abstractions/10.0.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-L3AdmZ1WOK4XXT5YFPEwyt0ep6l8lGIPs7F5OOBZc77Zqeo01Of7XXICy47628sdVl0v/owxYJTe86DTgFwKCA==",
+      "path": "microsoft.extensions.dependencyinjection.abstractions/10.0.0",
+      "hashPath": "microsoft.extensions.dependencyinjection.abstractions.10.0.0.nupkg.sha512"
+    },
+    "Microsoft.Extensions.FileProviders.Abstractions/10.0.7": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-teioDgVpi8L186wUfrXQV1YuBt6lCSPmFZiMZo53+FZxHFjOV+f4GXo4LXgJ273Mku9//AdXWVjk9J7eJP6inw==",
+      "path": "microsoft.extensions.fileproviders.abstractions/10.0.7",
+      "hashPath": "microsoft.extensions.fileproviders.abstractions.10.0.7.nupkg.sha512"
+    },
+    "Microsoft.Extensions.FileProviders.Physical/10.0.7": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-zhgWg/i0ECj5v0jLFBSZHplvc5ygCI91DR4nne+BP4XAKF5ycz0pEKnFiTw8C1jCABJEZsnBZh6pXAvn71kFmw==",
+      "path": "microsoft.extensions.fileproviders.physical/10.0.7",
+      "hashPath": "microsoft.extensions.fileproviders.physical.10.0.7.nupkg.sha512"
+    },
+    "Microsoft.Extensions.FileSystemGlobbing/10.0.7": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-NTUspqB+vH9g4wAD6KPOBx01xqYuKXR/cHXm449zpbq1GqfjdAxBmg7eJXrNsPw7SKwIdT2cJ05GxYVvc+lvsA==",
+      "path": "microsoft.extensions.filesystemglobbing/10.0.7",
+      "hashPath": "microsoft.extensions.filesystemglobbing.10.0.7.nupkg.sha512"
+    },
+    "Microsoft.Extensions.Logging.Abstractions/10.0.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-FU/IfjDfwaMuKr414SSQNTIti/69bHEMb+QKrskRb26oVqpx3lNFXMjs/RC9ZUuhBhcwDM2BwOgoMw+PZ+beqQ==",
+      "path": "microsoft.extensions.logging.abstractions/10.0.0",
+      "hashPath": "microsoft.extensions.logging.abstractions.10.0.0.nupkg.sha512"
+    },
+    "Microsoft.Extensions.Primitives/10.0.7": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-D5M0Jr551iTgwkZMN9rm0pSkgNLj5quUWQUmQPMZh7k/bnvZTnXRGfE2KuvXf1EEjt/ofD9yw9IumpgdP9QCnw==",
+      "path": "microsoft.extensions.primitives/10.0.7",
+      "hashPath": "microsoft.extensions.primitives.10.0.7.nupkg.sha512"
+    },
+    "Microsoft.IO.RecyclableMemoryStream/3.0.1": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==",
+      "path": "microsoft.io.recyclablememorystream/3.0.1",
+      "hashPath": "microsoft.io.recyclablememorystream.3.0.1.nupkg.sha512"
+    },
+    "Npgsql/10.0.2": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-q5RfBI+wywJSFUNDE1L4ZbHEHCFTblo8Uf6A6oe4feOUFYiUQXyAf9GBh5qEZpvJaHiEbpBPkQumjEhXCJxdrg==",
+      "path": "npgsql/10.0.2",
+      "hashPath": "npgsql.10.0.2.nupkg.sha512"
+    },
+    "OxyPlot.Core/2.2.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-QhXNdXR5FPpro/VoLx3BOp6AhQo7YrbfmWEZ9cgY+pnYM7RYORZjnu+aDMA8ka9A1r8hLkX//NbCPZNUv+l8qA==",
+      "path": "oxyplot.core/2.2.0",
+      "hashPath": "oxyplot.core.2.2.0.nupkg.sha512"
+    },
+    "OxyPlot.WindowsForms/2.2.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-jkveaQ7d3PanUP6OKaZ+IXFPuwpyabifPQiQZ40pQls8Tuedf0mUZ8JcLkMltK5Jkj/JRwGP2cNn1oJ8NwaV3A==",
+      "path": "oxyplot.windowsforms/2.2.0",
+      "hashPath": "oxyplot.windowsforms.2.2.0.nupkg.sha512"
+    },
+    "System.Management/7.0.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-A4jed4QUviDOm7fJNKAJObEAEkEUXmkGL/w0iyCYTzrl1rezTj8LGFHfsVst4Vb9JwFcTpboiDrvdST48avBpw==",
+      "path": "system.management/7.0.0",
+      "hashPath": "system.management.7.0.0.nupkg.sha512"
+    }
+  }
+}

BIN
ProviderApp/bin/Debug/net10.0-windows/ProviderApp.dll


BIN
ProviderApp/bin/Debug/net10.0-windows/ProviderApp.exe


BIN
ProviderApp/bin/Debug/net10.0-windows/ProviderApp.pdb


+ 19 - 0
ProviderApp/bin/Debug/net10.0-windows/ProviderApp.runtimeconfig.json

@@ -0,0 +1,19 @@
+{
+  "runtimeOptions": {
+    "tfm": "net10.0",
+    "frameworks": [
+      {
+        "name": "Microsoft.NETCore.App",
+        "version": "10.0.0"
+      },
+      {
+        "name": "Microsoft.WindowsDesktop.App",
+        "version": "10.0.0"
+      }
+    ],
+    "configProperties": {
+      "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false,
+      "CSWINRT_USE_WINDOWS_UI_XAML_PROJECTIONS": false
+    }
+  }
+}

BIN
ProviderApp/bin/Debug/net10.0-windows/System.Management.dll


BIN
ProviderApp/bin/Debug/net10.0-windows/runtimes/win/lib/net7.0/System.Management.dll


BIN
ProviderApp/bin/Release/net10.0-windows/EPPlus.Interfaces.dll


BIN
ProviderApp/bin/Release/net10.0-windows/EPPlus.dll


BIN
ProviderApp/bin/Release/net10.0-windows/Guna.UI2.dll


BIN
ProviderApp/bin/Release/net10.0-windows/Microsoft.Extensions.Configuration.Abstractions.dll


BIN
ProviderApp/bin/Release/net10.0-windows/Microsoft.Extensions.Configuration.FileExtensions.dll


BIN
ProviderApp/bin/Release/net10.0-windows/Microsoft.Extensions.Configuration.Json.dll


BIN
ProviderApp/bin/Release/net10.0-windows/Microsoft.Extensions.Configuration.dll


BIN
ProviderApp/bin/Release/net10.0-windows/Microsoft.Extensions.DependencyInjection.Abstractions.dll


BIN
ProviderApp/bin/Release/net10.0-windows/Microsoft.Extensions.FileProviders.Abstractions.dll


BIN
ProviderApp/bin/Release/net10.0-windows/Microsoft.Extensions.FileProviders.Physical.dll


BIN
ProviderApp/bin/Release/net10.0-windows/Microsoft.Extensions.FileSystemGlobbing.dll


BIN
ProviderApp/bin/Release/net10.0-windows/Microsoft.Extensions.Logging.Abstractions.dll


BIN
ProviderApp/bin/Release/net10.0-windows/Microsoft.Extensions.Primitives.dll


BIN
ProviderApp/bin/Release/net10.0-windows/Microsoft.IO.RecyclableMemoryStream.dll


BIN
ProviderApp/bin/Release/net10.0-windows/Npgsql.dll


BIN
ProviderApp/bin/Release/net10.0-windows/Output/mysetup.exe


BIN
ProviderApp/bin/Release/net10.0-windows/OxyPlot.WindowsForms.dll


BIN
ProviderApp/bin/Release/net10.0-windows/OxyPlot.dll


+ 352 - 0
ProviderApp/bin/Release/net10.0-windows/ProviderApp.deps.json

@@ -0,0 +1,352 @@
+{
+  "runtimeTarget": {
+    "name": ".NETCoreApp,Version=v10.0",
+    "signature": ""
+  },
+  "compilationOptions": {},
+  "targets": {
+    ".NETCoreApp,Version=v10.0": {
+      "ProviderApp/1.0.0": {
+        "dependencies": {
+          "EPPlus": "8.5.4",
+          "Guna.UI2.WinForms": "2.0.4.7",
+          "Npgsql": "10.0.2",
+          "OxyPlot.WindowsForms": "2.2.0"
+        },
+        "runtime": {
+          "ProviderApp.dll": {}
+        }
+      },
+      "EPPlus/8.5.4": {
+        "dependencies": {
+          "EPPlus.Interfaces": "8.4.0",
+          "Microsoft.Extensions.Configuration.Json": "10.0.7",
+          "Microsoft.IO.RecyclableMemoryStream": "3.0.1"
+        },
+        "runtime": {
+          "lib/net10.0/EPPlus.dll": {
+            "assemblyVersion": "8.5.4.0",
+            "fileVersion": "8.5.4.0"
+          }
+        }
+      },
+      "EPPlus.Interfaces/8.4.0": {
+        "runtime": {
+          "lib/net10.0/EPPlus.Interfaces.dll": {
+            "assemblyVersion": "8.4.0.0",
+            "fileVersion": "8.4.0.0"
+          }
+        }
+      },
+      "Guna.UI2.WinForms/2.0.4.7": {
+        "dependencies": {
+          "System.Management": "7.0.0"
+        },
+        "runtime": {
+          "lib/net7.0-windows7.0/Guna.UI2.dll": {
+            "assemblyVersion": "2.0.4.7",
+            "fileVersion": "2.0.4.7"
+          }
+        }
+      },
+      "Microsoft.Extensions.Configuration/10.0.7": {
+        "dependencies": {
+          "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+          "Microsoft.Extensions.Primitives": "10.0.7"
+        },
+        "runtime": {
+          "lib/net10.0/Microsoft.Extensions.Configuration.dll": {
+            "assemblyVersion": "10.0.0.0",
+            "fileVersion": "10.0.726.21808"
+          }
+        }
+      },
+      "Microsoft.Extensions.Configuration.Abstractions/10.0.7": {
+        "dependencies": {
+          "Microsoft.Extensions.Primitives": "10.0.7"
+        },
+        "runtime": {
+          "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll": {
+            "assemblyVersion": "10.0.0.0",
+            "fileVersion": "10.0.726.21808"
+          }
+        }
+      },
+      "Microsoft.Extensions.Configuration.FileExtensions/10.0.7": {
+        "dependencies": {
+          "Microsoft.Extensions.Configuration": "10.0.7",
+          "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+          "Microsoft.Extensions.FileProviders.Abstractions": "10.0.7",
+          "Microsoft.Extensions.FileProviders.Physical": "10.0.7",
+          "Microsoft.Extensions.Primitives": "10.0.7"
+        },
+        "runtime": {
+          "lib/net10.0/Microsoft.Extensions.Configuration.FileExtensions.dll": {
+            "assemblyVersion": "10.0.0.0",
+            "fileVersion": "10.0.726.21808"
+          }
+        }
+      },
+      "Microsoft.Extensions.Configuration.Json/10.0.7": {
+        "dependencies": {
+          "Microsoft.Extensions.Configuration": "10.0.7",
+          "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+          "Microsoft.Extensions.Configuration.FileExtensions": "10.0.7",
+          "Microsoft.Extensions.FileProviders.Abstractions": "10.0.7"
+        },
+        "runtime": {
+          "lib/net10.0/Microsoft.Extensions.Configuration.Json.dll": {
+            "assemblyVersion": "10.0.0.0",
+            "fileVersion": "10.0.726.21808"
+          }
+        }
+      },
+      "Microsoft.Extensions.DependencyInjection.Abstractions/10.0.0": {
+        "runtime": {
+          "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": {
+            "assemblyVersion": "10.0.0.0",
+            "fileVersion": "10.0.25.52411"
+          }
+        }
+      },
+      "Microsoft.Extensions.FileProviders.Abstractions/10.0.7": {
+        "dependencies": {
+          "Microsoft.Extensions.Primitives": "10.0.7"
+        },
+        "runtime": {
+          "lib/net10.0/Microsoft.Extensions.FileProviders.Abstractions.dll": {
+            "assemblyVersion": "10.0.0.0",
+            "fileVersion": "10.0.726.21808"
+          }
+        }
+      },
+      "Microsoft.Extensions.FileProviders.Physical/10.0.7": {
+        "dependencies": {
+          "Microsoft.Extensions.FileProviders.Abstractions": "10.0.7",
+          "Microsoft.Extensions.FileSystemGlobbing": "10.0.7",
+          "Microsoft.Extensions.Primitives": "10.0.7"
+        },
+        "runtime": {
+          "lib/net10.0/Microsoft.Extensions.FileProviders.Physical.dll": {
+            "assemblyVersion": "10.0.0.0",
+            "fileVersion": "10.0.726.21808"
+          }
+        }
+      },
+      "Microsoft.Extensions.FileSystemGlobbing/10.0.7": {
+        "runtime": {
+          "lib/net10.0/Microsoft.Extensions.FileSystemGlobbing.dll": {
+            "assemblyVersion": "10.0.0.0",
+            "fileVersion": "10.0.726.21808"
+          }
+        }
+      },
+      "Microsoft.Extensions.Logging.Abstractions/10.0.0": {
+        "dependencies": {
+          "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0"
+        },
+        "runtime": {
+          "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll": {
+            "assemblyVersion": "10.0.0.0",
+            "fileVersion": "10.0.25.52411"
+          }
+        }
+      },
+      "Microsoft.Extensions.Primitives/10.0.7": {
+        "runtime": {
+          "lib/net10.0/Microsoft.Extensions.Primitives.dll": {
+            "assemblyVersion": "10.0.0.0",
+            "fileVersion": "10.0.726.21808"
+          }
+        }
+      },
+      "Microsoft.IO.RecyclableMemoryStream/3.0.1": {
+        "runtime": {
+          "lib/net6.0/Microsoft.IO.RecyclableMemoryStream.dll": {
+            "assemblyVersion": "3.0.1.0",
+            "fileVersion": "3.0.1.0"
+          }
+        }
+      },
+      "Npgsql/10.0.2": {
+        "dependencies": {
+          "Microsoft.Extensions.Logging.Abstractions": "10.0.0"
+        },
+        "runtime": {
+          "lib/net10.0/Npgsql.dll": {
+            "assemblyVersion": "10.0.2.0",
+            "fileVersion": "10.0.2.0"
+          }
+        }
+      },
+      "OxyPlot.Core/2.2.0": {
+        "runtime": {
+          "lib/net8.0/OxyPlot.dll": {
+            "assemblyVersion": "2.2.0.0",
+            "fileVersion": "2.2.0.0"
+          }
+        }
+      },
+      "OxyPlot.WindowsForms/2.2.0": {
+        "dependencies": {
+          "OxyPlot.Core": "2.2.0"
+        },
+        "runtime": {
+          "lib/net8.0-windows7.0/OxyPlot.WindowsForms.dll": {
+            "assemblyVersion": "2.2.0.0",
+            "fileVersion": "2.2.0.0"
+          }
+        }
+      },
+      "System.Management/7.0.0": {
+        "runtime": {
+          "lib/net7.0/System.Management.dll": {
+            "assemblyVersion": "4.0.0.0",
+            "fileVersion": "7.0.22.51805"
+          }
+        },
+        "runtimeTargets": {
+          "runtimes/win/lib/net7.0/System.Management.dll": {
+            "rid": "win",
+            "assetType": "runtime",
+            "assemblyVersion": "4.0.0.0",
+            "fileVersion": "7.0.22.51805"
+          }
+        }
+      }
+    }
+  },
+  "libraries": {
+    "ProviderApp/1.0.0": {
+      "type": "project",
+      "serviceable": false,
+      "sha512": ""
+    },
+    "EPPlus/8.5.4": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-dHIYNqU4MDbkZPNyCF76bcQPSbx7/WhgDZw5hmDgGzRB1wn9+q/TMy4LC5gw00DamlqiWRnw4AVUXMdV1mGtOg==",
+      "path": "epplus/8.5.4",
+      "hashPath": "epplus.8.5.4.nupkg.sha512"
+    },
+    "EPPlus.Interfaces/8.4.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-QaEzDFG2vcQMPqUmoWv0vBTObtH8hffQmyDr2WkqRYF/nFSn2pkdBB4vudcd/EIv0N8M3P1fzzkPQgEWsFnUmA==",
+      "path": "epplus.interfaces/8.4.0",
+      "hashPath": "epplus.interfaces.8.4.0.nupkg.sha512"
+    },
+    "Guna.UI2.WinForms/2.0.4.7": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-8BdBYsfnntuICbFMHIZsd5morwURN8Ucsvq7wJeyLV1Z1ze6CAIxVKEirufgjGIiXfF0M0sCr9EVFloZSazWew==",
+      "path": "guna.ui2.winforms/2.0.4.7",
+      "hashPath": "guna.ui2.winforms.2.0.4.7.nupkg.sha512"
+    },
+    "Microsoft.Extensions.Configuration/10.0.7": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-wZbGh7J8R1vXN525O6d8dlcDTxhRTnd5MyW4LdfP5S0tSnTwTCseYSrq6g0Mxh7W9xn8P/2xPuf0D/m6k2dy2w==",
+      "path": "microsoft.extensions.configuration/10.0.7",
+      "hashPath": "microsoft.extensions.configuration.10.0.7.nupkg.sha512"
+    },
+    "Microsoft.Extensions.Configuration.Abstractions/10.0.7": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-t56nEgvECcyLPojZIUFWJknQQDAbgfTf9J+QMYJE1YYvVgz69vN6B/AKL8Grvj3Lcnp8kTpNqwmwFhb3YLJmtQ==",
+      "path": "microsoft.extensions.configuration.abstractions/10.0.7",
+      "hashPath": "microsoft.extensions.configuration.abstractions.10.0.7.nupkg.sha512"
+    },
+    "Microsoft.Extensions.Configuration.FileExtensions/10.0.7": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-qbZLvLsoTdArSloEnSxs21P781YUmwVmHc5NJPQD/ezAreQ7884z+6QfAZVKi86WAZtzx83jK2uC4itxOM44gQ==",
+      "path": "microsoft.extensions.configuration.fileextensions/10.0.7",
+      "hashPath": "microsoft.extensions.configuration.fileextensions.10.0.7.nupkg.sha512"
+    },
+    "Microsoft.Extensions.Configuration.Json/10.0.7": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-64dimvyyKk0dbUbrLg/YCv4ugJ4sVz2aXLwfvZwR1EC4tJqW9ru/oVRcXwoJRa2lQGXtYtlpk4maWOeIb48tQw==",
+      "path": "microsoft.extensions.configuration.json/10.0.7",
+      "hashPath": "microsoft.extensions.configuration.json.10.0.7.nupkg.sha512"
+    },
+    "Microsoft.Extensions.DependencyInjection.Abstractions/10.0.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-L3AdmZ1WOK4XXT5YFPEwyt0ep6l8lGIPs7F5OOBZc77Zqeo01Of7XXICy47628sdVl0v/owxYJTe86DTgFwKCA==",
+      "path": "microsoft.extensions.dependencyinjection.abstractions/10.0.0",
+      "hashPath": "microsoft.extensions.dependencyinjection.abstractions.10.0.0.nupkg.sha512"
+    },
+    "Microsoft.Extensions.FileProviders.Abstractions/10.0.7": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-teioDgVpi8L186wUfrXQV1YuBt6lCSPmFZiMZo53+FZxHFjOV+f4GXo4LXgJ273Mku9//AdXWVjk9J7eJP6inw==",
+      "path": "microsoft.extensions.fileproviders.abstractions/10.0.7",
+      "hashPath": "microsoft.extensions.fileproviders.abstractions.10.0.7.nupkg.sha512"
+    },
+    "Microsoft.Extensions.FileProviders.Physical/10.0.7": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-zhgWg/i0ECj5v0jLFBSZHplvc5ygCI91DR4nne+BP4XAKF5ycz0pEKnFiTw8C1jCABJEZsnBZh6pXAvn71kFmw==",
+      "path": "microsoft.extensions.fileproviders.physical/10.0.7",
+      "hashPath": "microsoft.extensions.fileproviders.physical.10.0.7.nupkg.sha512"
+    },
+    "Microsoft.Extensions.FileSystemGlobbing/10.0.7": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-NTUspqB+vH9g4wAD6KPOBx01xqYuKXR/cHXm449zpbq1GqfjdAxBmg7eJXrNsPw7SKwIdT2cJ05GxYVvc+lvsA==",
+      "path": "microsoft.extensions.filesystemglobbing/10.0.7",
+      "hashPath": "microsoft.extensions.filesystemglobbing.10.0.7.nupkg.sha512"
+    },
+    "Microsoft.Extensions.Logging.Abstractions/10.0.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-FU/IfjDfwaMuKr414SSQNTIti/69bHEMb+QKrskRb26oVqpx3lNFXMjs/RC9ZUuhBhcwDM2BwOgoMw+PZ+beqQ==",
+      "path": "microsoft.extensions.logging.abstractions/10.0.0",
+      "hashPath": "microsoft.extensions.logging.abstractions.10.0.0.nupkg.sha512"
+    },
+    "Microsoft.Extensions.Primitives/10.0.7": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-D5M0Jr551iTgwkZMN9rm0pSkgNLj5quUWQUmQPMZh7k/bnvZTnXRGfE2KuvXf1EEjt/ofD9yw9IumpgdP9QCnw==",
+      "path": "microsoft.extensions.primitives/10.0.7",
+      "hashPath": "microsoft.extensions.primitives.10.0.7.nupkg.sha512"
+    },
+    "Microsoft.IO.RecyclableMemoryStream/3.0.1": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==",
+      "path": "microsoft.io.recyclablememorystream/3.0.1",
+      "hashPath": "microsoft.io.recyclablememorystream.3.0.1.nupkg.sha512"
+    },
+    "Npgsql/10.0.2": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-q5RfBI+wywJSFUNDE1L4ZbHEHCFTblo8Uf6A6oe4feOUFYiUQXyAf9GBh5qEZpvJaHiEbpBPkQumjEhXCJxdrg==",
+      "path": "npgsql/10.0.2",
+      "hashPath": "npgsql.10.0.2.nupkg.sha512"
+    },
+    "OxyPlot.Core/2.2.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-QhXNdXR5FPpro/VoLx3BOp6AhQo7YrbfmWEZ9cgY+pnYM7RYORZjnu+aDMA8ka9A1r8hLkX//NbCPZNUv+l8qA==",
+      "path": "oxyplot.core/2.2.0",
+      "hashPath": "oxyplot.core.2.2.0.nupkg.sha512"
+    },
+    "OxyPlot.WindowsForms/2.2.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-jkveaQ7d3PanUP6OKaZ+IXFPuwpyabifPQiQZ40pQls8Tuedf0mUZ8JcLkMltK5Jkj/JRwGP2cNn1oJ8NwaV3A==",
+      "path": "oxyplot.windowsforms/2.2.0",
+      "hashPath": "oxyplot.windowsforms.2.2.0.nupkg.sha512"
+    },
+    "System.Management/7.0.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-A4jed4QUviDOm7fJNKAJObEAEkEUXmkGL/w0iyCYTzrl1rezTj8LGFHfsVst4Vb9JwFcTpboiDrvdST48avBpw==",
+      "path": "system.management/7.0.0",
+      "hashPath": "system.management.7.0.0.nupkg.sha512"
+    }
+  }
+}

BIN
ProviderApp/bin/Release/net10.0-windows/ProviderApp.dll


BIN
ProviderApp/bin/Release/net10.0-windows/ProviderApp.exe


BIN
ProviderApp/bin/Release/net10.0-windows/ProviderApp.pdb


+ 20 - 0
ProviderApp/bin/Release/net10.0-windows/ProviderApp.runtimeconfig.json

@@ -0,0 +1,20 @@
+{
+  "runtimeOptions": {
+    "tfm": "net10.0",
+    "frameworks": [
+      {
+        "name": "Microsoft.NETCore.App",
+        "version": "10.0.0"
+      },
+      {
+        "name": "Microsoft.WindowsDesktop.App",
+        "version": "10.0.0"
+      }
+    ],
+    "configProperties": {
+      "System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
+      "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false,
+      "CSWINRT_USE_WINDOWS_UI_XAML_PROJECTIONS": false
+    }
+  }
+}

+ 67 - 0
ProviderApp/bin/Release/net10.0-windows/Setup.iss

@@ -0,0 +1,67 @@
+#define MyAppName "ProviderApp"
+#define MyAppVersion "1.0"
+#define MyAppPublisher "My Company, Inc."
+#define MyAppURL "https://www.example.com/"
+#define MyAppExeName "ProviderApp.exe"
+#define MyAppAssocName MyAppName + " File"
+#define MyAppAssocExt ".myp"
+#define MyAppAssocKey StringChange(MyAppAssocName, " ", "") + MyAppAssocExt
+[Setup]
+AppId={{12899C7D-8C57-49AF-BF8E-448E533BB298}
+AppName={#MyAppName}
+AppVersion={#MyAppVersion}
+AppPublisher={#MyAppPublisher}
+AppPublisherURL={#MyAppURL}
+AppSupportURL={#MyAppURL}
+AppUpdatesURL={#MyAppURL}
+DefaultDirName={autopf}\{#MyAppName}
+UninstallDisplayIcon={app}\{#MyAppExeName}
+ArchitecturesAllowed=x64compatible
+ArchitecturesInstallIn64BitMode=x64compatible
+ChangesAssociations=yes
+DisableProgramGroupPage=yes
+OutputBaseFilename=mysetup
+SolidCompression=yes
+WizardStyle=modern dynamic
+[Languages]
+Name: "english"; MessagesFile: "compiler:Default.isl"
+[Tasks]
+Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
+[Files]
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\EPPlus.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\EPPlus.Interfaces.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\Guna.UI2.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\Microsoft.Extensions.Configuration.Abstractions.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\Microsoft.Extensions.Configuration.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\Microsoft.Extensions.Configuration.FileExtensions.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\Microsoft.Extensions.Configuration.Json.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\Microsoft.Extensions.DependencyInjection.Abstractions.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\Microsoft.Extensions.FileProviders.Abstractions.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\Microsoft.Extensions.FileProviders.Physical.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\Microsoft.Extensions.FileSystemGlobbing.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\Microsoft.Extensions.Logging.Abstractions.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\Microsoft.Extensions.Primitives.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\Microsoft.IO.RecyclableMemoryStream.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\Npgsql.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\OxyPlot.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\OxyPlot.WindowsForms.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\ProviderApp.deps.json"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\ProviderApp.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\ProviderApp.exe"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\ProviderApp.pdb"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\ProviderApp.runtimeconfig.json"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\System.Management.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\user\source\repos\ProviderApp\ProviderApp\bin\Release\net10.0-windows\runtimes\*"; DestDir: "{app}\runtimes"; Flags: ignoreversion recursesubdirs createallsubdirs
+[Registry]
+Root: HKA; Subkey: "Software\Classes\{#MyAppAssocExt}\OpenWithProgids"; ValueType: string; ValueName: "{#MyAppAssocKey}"; ValueData: ""; Flags: uninsdeletevalue
+Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}"; ValueType: string; ValueName: ""; ValueData: "{#MyAppAssocName}"; Flags: uninsdeletekey
+Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#MyAppExeName},0"
+Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1"""
+
+[Icons]
+Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
+Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
+
+[Run]
+Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
+

BIN
ProviderApp/bin/Release/net10.0-windows/System.Management.dll


BIN
ProviderApp/bin/Release/net10.0-windows/runtimes/win/lib/net7.0/System.Management.dll


BIN
ProviderApp/bin/Release/net10.0-windows/win-x64/Accessibility.dll


BIN
ProviderApp/bin/Release/net10.0-windows/win-x64/EPPlus.Interfaces.dll


BIN
ProviderApp/bin/Release/net10.0-windows/win-x64/EPPlus.dll


BIN
ProviderApp/bin/Release/net10.0-windows/win-x64/Guna.UI2.dll


BIN
ProviderApp/bin/Release/net10.0-windows/win-x64/Microsoft.CSharp.dll


BIN
ProviderApp/bin/Release/net10.0-windows/win-x64/Microsoft.DiaSymReader.Native.amd64.dll


BIN
ProviderApp/bin/Release/net10.0-windows/win-x64/Microsoft.Extensions.Configuration.Abstractions.dll


BIN
ProviderApp/bin/Release/net10.0-windows/win-x64/Microsoft.Extensions.Configuration.FileExtensions.dll


BIN
ProviderApp/bin/Release/net10.0-windows/win-x64/Microsoft.Extensions.Configuration.Json.dll


BIN
ProviderApp/bin/Release/net10.0-windows/win-x64/Microsoft.Extensions.Configuration.dll


BIN
ProviderApp/bin/Release/net10.0-windows/win-x64/Microsoft.Extensions.DependencyInjection.Abstractions.dll


BIN
ProviderApp/bin/Release/net10.0-windows/win-x64/Microsoft.Extensions.FileProviders.Abstractions.dll


この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません