Răsfoiți Sursa

добавление проекта и инсталлера

icp0 1 săptămână în urmă
comite
589b386ff2
72 a modificat fișierele cu 8952 adăugiri și 0 ștergeri
  1. 14 0
      .gitignore
  2. 30 0
      App.config
  3. 18 0
      Client.cs
  4. 201 0
      ClientEditForm.Designer.cs
  5. 83 0
      ClientEditForm.cs
  6. 120 0
      ClientEditForm.resx
  7. 148 0
      ClientForm.Designer.cs
  8. 108 0
      ClientForm.cs
  9. 120 0
      ClientForm.resx
  10. 120 0
      ClientRepository.cs
  11. 40 0
      Database.cs
  12. 15 0
      Dish.cs
  13. 218 0
      DishEditForm.Designer.cs
  14. 111 0
      DishEditForm.cs
  15. 120 0
      DishEditForm.resx
  16. 172 0
      DishForm.Designer.cs
  17. 119 0
      DishForm.cs
  18. 120 0
      DishForm.resx
  19. 161 0
      DishRepository.cs
  20. 21 0
      Employee.cs
  21. 256 0
      EmployeeEditForm.Designer.cs
  22. 110 0
      EmployeeEditForm.cs
  23. 120 0
      EmployeeEditForm.resx
  24. 172 0
      EmployeeForm.Designer.cs
  25. 131 0
      EmployeeForm.cs
  26. 120 0
      EmployeeForm.resx
  27. 128 0
      EmployeeRepository.cs
  28. 105 0
      LoginForm.Designer.cs
  29. 58 0
      LoginForm.cs
  30. 120 0
      LoginForm.resx
  31. 135 0
      MainForm.Designer.cs
  32. 41 0
      MainForm.cs
  33. 126 0
      MainForm.resx
  34. 155 0
      OPERATOR_GUIDE.md
  35. 34 0
      Order.cs
  36. 170 0
      OrderEditForm.Designer.cs
  37. 96 0
      OrderEditForm.cs
  38. 120 0
      OrderEditForm.resx
  39. 468 0
      OrderForm.Designer.cs
  40. 250 0
      OrderForm.cs
  41. 120 0
      OrderForm.resx
  42. 4 0
      OrderItem.cs
  43. 173 0
      OrderItemEditForm.Designer.cs
  44. 83 0
      OrderItemEditForm.cs
  45. 120 0
      OrderItemEditForm.resx
  46. 190 0
      OrderRepository.cs
  47. 210 0
      PaymentForm.Designer.cs
  48. 110 0
      PaymentForm.cs
  49. 120 0
      PaymentForm.resx
  50. 19 0
      Program.cs
  51. 33 0
      Properties/AssemblyInfo.cs
  52. 71 0
      Properties/Resources.Designer.cs
  53. 117 0
      Properties/Resources.resx
  54. 30 0
      Properties/Settings.Designer.cs
  55. 7 0
      Properties/Settings.settings
  56. 106 0
      README.md
  57. 508 0
      ReportForm.Designer.cs
  58. 146 0
      ReportForm.cs
  59. 120 0
      ReportForm.resx
  60. 115 0
      ReportRepository.cs
  61. 21 0
      Reservation.cs
  62. 267 0
      ReservationEditForm.Designer.cs
  63. 133 0
      ReservationEditForm.cs
  64. 120 0
      ReservationEditForm.resx
  65. 261 0
      ReservationForm.Designer.cs
  66. 134 0
      ReservationForm.cs
  67. 120 0
      ReservationForm.resx
  68. 135 0
      ReservationRepository.cs
  69. 254 0
      RestaurantApp.csproj
  70. 118 0
      RestaurantAppSetup.iss
  71. BIN
      installer-output/RestaurantAppSetup.exe
  72. 13 0
      packages.config

+ 14 - 0
.gitignore

@@ -0,0 +1,14 @@
+.vs/
+bin/
+obj/
+packages/
+
+*.user
+*.suo
+*.cache
+*.log
+*.tmp
+
+*.pdb
+*.ilk
+*.vshost.*

+ 30 - 0
App.config

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

+ 18 - 0
Client.cs

@@ -0,0 +1,18 @@
+using System;
+
+namespace RestaurantApp.Models
+{
+    public class Client
+    {
+        public int ClientId { get; set; }
+        public string FirstName { get; set; }
+        public string LastName { get; set; }
+        public string Phone { get; set; }
+        public string Email { get; set; }
+        public DateTime? BirthDate { get; set; }
+        public int BonusPoints { get; set; }
+        public DateTime RegisteredAt { get; set; }
+
+        public string FullName => $"{LastName} {FirstName}";
+    }
+}

+ 201 - 0
ClientEditForm.Designer.cs

@@ -0,0 +1,201 @@
+namespace RestaurantApp
+{
+    partial class ClientEditForm
+    {
+        private System.ComponentModel.IContainer components = null;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.lblFirstName = new System.Windows.Forms.Label();
+            this.txtFirstName = new System.Windows.Forms.TextBox();
+            this.lblLastName = new System.Windows.Forms.Label();
+            this.txtLastName = new System.Windows.Forms.TextBox();
+            this.lblPhone = new System.Windows.Forms.Label();
+            this.txtPhone = new System.Windows.Forms.TextBox();
+            this.lblEmail = new System.Windows.Forms.Label();
+            this.txtEmail = new System.Windows.Forms.TextBox();
+            this.lblBirthDate = new System.Windows.Forms.Label();
+            this.dtpBirthDate = new System.Windows.Forms.DateTimePicker();
+            this.lblBonusPoints = new System.Windows.Forms.Label();
+            this.numBonusPoints = new System.Windows.Forms.NumericUpDown();
+            this.btnSave = new System.Windows.Forms.Button();
+            this.btnCancel = new System.Windows.Forms.Button();
+            ((System.ComponentModel.ISupportInitialize)(this.numBonusPoints)).BeginInit();
+            this.SuspendLayout();
+            // 
+            // lblFirstName
+            // 
+            this.lblFirstName.AutoSize = true;
+            this.lblFirstName.Location = new System.Drawing.Point(30, 30);
+            this.lblFirstName.Name = "lblFirstName";
+            this.lblFirstName.Size = new System.Drawing.Size(32, 13);
+            this.lblFirstName.TabIndex = 0;
+            this.lblFirstName.Text = "Имя:";
+            // 
+            // txtFirstName
+            // 
+            this.txtFirstName.Location = new System.Drawing.Point(120, 27);
+            this.txtFirstName.Name = "txtFirstName";
+            this.txtFirstName.Size = new System.Drawing.Size(200, 20);
+            this.txtFirstName.TabIndex = 1;
+            // 
+            // lblLastName
+            // 
+            this.lblLastName.AutoSize = true;
+            this.lblLastName.Location = new System.Drawing.Point(30, 60);
+            this.lblLastName.Name = "lblLastName";
+            this.lblLastName.Size = new System.Drawing.Size(59, 13);
+            this.lblLastName.TabIndex = 2;
+            this.lblLastName.Text = "Фамилия:";
+            // 
+            // txtLastName
+            // 
+            this.txtLastName.Location = new System.Drawing.Point(120, 57);
+            this.txtLastName.Name = "txtLastName";
+            this.txtLastName.Size = new System.Drawing.Size(200, 20);
+            this.txtLastName.TabIndex = 3;
+            // 
+            // lblPhone
+            // 
+            this.lblPhone.AutoSize = true;
+            this.lblPhone.Location = new System.Drawing.Point(30, 90);
+            this.lblPhone.Name = "lblPhone";
+            this.lblPhone.Size = new System.Drawing.Size(55, 13);
+            this.lblPhone.TabIndex = 4;
+            this.lblPhone.Text = "Телефон:";
+            // 
+            // txtPhone
+            // 
+            this.txtPhone.Location = new System.Drawing.Point(120, 87);
+            this.txtPhone.Name = "txtPhone";
+            this.txtPhone.Size = new System.Drawing.Size(200, 20);
+            this.txtPhone.TabIndex = 5;
+            // 
+            // lblEmail
+            // 
+            this.lblEmail.AutoSize = true;
+            this.lblEmail.Location = new System.Drawing.Point(30, 120);
+            this.lblEmail.Name = "lblEmail";
+            this.lblEmail.Size = new System.Drawing.Size(35, 13);
+            this.lblEmail.TabIndex = 6;
+            this.lblEmail.Text = "Email:";
+            // 
+            // txtEmail
+            // 
+            this.txtEmail.Location = new System.Drawing.Point(120, 117);
+            this.txtEmail.Name = "txtEmail";
+            this.txtEmail.Size = new System.Drawing.Size(200, 20);
+            this.txtEmail.TabIndex = 7;
+            // 
+            // lblBirthDate
+            // 
+            this.lblBirthDate.AutoSize = true;
+            this.lblBirthDate.Location = new System.Drawing.Point(30, 150);
+            this.lblBirthDate.Name = "lblBirthDate";
+            this.lblBirthDate.Size = new System.Drawing.Size(89, 13);
+            this.lblBirthDate.TabIndex = 8;
+            this.lblBirthDate.Text = "Дата рождения:";
+            // 
+            // dtpBirthDate
+            // 
+            this.dtpBirthDate.Format = System.Windows.Forms.DateTimePickerFormat.Short;
+            this.dtpBirthDate.Location = new System.Drawing.Point(120, 147);
+            this.dtpBirthDate.Name = "dtpBirthDate";
+            this.dtpBirthDate.Size = new System.Drawing.Size(200, 20);
+            this.dtpBirthDate.TabIndex = 9;
+            // 
+            // lblBonusPoints
+            // 
+            this.lblBonusPoints.AutoSize = true;
+            this.lblBonusPoints.Location = new System.Drawing.Point(30, 180);
+            this.lblBonusPoints.Name = "lblBonusPoints";
+            this.lblBonusPoints.Size = new System.Drawing.Size(48, 13);
+            this.lblBonusPoints.TabIndex = 10;
+            this.lblBonusPoints.Text = "Бонусы:";
+            // 
+            // numBonusPoints
+            // 
+            this.numBonusPoints.Location = new System.Drawing.Point(120, 178);
+            this.numBonusPoints.Maximum = new decimal(new int[] {
+            100000,
+            0,
+            0,
+            0});
+            this.numBonusPoints.Name = "numBonusPoints";
+            this.numBonusPoints.Size = new System.Drawing.Size(200, 20);
+            this.numBonusPoints.TabIndex = 11;
+            // 
+            // btnSave
+            // 
+            this.btnSave.Location = new System.Drawing.Point(120, 220);
+            this.btnSave.Name = "btnSave";
+            this.btnSave.Size = new System.Drawing.Size(90, 30);
+            this.btnSave.TabIndex = 12;
+            this.btnSave.Text = "Сохранить";
+            this.btnSave.UseVisualStyleBackColor = true;
+            // 
+            // btnCancel
+            // 
+            this.btnCancel.Location = new System.Drawing.Point(230, 220);
+            this.btnCancel.Name = "btnCancel";
+            this.btnCancel.Size = new System.Drawing.Size(90, 30);
+            this.btnCancel.TabIndex = 13;
+            this.btnCancel.Text = "Отмена";
+            this.btnCancel.UseVisualStyleBackColor = true;
+            // 
+            // ClientEditForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(354, 271);
+            this.Controls.Add(this.btnCancel);
+            this.Controls.Add(this.btnSave);
+            this.Controls.Add(this.numBonusPoints);
+            this.Controls.Add(this.lblBonusPoints);
+            this.Controls.Add(this.dtpBirthDate);
+            this.Controls.Add(this.lblBirthDate);
+            this.Controls.Add(this.txtEmail);
+            this.Controls.Add(this.lblEmail);
+            this.Controls.Add(this.txtPhone);
+            this.Controls.Add(this.lblPhone);
+            this.Controls.Add(this.txtLastName);
+            this.Controls.Add(this.lblLastName);
+            this.Controls.Add(this.txtFirstName);
+            this.Controls.Add(this.lblFirstName);
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+            this.MaximizeBox = false;
+            this.MinimizeBox = false;
+            this.Name = "ClientEditForm";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+            this.Text = "Клиент";
+            ((System.ComponentModel.ISupportInitialize)(this.numBonusPoints)).EndInit();
+            this.ResumeLayout(false);
+            this.PerformLayout();
+
+        }
+
+        private System.Windows.Forms.Label lblFirstName;
+        private System.Windows.Forms.TextBox txtFirstName;
+        private System.Windows.Forms.Label lblLastName;
+        private System.Windows.Forms.TextBox txtLastName;
+        private System.Windows.Forms.Label lblPhone;
+        private System.Windows.Forms.TextBox txtPhone;
+        private System.Windows.Forms.Label lblEmail;
+        private System.Windows.Forms.TextBox txtEmail;
+        private System.Windows.Forms.Label lblBirthDate;
+        private System.Windows.Forms.DateTimePicker dtpBirthDate;
+        private System.Windows.Forms.Label lblBonusPoints;
+        private System.Windows.Forms.NumericUpDown numBonusPoints;
+        private System.Windows.Forms.Button btnSave;
+        private System.Windows.Forms.Button btnCancel;
+    }
+}

+ 83 - 0
ClientEditForm.cs

@@ -0,0 +1,83 @@
+using System;
+using System.Windows.Forms;
+using RestaurantApp.DAL;
+using RestaurantApp.Models;
+
+namespace RestaurantApp
+{
+    public partial class ClientEditForm : Form
+    {
+        private ClientRepository repo = new ClientRepository();
+        private Client client;
+        private bool isEdit = false;
+
+        public ClientEditForm()
+        {
+            InitializeComponent();
+            isEdit = false;
+            client = new Client();
+            Text = "Новый клиент";
+            dtpBirthDate.Value = DateTime.Now.AddYears(-18);
+
+            btnSave.Click += BtnSave_Click;
+            btnCancel.Click += (s, e) => DialogResult = DialogResult.Cancel;
+        }
+
+        public ClientEditForm(Client existingClient)
+        {
+            InitializeComponent();
+            isEdit = true;
+            client = existingClient;
+            Text = "Редактирование клиента";
+            LoadData();
+
+            btnSave.Click += BtnSave_Click;
+            btnCancel.Click += (s, e) => DialogResult = DialogResult.Cancel;
+        }
+
+        private void LoadData()
+        {
+            txtFirstName.Text = client.FirstName;
+            txtLastName.Text = client.LastName;
+            txtPhone.Text = client.Phone;
+            txtEmail.Text = client.Email;
+            if (client.BirthDate.HasValue)
+                dtpBirthDate.Value = client.BirthDate.Value;
+            numBonusPoints.Value = client.BonusPoints;
+        }
+
+        private void SaveData()
+        {
+            client.FirstName = txtFirstName.Text.Trim();
+            client.LastName = txtLastName.Text.Trim();
+            client.Phone = txtPhone.Text.Trim();
+            client.Email = txtEmail.Text.Trim();
+            client.BirthDate = dtpBirthDate.Value;
+            client.BonusPoints = (int)numBonusPoints.Value;
+
+            if (string.IsNullOrWhiteSpace(client.FirstName))
+                throw new Exception("Введите имя");
+            if (string.IsNullOrWhiteSpace(client.LastName))
+                throw new Exception("Введите фамилию");
+
+            if (isEdit)
+                repo.Update(client);
+            else
+                repo.Insert(client);
+        }
+
+        private void BtnSave_Click(object sender, EventArgs e)
+        {
+            try
+            {
+                SaveData();
+                DialogResult = DialogResult.OK;
+                Close();
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show(ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
+            }
+        }
+    }
+}

+ 120 - 0
ClientEditForm.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>

+ 148 - 0
ClientForm.Designer.cs

@@ -0,0 +1,148 @@
+namespace RestaurantApp
+{
+    partial class ClientForm
+    {
+        private System.ComponentModel.IContainer components = null;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.dgvClients = new System.Windows.Forms.DataGridView();
+            this.txtSearch = new System.Windows.Forms.TextBox();
+            this.btnSearch = new System.Windows.Forms.Button();
+            this.btnAdd = new System.Windows.Forms.Button();
+            this.btnEdit = new System.Windows.Forms.Button();
+            this.btnDelete = new System.Windows.Forms.Button();
+            this.btnRefresh = new System.Windows.Forms.Button();
+            this.lblSearch = new System.Windows.Forms.Label();
+            this.panelButtons = new System.Windows.Forms.Panel();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvClients)).BeginInit();
+            this.panelButtons.SuspendLayout();
+            this.SuspendLayout();
+            // 
+            // dgvClients
+            // 
+            this.dgvClients.AllowUserToAddRows = false;
+            this.dgvClients.AllowUserToDeleteRows = false;
+            this.dgvClients.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
+            this.dgvClients.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
+            this.dgvClients.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.dgvClients.Location = new System.Drawing.Point(0, 50);
+            this.dgvClients.Name = "dgvClients";
+            this.dgvClients.ReadOnly = true;
+            this.dgvClients.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
+            this.dgvClients.Size = new System.Drawing.Size(884, 461);
+            this.dgvClients.TabIndex = 0;
+            // 
+            // txtSearch
+            // 
+            this.txtSearch.Location = new System.Drawing.Point(60, 15);
+            this.txtSearch.Name = "txtSearch";
+            this.txtSearch.Size = new System.Drawing.Size(200, 20);
+            this.txtSearch.TabIndex = 1;
+            // 
+            // btnSearch
+            // 
+            this.btnSearch.Location = new System.Drawing.Point(270, 12);
+            this.btnSearch.Name = "btnSearch";
+            this.btnSearch.Size = new System.Drawing.Size(75, 23);
+            this.btnSearch.TabIndex = 2;
+            this.btnSearch.Text = "Поиск";
+            this.btnSearch.UseVisualStyleBackColor = true;
+            // 
+            // btnAdd
+            // 
+            this.btnAdd.Location = new System.Drawing.Point(370, 12);
+            this.btnAdd.Name = "btnAdd";
+            this.btnAdd.Size = new System.Drawing.Size(75, 23);
+            this.btnAdd.TabIndex = 3;
+            this.btnAdd.Text = "Добавить";
+            this.btnAdd.UseVisualStyleBackColor = true;
+            // 
+            // btnEdit
+            // 
+            this.btnEdit.Location = new System.Drawing.Point(460, 12);
+            this.btnEdit.Name = "btnEdit";
+            this.btnEdit.Size = new System.Drawing.Size(75, 23);
+            this.btnEdit.TabIndex = 4;
+            this.btnEdit.Text = "Изменить";
+            this.btnEdit.UseVisualStyleBackColor = true;
+            // 
+            // btnDelete
+            // 
+            this.btnDelete.Location = new System.Drawing.Point(550, 12);
+            this.btnDelete.Name = "btnDelete";
+            this.btnDelete.Size = new System.Drawing.Size(75, 23);
+            this.btnDelete.TabIndex = 5;
+            this.btnDelete.Text = "Удалить";
+            this.btnDelete.UseVisualStyleBackColor = true;
+            // 
+            // btnRefresh
+            // 
+            this.btnRefresh.Location = new System.Drawing.Point(640, 12);
+            this.btnRefresh.Name = "btnRefresh";
+            this.btnRefresh.Size = new System.Drawing.Size(75, 23);
+            this.btnRefresh.TabIndex = 6;
+            this.btnRefresh.Text = "Обновить";
+            this.btnRefresh.UseVisualStyleBackColor = true;
+            // 
+            // lblSearch
+            // 
+            this.lblSearch.AutoSize = true;
+            this.lblSearch.Location = new System.Drawing.Point(12, 18);
+            this.lblSearch.Name = "lblSearch";
+            this.lblSearch.Size = new System.Drawing.Size(42, 13);
+            this.lblSearch.TabIndex = 7;
+            this.lblSearch.Text = "Поиск:";
+            // 
+            // panelButtons
+            // 
+            this.panelButtons.Controls.Add(this.lblSearch);
+            this.panelButtons.Controls.Add(this.txtSearch);
+            this.panelButtons.Controls.Add(this.btnRefresh);
+            this.panelButtons.Controls.Add(this.btnSearch);
+            this.panelButtons.Controls.Add(this.btnDelete);
+            this.panelButtons.Controls.Add(this.btnAdd);
+            this.panelButtons.Controls.Add(this.btnEdit);
+            this.panelButtons.Dock = System.Windows.Forms.DockStyle.Top;
+            this.panelButtons.Location = new System.Drawing.Point(0, 0);
+            this.panelButtons.Name = "panelButtons";
+            this.panelButtons.Size = new System.Drawing.Size(884, 50);
+            this.panelButtons.TabIndex = 8;
+            // 
+            // ClientForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(884, 511);
+            this.Controls.Add(this.dgvClients);
+            this.Controls.Add(this.panelButtons);
+            this.Name = "ClientForm";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+            this.Text = "Клиенты";
+            ((System.ComponentModel.ISupportInitialize)(this.dgvClients)).EndInit();
+            this.panelButtons.ResumeLayout(false);
+            this.panelButtons.PerformLayout();
+            this.ResumeLayout(false);
+
+        }
+
+        private System.Windows.Forms.DataGridView dgvClients;
+        private System.Windows.Forms.TextBox txtSearch;
+        private System.Windows.Forms.Button btnSearch;
+        private System.Windows.Forms.Button btnAdd;
+        private System.Windows.Forms.Button btnEdit;
+        private System.Windows.Forms.Button btnDelete;
+        private System.Windows.Forms.Button btnRefresh;
+        private System.Windows.Forms.Label lblSearch;
+        private System.Windows.Forms.Panel panelButtons;
+    }
+}

+ 108 - 0
ClientForm.cs

@@ -0,0 +1,108 @@
+using System;
+using System.Windows.Forms;
+using RestaurantApp.DAL;
+using RestaurantApp.Models;
+
+namespace RestaurantApp
+{
+    public partial class ClientForm : Form
+    {
+        private ClientRepository repo = new ClientRepository();
+
+        public ClientForm()
+        {
+            InitializeComponent();
+            LoadData();
+
+            btnSearch.Click += (s, e) => LoadData();
+            btnRefresh.Click += (s, e) => LoadData();
+            btnAdd.Click += BtnAdd_Click;
+            btnEdit.Click += BtnEdit_Click;
+            btnDelete.Click += BtnDelete_Click;
+            dgvClients.DoubleClick += (s, e) => BtnEdit_Click(s, e);
+        }
+
+        private void LoadData()
+        {
+            try
+            {
+                var clients = repo.GetAll(txtSearch.Text);
+                dgvClients.DataSource = null;
+                dgvClients.DataSource = clients;
+
+                if (dgvClients.Columns.Count > 0)
+                {
+                    dgvClients.Columns["ClientId"].HeaderText = "ID";
+                    dgvClients.Columns["FirstName"].HeaderText = "Имя";
+                    dgvClients.Columns["LastName"].HeaderText = "Фамилия";
+                    dgvClients.Columns["Phone"].HeaderText = "Телефон";
+                    dgvClients.Columns["Email"].HeaderText = "Email";
+                    dgvClients.Columns["BirthDate"].HeaderText = "Дата рождения";
+                    dgvClients.Columns["BonusPoints"].HeaderText = "Бонусы";
+                    dgvClients.Columns["RegisteredAt"].HeaderText = "Дата регистрации";
+                    dgvClients.Columns["FullName"].Visible = false;
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки: {ex.Message}", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
+            }
+        }
+
+        private void BtnAdd_Click(object sender, EventArgs e)
+        {
+            var form = new ClientEditForm();
+            if (form.ShowDialog() == DialogResult.OK)
+            {
+                LoadData();
+            }
+        }
+
+        private void BtnEdit_Click(object sender, EventArgs e)
+        {
+            if (dgvClients.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите клиента", "Внимание", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+                return;
+            }
+
+            var client = (Client)dgvClients.CurrentRow.DataBoundItem;
+            var form = new ClientEditForm(client);
+            if (form.ShowDialog() == DialogResult.OK)
+            {
+                LoadData();
+            }
+        }
+
+        private void btnRefresh_Click(object sender, EventArgs e)
+        {
+            txtSearch.Text = "";
+            LoadData();
+        }
+
+        private void BtnDelete_Click(object sender, EventArgs e)
+        {
+            if (dgvClients.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите клиента", "Внимание", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+                return;
+            }
+
+            var client = (Client)dgvClients.CurrentRow.DataBoundItem;
+            if (MessageBox.Show($"Удалить клиента {client.FullName}?", "Подтверждение",
+                MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
+            {
+                try
+                {
+                    repo.Delete(client.ClientId);
+                    LoadData();
+                    MessageBox.Show("Клиент удалён", "Успех", MessageBoxButtons.OK, MessageBoxIcon.Information);
+                }
+                catch (Exception ex)
+                {
+                    MessageBox.Show($"Ошибка удаления: {ex.Message}", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
+                }
+            }
+        }
+    }
+}

+ 120 - 0
ClientForm.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>

+ 120 - 0
ClientRepository.cs

@@ -0,0 +1,120 @@
+using System;
+using System.Collections.Generic;
+using Npgsql;
+using RestaurantApp.Models;
+
+namespace RestaurantApp.DAL
+{
+    public class ClientRepository
+    {
+        public List<Client> GetAll(string search = "")
+        {
+            var list = new List<Client>();
+            using (var conn = Database.GetConnection())
+            {
+                var sql = @"SELECT client_id, first_name, last_name, phone, email,
+                                   birth_date, bonus_points, registered_at
+                            FROM client WHERE 1=1 ";
+                if (!string.IsNullOrWhiteSpace(search))
+                    sql += @" AND (LOWER(first_name) LIKE LOWER(@s) OR LOWER(last_name) LIKE LOWER(@s)
+                              OR phone LIKE @s OR email LIKE @s)";
+                sql += " ORDER BY last_name, first_name";
+
+                using (var cmd = new NpgsqlCommand(sql, conn))
+                {
+                    if (!string.IsNullOrWhiteSpace(search))
+                        cmd.Parameters.AddWithValue("s", $"%{search}%");
+                    using (var r = cmd.ExecuteReader())
+                        while (r.Read()) list.Add(MapClient(r));
+                }
+            }
+            return list;
+        }
+
+        public Client GetById(int id)
+        {
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                "SELECT client_id, first_name, last_name, phone, email, birth_date, bonus_points, registered_at FROM client WHERE client_id=@id", conn))
+            {
+                cmd.Parameters.AddWithValue("id", id);
+                using (var r = cmd.ExecuteReader())
+                    if (r.Read()) return MapClient(r);
+            }
+            return null;
+        }
+
+        public void Insert(Client c)
+        {
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                @"INSERT INTO client(first_name, last_name, phone, email, birth_date, bonus_points)
+                  VALUES(@fn, @ln, @ph, @em, @bd, @bp)", conn))
+            {
+                cmd.Parameters.AddWithValue("fn", c.FirstName);
+                cmd.Parameters.AddWithValue("ln", c.LastName);
+                cmd.Parameters.AddWithValue("ph", string.IsNullOrEmpty(c.Phone) ? (object)DBNull.Value : c.Phone);
+                cmd.Parameters.AddWithValue("em", string.IsNullOrEmpty(c.Email) ? (object)DBNull.Value : c.Email);
+                cmd.Parameters.AddWithValue("bd", c.BirthDate.HasValue ? (object)c.BirthDate.Value : DBNull.Value);
+                cmd.Parameters.AddWithValue("bp", c.BonusPoints);
+                cmd.ExecuteNonQuery();
+            }
+        }
+
+        public void Update(Client c)
+        {
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                @"UPDATE client SET first_name=@fn, last_name=@ln, phone=@ph, email=@em,
+                  birth_date=@bd, bonus_points=@bp WHERE client_id=@id", conn))
+            {
+                cmd.Parameters.AddWithValue("id", c.ClientId);
+                cmd.Parameters.AddWithValue("fn", c.FirstName);
+                cmd.Parameters.AddWithValue("ln", c.LastName);
+                cmd.Parameters.AddWithValue("ph", string.IsNullOrEmpty(c.Phone) ? (object)DBNull.Value : c.Phone);
+                cmd.Parameters.AddWithValue("em", string.IsNullOrEmpty(c.Email) ? (object)DBNull.Value : c.Email);
+                cmd.Parameters.AddWithValue("bd", c.BirthDate.HasValue ? (object)c.BirthDate.Value : DBNull.Value);
+                cmd.Parameters.AddWithValue("bp", c.BonusPoints);
+                cmd.ExecuteNonQuery();
+            }
+        }
+
+        public void Delete(int id)
+        {
+            using (var conn = Database.GetConnection())
+            {
+                using (var checkCmd = new NpgsqlCommand(
+                    "SELECT COUNT(*) FROM reservation WHERE client_id = @id", conn))
+                {
+                    checkCmd.Parameters.AddWithValue("id", id);
+                    long count = (long)checkCmd.ExecuteScalar();
+
+                    if (count > 0)
+                    {
+                        throw new Exception("Íåâîçìîæíî óäàëèòü êëèåíòà: ó íåãî åñòü áðîíèðîâàíèÿ");
+                    }
+                    else
+                    {
+                        using (var cmd = new NpgsqlCommand("DELETE FROM client WHERE client_id = @id", conn))
+                        {
+                            cmd.Parameters.AddWithValue("id", id);
+                            cmd.ExecuteNonQuery();
+                        }
+                    }
+                }
+            }
+        }
+
+        private Client MapClient(NpgsqlDataReader r) => new Client
+        {
+            ClientId = r.GetInt32(0),
+            FirstName = r.GetString(1),
+            LastName = r.GetString(2),
+            Phone = r.IsDBNull(3) ? "" : r.GetString(3),
+            Email = r.IsDBNull(4) ? "" : r.GetString(4),
+            BirthDate = r.IsDBNull(5) ? (DateTime?)null : r.GetDateTime(5),
+            BonusPoints = r.GetInt32(6),
+            RegisteredAt = r.GetDateTime(7)
+        };
+    }
+}

+ 40 - 0
Database.cs

@@ -0,0 +1,40 @@
+using System;
+using Npgsql;
+
+namespace RestaurantApp.DAL
+{
+    public static class Database
+    {
+        private const string Host = "edu-pg.itiscaf.ru";
+        private const int Port = 5432;
+        private const string DbName = "dbwork";
+        private const string User = "shagavaliev_iv";
+        private const string Password = "75&n057$380";
+        private const string Schema = "shagavaliev_iv";
+
+        public static string ConnectionString =>
+            $"Host={Host};Port={Port};Database={DbName};Username={User};Password={Password};Search Path={Schema};";
+
+        public static NpgsqlConnection GetConnection()
+        {
+            var conn = new NpgsqlConnection(ConnectionString);
+            conn.Open();
+            return conn;
+        }
+
+        public static bool TestConnection(out string error)
+        {
+            error = "";
+            try
+            {
+                using (var conn = GetConnection()) { }
+                return true;
+            }
+            catch (Exception ex)
+            {
+                error = ex.Message;
+                return false;
+            }
+        }
+    }
+}

+ 15 - 0
Dish.cs

@@ -0,0 +1,15 @@
+namespace RestaurantApp.Models
+{
+    public class Dish
+    {
+        public int DishId { get; set; }
+        public int CategoryId { get; set; }
+        public string CategoryName { get; set; }
+        public string Name { get; set; }
+        public string Description { get; set; }
+        public decimal Price { get; set; }
+        public int? WeightG { get; set; }
+        public int? CookTimeMin { get; set; }
+        public bool IsAvailable { get; set; }
+    }
+}

+ 218 - 0
DishEditForm.Designer.cs

@@ -0,0 +1,218 @@
+namespace RestaurantApp
+{
+    partial class DishEditForm
+    {
+        private System.ComponentModel.IContainer components = null;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.lblCategory = new System.Windows.Forms.Label();
+            this.cmbCategory = new System.Windows.Forms.ComboBox();
+            this.lblName = new System.Windows.Forms.Label();
+            this.txtName = new System.Windows.Forms.TextBox();
+            this.lblDescription = new System.Windows.Forms.Label();
+            this.txtDescription = new System.Windows.Forms.TextBox();
+            this.lblPrice = new System.Windows.Forms.Label();
+            this.numPrice = new System.Windows.Forms.NumericUpDown();
+            this.lblWeight = new System.Windows.Forms.Label();
+            this.numWeight = new System.Windows.Forms.NumericUpDown();
+            this.lblCookTime = new System.Windows.Forms.Label();
+            this.numCookTime = new System.Windows.Forms.NumericUpDown();
+            this.chkAvailable = new System.Windows.Forms.CheckBox();
+            this.btnSave = new System.Windows.Forms.Button();
+            this.btnCancel = new System.Windows.Forms.Button();
+            ((System.ComponentModel.ISupportInitialize)(this.numPrice)).BeginInit();
+            ((System.ComponentModel.ISupportInitialize)(this.numWeight)).BeginInit();
+            ((System.ComponentModel.ISupportInitialize)(this.numCookTime)).BeginInit();
+            this.SuspendLayout();
+            // 
+            // lblCategory
+            // 
+            this.lblCategory.AutoSize = true;
+            this.lblCategory.Location = new System.Drawing.Point(30, 25);
+            this.lblCategory.Name = "lblCategory";
+            this.lblCategory.Size = new System.Drawing.Size(63, 13);
+            this.lblCategory.TabIndex = 0;
+            this.lblCategory.Text = "Категория:";
+            // 
+            // cmbCategory
+            // 
+            this.cmbCategory.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbCategory.Location = new System.Drawing.Point(120, 22);
+            this.cmbCategory.Name = "cmbCategory";
+            this.cmbCategory.Size = new System.Drawing.Size(250, 21);
+            this.cmbCategory.TabIndex = 1;
+            // 
+            // lblName
+            // 
+            this.lblName.AutoSize = true;
+            this.lblName.Location = new System.Drawing.Point(30, 55);
+            this.lblName.Name = "lblName";
+            this.lblName.Size = new System.Drawing.Size(60, 13);
+            this.lblName.TabIndex = 2;
+            this.lblName.Text = "Название:";
+            // 
+            // txtName
+            // 
+            this.txtName.Location = new System.Drawing.Point(120, 52);
+            this.txtName.Name = "txtName";
+            this.txtName.Size = new System.Drawing.Size(250, 20);
+            this.txtName.TabIndex = 3;
+            // 
+            // lblDescription
+            // 
+            this.lblDescription.AutoSize = true;
+            this.lblDescription.Location = new System.Drawing.Point(30, 85);
+            this.lblDescription.Name = "lblDescription";
+            this.lblDescription.Size = new System.Drawing.Size(60, 13);
+            this.lblDescription.TabIndex = 4;
+            this.lblDescription.Text = "Описание:";
+            // 
+            // txtDescription
+            // 
+            this.txtDescription.Location = new System.Drawing.Point(120, 82);
+            this.txtDescription.Multiline = true;
+            this.txtDescription.Name = "txtDescription";
+            this.txtDescription.Size = new System.Drawing.Size(250, 60);
+            this.txtDescription.TabIndex = 5;
+            // 
+            // lblPrice
+            // 
+            this.lblPrice.AutoSize = true;
+            this.lblPrice.Location = new System.Drawing.Point(30, 155);
+            this.lblPrice.Name = "lblPrice";
+            this.lblPrice.Size = new System.Drawing.Size(36, 13);
+            this.lblPrice.TabIndex = 6;
+            this.lblPrice.Text = "Цена:";
+            // 
+            // numPrice
+            // 
+            this.numPrice.DecimalPlaces = 2;
+            this.numPrice.Location = new System.Drawing.Point(120, 153);
+            this.numPrice.Maximum = new decimal(new int[] { 100000, 0, 0, 0 });
+            this.numPrice.Name = "numPrice";
+            this.numPrice.Size = new System.Drawing.Size(120, 20);
+            this.numPrice.TabIndex = 7;
+            // 
+            // lblWeight
+            // 
+            this.lblWeight.AutoSize = true;
+            this.lblWeight.Location = new System.Drawing.Point(30, 185);
+            this.lblWeight.Name = "lblWeight";
+            this.lblWeight.Size = new System.Drawing.Size(57, 13);
+            this.lblWeight.TabIndex = 8;
+            this.lblWeight.Text = "Вес (г):";
+            // 
+            // numWeight
+            // 
+            this.numWeight.Location = new System.Drawing.Point(120, 183);
+            this.numWeight.Maximum = new decimal(new int[] { 10000, 0, 0, 0 });
+            this.numWeight.Name = "numWeight";
+            this.numWeight.Size = new System.Drawing.Size(120, 20);
+            this.numWeight.TabIndex = 9;
+            // 
+            // lblCookTime
+            // 
+            this.lblCookTime.AutoSize = true;
+            this.lblCookTime.Location = new System.Drawing.Point(30, 215);
+            this.lblCookTime.Name = "lblCookTime";
+            this.lblCookTime.Size = new System.Drawing.Size(91, 13);
+            this.lblCookTime.TabIndex = 10;
+            this.lblCookTime.Text = "Время (мин):";
+            // 
+            // numCookTime
+            // 
+            this.numCookTime.Location = new System.Drawing.Point(120, 213);
+            this.numCookTime.Maximum = new decimal(new int[] { 1000, 0, 0, 0 });
+            this.numCookTime.Name = "numCookTime";
+            this.numCookTime.Size = new System.Drawing.Size(120, 20);
+            this.numCookTime.TabIndex = 11;
+            // 
+            // chkAvailable
+            // 
+            this.chkAvailable.AutoSize = true;
+            this.chkAvailable.Location = new System.Drawing.Point(120, 245);
+            this.chkAvailable.Name = "chkAvailable";
+            this.chkAvailable.Size = new System.Drawing.Size(91, 17);
+            this.chkAvailable.TabIndex = 12;
+            this.chkAvailable.Text = "Доступно";
+            this.chkAvailable.UseVisualStyleBackColor = true;
+            // 
+            // btnSave
+            // 
+            this.btnSave.BackColor = System.Drawing.Color.LimeGreen;
+            this.btnSave.Location = new System.Drawing.Point(120, 280);
+            this.btnSave.Name = "btnSave";
+            this.btnSave.Size = new System.Drawing.Size(100, 30);
+            this.btnSave.TabIndex = 13;
+            this.btnSave.Text = "Сохранить";
+            this.btnSave.UseVisualStyleBackColor = false;
+            // 
+            // btnCancel
+            // 
+            this.btnCancel.Location = new System.Drawing.Point(240, 280);
+            this.btnCancel.Name = "btnCancel";
+            this.btnCancel.Size = new System.Drawing.Size(100, 30);
+            this.btnCancel.TabIndex = 14;
+            this.btnCancel.Text = "Отмена";
+            this.btnCancel.UseVisualStyleBackColor = true;
+            // 
+            // DishEditForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(420, 340);
+            this.Controls.Add(this.btnCancel);
+            this.Controls.Add(this.btnSave);
+            this.Controls.Add(this.chkAvailable);
+            this.Controls.Add(this.numCookTime);
+            this.Controls.Add(this.lblCookTime);
+            this.Controls.Add(this.numWeight);
+            this.Controls.Add(this.lblWeight);
+            this.Controls.Add(this.numPrice);
+            this.Controls.Add(this.lblPrice);
+            this.Controls.Add(this.txtDescription);
+            this.Controls.Add(this.lblDescription);
+            this.Controls.Add(this.txtName);
+            this.Controls.Add(this.lblName);
+            this.Controls.Add(this.cmbCategory);
+            this.Controls.Add(this.lblCategory);
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+            this.MaximizeBox = false;
+            this.MinimizeBox = false;
+            this.Name = "DishEditForm";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+            this.Text = "Блюдо";
+            ((System.ComponentModel.ISupportInitialize)(this.numPrice)).EndInit();
+            ((System.ComponentModel.ISupportInitialize)(this.numWeight)).EndInit();
+            ((System.ComponentModel.ISupportInitialize)(this.numCookTime)).EndInit();
+            this.ResumeLayout(false);
+            this.PerformLayout();
+        }
+
+        private System.Windows.Forms.Label lblCategory;
+        private System.Windows.Forms.ComboBox cmbCategory;
+        private System.Windows.Forms.Label lblName;
+        private System.Windows.Forms.TextBox txtName;
+        private System.Windows.Forms.Label lblDescription;
+        private System.Windows.Forms.TextBox txtDescription;
+        private System.Windows.Forms.Label lblPrice;
+        private System.Windows.Forms.NumericUpDown numPrice;
+        private System.Windows.Forms.Label lblWeight;
+        private System.Windows.Forms.NumericUpDown numWeight;
+        private System.Windows.Forms.Label lblCookTime;
+        private System.Windows.Forms.NumericUpDown numCookTime;
+        private System.Windows.Forms.CheckBox chkAvailable;
+        private System.Windows.Forms.Button btnSave;
+        private System.Windows.Forms.Button btnCancel;
+    }
+}

+ 111 - 0
DishEditForm.cs

@@ -0,0 +1,111 @@
+using RestaurantApp.DAL;
+using RestaurantApp.Models;
+using System;
+using System.Data;
+using System.Linq;
+using System.Windows.Forms;
+using System.Xml.Linq;
+
+namespace RestaurantApp
+{
+    public partial class DishEditForm : Form
+    {
+        private DishRepository repo = new DishRepository();
+        private Dish dish;
+        private bool isEdit = false;
+
+        public DishEditForm()
+        {
+            InitializeComponent();
+            isEdit = false;
+            dish = new Dish();
+            this.Text = "Новое блюдо";
+            LoadCategories();
+
+            btnSave.Click += BtnSave_Click;
+            btnCancel.Click += (s, e) => DialogResult = DialogResult.Cancel;
+        }
+
+        public DishEditForm(Dish existingDish)
+        {
+            InitializeComponent();
+            isEdit = true;
+            dish = existingDish;
+            this.Text = "Редактирование блюда";
+            LoadCategories();
+            LoadData();
+
+            btnSave.Click += BtnSave_Click;
+            btnCancel.Click += (s, e) => DialogResult = DialogResult.Cancel;
+        }
+
+        private void LoadCategories()
+        {
+            var dtCategories = repo.GetCategories();
+
+            var list = dtCategories.AsEnumerable().Select(r => new
+            {
+                category_id = r.Field<int>("category_id"),
+                name = r.Field<string>("name")
+            }).ToList();
+
+            cmbCategory.DataSource = list;
+            cmbCategory.DisplayMember = "name";
+            cmbCategory.ValueMember = "category_id";
+
+            if (!isEdit && cmbCategory.Items.Count > 0)
+                cmbCategory.SelectedIndex = 0;
+        }
+
+        private void LoadData()
+        {
+            cmbCategory.SelectedValue = dish.CategoryId;
+            txtName.Text = dish.Name;
+            txtDescription.Text = dish.Description;
+            numPrice.Value = dish.Price;
+            numWeight.Value = dish.WeightG ?? 0;
+            numCookTime.Value = dish.CookTimeMin ?? 0;
+            chkAvailable.Checked = dish.IsAvailable;
+        }
+
+        private void SaveData()
+        {
+            dish.CategoryId = (int)cmbCategory.SelectedValue;
+            dish.Name = txtName.Text.Trim();
+            dish.Description = txtDescription.Text.Trim();
+            dish.Price = numPrice.Value;
+            dish.WeightG = numWeight.Value > 0 ? (int?)numWeight.Value : null;
+            dish.CookTimeMin = numCookTime.Value > 0 ? (int?)numCookTime.Value : null;
+            dish.IsAvailable = chkAvailable.Checked;
+
+            if (string.IsNullOrWhiteSpace(dish.Name))
+                throw new Exception("Введите название блюда");
+            if (dish.Price <= 0)
+                throw new Exception("Цена должна быть больше 0");
+
+            if (isEdit)
+                repo.Update(dish);
+            else
+                repo.Insert(dish);
+        }
+
+        private void BtnSave_Click(object sender, EventArgs e)
+        {
+            try
+            {
+                SaveData();
+                DialogResult = DialogResult.OK;
+                Close();
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show(ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
+            }
+        }
+
+        private void DishEditForm_Load(object sender, EventArgs e)
+        {
+
+        }
+    }
+}

+ 120 - 0
DishEditForm.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>

+ 172 - 0
DishForm.Designer.cs

@@ -0,0 +1,172 @@
+namespace RestaurantApp
+{
+    partial class DishForm
+    {
+        private System.ComponentModel.IContainer components = null;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.dgvDishes = new System.Windows.Forms.DataGridView();
+            this.panelTop = new System.Windows.Forms.Panel();
+            this.chkAvailable = new System.Windows.Forms.CheckBox();
+            this.cmbCategory = new System.Windows.Forms.ComboBox();
+            this.lblCategory = new System.Windows.Forms.Label();
+            this.txtSearch = new System.Windows.Forms.TextBox();
+            this.btnSearch = new System.Windows.Forms.Button();
+            this.btnRefresh = new System.Windows.Forms.Button();
+            this.btnAdd = new System.Windows.Forms.Button();
+            this.btnEdit = new System.Windows.Forms.Button();
+            this.btnDelete = new System.Windows.Forms.Button();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvDishes)).BeginInit();
+            this.panelTop.SuspendLayout();
+            this.SuspendLayout();
+            // 
+            // dgvDishes
+            // 
+            this.dgvDishes.AllowUserToAddRows = false;
+            this.dgvDishes.AllowUserToDeleteRows = false;
+            this.dgvDishes.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
+            this.dgvDishes.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
+            this.dgvDishes.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.dgvDishes.Location = new System.Drawing.Point(0, 50);
+            this.dgvDishes.Name = "dgvDishes";
+            this.dgvDishes.ReadOnly = true;
+            this.dgvDishes.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
+            this.dgvDishes.Size = new System.Drawing.Size(984, 550);
+            this.dgvDishes.TabIndex = 0;
+            // 
+            // panelTop
+            // 
+            this.panelTop.Controls.Add(this.chkAvailable);
+            this.panelTop.Controls.Add(this.cmbCategory);
+            this.panelTop.Controls.Add(this.lblCategory);
+            this.panelTop.Controls.Add(this.txtSearch);
+            this.panelTop.Controls.Add(this.btnSearch);
+            this.panelTop.Controls.Add(this.btnRefresh);
+            this.panelTop.Controls.Add(this.btnAdd);
+            this.panelTop.Controls.Add(this.btnEdit);
+            this.panelTop.Controls.Add(this.btnDelete);
+            this.panelTop.Dock = System.Windows.Forms.DockStyle.Top;
+            this.panelTop.Location = new System.Drawing.Point(0, 0);
+            this.panelTop.Name = "panelTop";
+            this.panelTop.Size = new System.Drawing.Size(984, 50);
+            this.panelTop.TabIndex = 1;
+            // 
+            // chkAvailable
+            // 
+            this.chkAvailable.AutoSize = true;
+            this.chkAvailable.Location = new System.Drawing.Point(610, 17);
+            this.chkAvailable.Name = "chkAvailable";
+            this.chkAvailable.Size = new System.Drawing.Size(122, 17);
+            this.chkAvailable.TabIndex = 7;
+            this.chkAvailable.Text = "Только доступные";
+            this.chkAvailable.UseVisualStyleBackColor = true;
+            // 
+            // cmbCategory
+            // 
+            this.cmbCategory.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbCategory.Location = new System.Drawing.Point(100, 15);
+            this.cmbCategory.Name = "cmbCategory";
+            this.cmbCategory.Size = new System.Drawing.Size(150, 21);
+            this.cmbCategory.TabIndex = 6;
+            // 
+            // lblCategory
+            // 
+            this.lblCategory.AutoSize = true;
+            this.lblCategory.Location = new System.Drawing.Point(15, 18);
+            this.lblCategory.Name = "lblCategory";
+            this.lblCategory.Size = new System.Drawing.Size(63, 13);
+            this.lblCategory.TabIndex = 5;
+            this.lblCategory.Text = "Категория:";
+            // 
+            // txtSearch
+            // 
+            this.txtSearch.Location = new System.Drawing.Point(270, 15);
+            this.txtSearch.Name = "txtSearch";
+            this.txtSearch.Size = new System.Drawing.Size(150, 20);
+            this.txtSearch.TabIndex = 4;
+            // 
+            // btnSearch
+            // 
+            this.btnSearch.Location = new System.Drawing.Point(430, 13);
+            this.btnSearch.Name = "btnSearch";
+            this.btnSearch.Size = new System.Drawing.Size(75, 23);
+            this.btnSearch.TabIndex = 3;
+            this.btnSearch.Text = "Поиск";
+            this.btnSearch.UseVisualStyleBackColor = true;
+            // 
+            // btnRefresh
+            // 
+            this.btnRefresh.Location = new System.Drawing.Point(515, 13);
+            this.btnRefresh.Name = "btnRefresh";
+            this.btnRefresh.Size = new System.Drawing.Size(75, 23);
+            this.btnRefresh.TabIndex = 2;
+            this.btnRefresh.Text = "Обновить";
+            this.btnRefresh.UseVisualStyleBackColor = true;
+            // 
+            // btnAdd
+            // 
+            this.btnAdd.BackColor = System.Drawing.Color.LimeGreen;
+            this.btnAdd.Location = new System.Drawing.Point(750, 13);
+            this.btnAdd.Name = "btnAdd";
+            this.btnAdd.Size = new System.Drawing.Size(75, 23);
+            this.btnAdd.TabIndex = 1;
+            this.btnAdd.Text = "Добавить";
+            this.btnAdd.UseVisualStyleBackColor = false;
+            // 
+            // btnEdit
+            // 
+            this.btnEdit.Location = new System.Drawing.Point(830, 13);
+            this.btnEdit.Name = "btnEdit";
+            this.btnEdit.Size = new System.Drawing.Size(75, 23);
+            this.btnEdit.TabIndex = 1;
+            this.btnEdit.Text = "Изменить";
+            this.btnEdit.UseVisualStyleBackColor = true;
+            // 
+            // btnDelete
+            // 
+            this.btnDelete.Location = new System.Drawing.Point(910, 13);
+            this.btnDelete.Name = "btnDelete";
+            this.btnDelete.Size = new System.Drawing.Size(75, 23);
+            this.btnDelete.TabIndex = 0;
+            this.btnDelete.Text = "Удалить";
+            this.btnDelete.UseVisualStyleBackColor = true;
+            // 
+            // DishForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(984, 600);
+            this.Controls.Add(this.dgvDishes);
+            this.Controls.Add(this.panelTop);
+            this.Name = "DishForm";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+            this.Text = "Управление меню";
+            ((System.ComponentModel.ISupportInitialize)(this.dgvDishes)).EndInit();
+            this.panelTop.ResumeLayout(false);
+            this.panelTop.PerformLayout();
+            this.ResumeLayout(false);
+        }
+
+        private System.Windows.Forms.DataGridView dgvDishes;
+        private System.Windows.Forms.Panel panelTop;
+        private System.Windows.Forms.TextBox txtSearch;
+        private System.Windows.Forms.Button btnSearch;
+        private System.Windows.Forms.Button btnRefresh;
+        private System.Windows.Forms.Button btnAdd;
+        private System.Windows.Forms.Button btnEdit;
+        private System.Windows.Forms.Button btnDelete;
+        private System.Windows.Forms.ComboBox cmbCategory;
+        private System.Windows.Forms.Label lblCategory;
+        private System.Windows.Forms.CheckBox chkAvailable;
+    }
+}

+ 119 - 0
DishForm.cs

@@ -0,0 +1,119 @@
+using System;
+using System.Data;
+using System.Linq;
+using System.Windows.Forms;
+using RestaurantApp.DAL;
+using RestaurantApp.Models;
+
+namespace RestaurantApp
+{
+    public partial class DishForm : Form
+    {
+        private DishRepository repo = new DishRepository();
+
+        public DishForm()
+        {
+            InitializeComponent();
+            LoadCategories();
+            LoadDishes();
+
+            btnSearch.Click += (s, e) => LoadDishes();
+            btnRefresh.Click += (s, e) => LoadDishes();
+            btnAdd.Click += BtnAdd_Click;
+            btnEdit.Click += BtnEdit_Click;
+            btnDelete.Click += BtnDelete_Click;
+            cmbCategory.SelectedIndexChanged += (s, e) => LoadDishes();
+            chkAvailable.CheckedChanged += (s, e) => LoadDishes();
+            dgvDishes.DoubleClick += (s, e) => BtnEdit_Click(s, e);
+        }
+
+        private void LoadCategories()
+        {
+            var dtCategories = repo.GetCategories();
+
+            var list = dtCategories.AsEnumerable().Select(r => new
+            {
+                category_id = r.Field<int>("category_id"),
+                name = r.Field<string>("name")
+            }).ToList();
+
+            list.Insert(0, new { category_id = 0, name = "Все категории" });
+
+            cmbCategory.DataSource = list;
+            cmbCategory.DisplayMember = "name";
+            cmbCategory.ValueMember = "category_id";
+        }
+
+        private void LoadDishes()
+        {
+            int categoryId = (int)cmbCategory.SelectedValue;
+            if (categoryId == 0) categoryId = 0;
+
+            bool? available = chkAvailable.Checked ? (bool?)true : null;
+
+            var dishes = repo.GetAll(txtSearch.Text, categoryId, available);
+            dgvDishes.DataSource = dishes;
+
+            if (dgvDishes.Columns.Count > 0)
+            {
+                dgvDishes.Columns["DishId"].HeaderText = "ID";
+                dgvDishes.Columns["CategoryName"].HeaderText = "Категория";
+                dgvDishes.Columns["Name"].HeaderText = "Название";
+                dgvDishes.Columns["Description"].HeaderText = "Описание";
+                dgvDishes.Columns["Price"].HeaderText = "Цена";
+                dgvDishes.Columns["WeightG"].HeaderText = "Вес (г)";
+                dgvDishes.Columns["CookTimeMin"].HeaderText = "Время (мин)";
+                dgvDishes.Columns["IsAvailable"].HeaderText = "Доступно";
+            }
+        }
+
+        private void BtnAdd_Click(object sender, EventArgs e)
+        {
+            var form = new DishEditForm();
+            if (form.ShowDialog() == DialogResult.OK)
+            {
+                LoadDishes();
+            }
+        }
+
+        private void BtnEdit_Click(object sender, EventArgs e)
+        {
+            if (dgvDishes.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите блюдо для редактирования");
+                return;
+            }
+
+            var dish = (Dish)dgvDishes.CurrentRow.DataBoundItem;
+            var form = new DishEditForm(dish);
+            if (form.ShowDialog() == DialogResult.OK)
+            {
+                LoadDishes();
+            }
+        }
+
+        private void BtnDelete_Click(object sender, EventArgs e)
+        {
+            if (dgvDishes.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите блюдо для удаления");
+                return;
+            }
+
+            var dish = (Dish)dgvDishes.CurrentRow.DataBoundItem;
+            if (MessageBox.Show($"Удалить блюдо \"{dish.Name}\"?", "Подтверждение",
+                MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
+            {
+                try
+                {
+                    repo.Delete(dish.DishId);
+                    LoadDishes();
+                }
+                catch (Exception ex)
+                {
+                    MessageBox.Show($"Ошибка удаления: {ex.Message}");
+                }
+            }
+        }
+    }
+}

+ 120 - 0
DishForm.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>

+ 161 - 0
DishRepository.cs

@@ -0,0 +1,161 @@
+using Npgsql;
+using RestaurantApp.Models;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Windows.Forms;
+
+namespace RestaurantApp.DAL
+{
+    public class DishRepository
+    {
+        public List<Dish> GetAll(string search = "", int categoryId = 0, bool? available = null)
+        {
+            var list = new List<Dish>();
+            using (var conn = Database.GetConnection())
+            {
+                var sql = @"SELECT d.dish_id, d.category_id, dc.name as cat_name,
+                                   d.name, d.description, d.price, d.weight_g,
+                                   d.cook_time_min, d.is_available
+                            FROM dish d
+                            JOIN dish_category dc ON dc.category_id = d.category_id
+                            WHERE 1=1 ";
+                if (!string.IsNullOrWhiteSpace(search))
+                    sql += " AND (LOWER(d.name) LIKE LOWER(@search) OR LOWER(d.description) LIKE LOWER(@search))";
+                if (categoryId > 0)
+                    sql += " AND d.category_id = @cat";
+                if (available.HasValue)
+                    sql += " AND d.is_available = @avail";
+                sql += " ORDER BY dc.name, d.name";
+
+                using (var cmd = new NpgsqlCommand(sql, conn))
+                {
+                    if (!string.IsNullOrWhiteSpace(search))
+                        cmd.Parameters.AddWithValue("search", $"%{search}%");
+                    if (categoryId > 0)
+                        cmd.Parameters.AddWithValue("cat", categoryId);
+                    if (available.HasValue)
+                        cmd.Parameters.AddWithValue("avail", available.Value);
+
+                    using (var r = cmd.ExecuteReader())
+                    {
+                        while (r.Read())
+                            list.Add(MapDish(r));
+                    }
+                }
+            }
+            return list;
+        }
+
+        public Dish GetById(int id)
+        {
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                @"SELECT d.dish_id, d.category_id, dc.name, d.name, d.description,
+                         d.price, d.weight_g, d.cook_time_min, d.is_available
+                  FROM dish d JOIN dish_category dc ON dc.category_id=d.category_id
+                  WHERE d.dish_id=@id", conn))
+            {
+                cmd.Parameters.AddWithValue("id", id);
+                using (var r = cmd.ExecuteReader())
+                    if (r.Read()) return MapDish(r);
+            }
+            return null;
+        }
+
+        public void Insert(Dish d)
+        {
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                @"INSERT INTO dish(category_id, name, description, price, weight_g, cook_time_min, is_available)
+          VALUES(@cat, @name, @desc, @price, @w, @ct, @avail)", conn))
+            {
+                // Çäåñü ÍÅ äîëæíî áûòü ïàðàìåòðà dish_id
+                cmd.Parameters.AddWithValue("cat", d.CategoryId);
+                cmd.Parameters.AddWithValue("name", d.Name);
+                cmd.Parameters.AddWithValue("desc", (object)d.Description ?? DBNull.Value);
+                cmd.Parameters.AddWithValue("price", d.Price);
+                cmd.Parameters.AddWithValue("w", d.WeightG.HasValue ? (object)d.WeightG.Value : DBNull.Value);
+                cmd.Parameters.AddWithValue("ct", d.CookTimeMin.HasValue ? (object)d.CookTimeMin.Value : DBNull.Value);
+                cmd.Parameters.AddWithValue("avail", d.IsAvailable);
+                cmd.ExecuteNonQuery();
+            }
+        }
+
+        public void Update(Dish d)
+        {
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                @"UPDATE dish SET category_id=@cat,name=@name,description=@desc,price=@price,
+                  weight_g=@w,cook_time_min=@ct,is_available=@avail WHERE dish_id=@id", conn))
+            {
+                cmd.Parameters.AddWithValue("id", d.DishId);
+                cmd.Parameters.AddWithValue("cat", d.CategoryId);
+                cmd.Parameters.AddWithValue("name", d.Name);
+                cmd.Parameters.AddWithValue("desc", (object)d.Description ?? DBNull.Value);
+                cmd.Parameters.AddWithValue("price", d.Price);
+                cmd.Parameters.AddWithValue("w", d.WeightG.HasValue ? (object)d.WeightG.Value : DBNull.Value);
+                cmd.Parameters.AddWithValue("ct", d.CookTimeMin.HasValue ? (object)d.CookTimeMin.Value : DBNull.Value);
+                cmd.Parameters.AddWithValue("avail", d.IsAvailable);
+                cmd.ExecuteNonQuery();
+            }
+        }
+
+        public void Delete(int id)
+        {
+            using (var conn = Database.GetConnection())
+            {
+                using (var checkCmd = new NpgsqlCommand(
+                    "SELECT COUNT(*) FROM order_item WHERE dish_id = @id", conn))
+                {
+                    checkCmd.Parameters.AddWithValue("id", id);
+                    long count = (long)checkCmd.ExecuteScalar();
+
+                    if (count > 0)
+                    {
+                        using (var updateCmd = new NpgsqlCommand(
+                            "UPDATE dish SET is_available = false WHERE dish_id = @id", conn))
+                        {
+                            updateCmd.Parameters.AddWithValue("id", id);
+                            updateCmd.ExecuteNonQuery();
+                        }
+                        MessageBox.Show("Áëþäî èñïîëüçóåòñÿ â çàêàçàõ. Îíî ïîìå÷åíî êàê íåäîñòóïíîå.",
+                            "Èíôîðìàöèÿ", MessageBoxButtons.OK, MessageBoxIcon.Information);
+                    }
+                    else
+                    {
+                        using (var deleteCmd = new NpgsqlCommand(
+                            "DELETE FROM dish WHERE dish_id = @id", conn))
+                        {
+                            deleteCmd.Parameters.AddWithValue("id", id);
+                            deleteCmd.ExecuteNonQuery();
+                        }
+                    }
+                }
+            }
+        }
+
+        public DataTable GetCategories()
+        {
+            var dt = new DataTable();
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand("SELECT category_id, name FROM dish_category ORDER BY name", conn))
+            using (var da = new NpgsqlDataAdapter(cmd))
+                da.Fill(dt);
+            return dt;
+        }
+
+        private Dish MapDish(NpgsqlDataReader r) => new Dish
+        {
+            DishId = r.GetInt32(0),
+            CategoryId = r.GetInt32(1),
+            CategoryName = r.GetString(2),
+            Name = r.GetString(3),
+            Description = r.IsDBNull(4) ? "" : r.GetString(4),
+            Price = r.GetDecimal(5),
+            WeightG = r.IsDBNull(6) ? (int?)null : r.GetInt32(6),
+            CookTimeMin = r.IsDBNull(7) ? (int?)null : r.GetInt32(7),
+            IsAvailable = r.GetBoolean(8)
+        };
+    }
+}

+ 21 - 0
Employee.cs

@@ -0,0 +1,21 @@
+using System;
+
+namespace RestaurantApp.Models
+{
+    public class Employee
+    {
+        public int EmployeeId { get; set; }
+        public int PositionId { get; set; }
+        public string PositionTitle { get; set; }
+        public string FirstName { get; set; }
+        public string LastName { get; set; }
+        public string MiddleName { get; set; }
+        public string Phone { get; set; }
+        public string Email { get; set; }
+        public DateTime HireDate { get; set; }
+        public decimal Salary { get; set; }
+        public bool IsActive { get; set; }
+
+        public string FullName => $"{LastName} {FirstName} {MiddleName}".Trim();
+    }
+}

+ 256 - 0
EmployeeEditForm.Designer.cs

@@ -0,0 +1,256 @@
+namespace RestaurantApp
+{
+    partial class EmployeeEditForm
+    {
+        private System.ComponentModel.IContainer components = null;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.lblLastName = new System.Windows.Forms.Label();
+            this.txtLastName = new System.Windows.Forms.TextBox();
+            this.lblFirstName = new System.Windows.Forms.Label();
+            this.txtFirstName = new System.Windows.Forms.TextBox();
+            this.lblMiddleName = new System.Windows.Forms.Label();
+            this.txtMiddleName = new System.Windows.Forms.TextBox();
+            this.lblPosition = new System.Windows.Forms.Label();
+            this.cmbPosition = new System.Windows.Forms.ComboBox();
+            this.lblPhone = new System.Windows.Forms.Label();
+            this.txtPhone = new System.Windows.Forms.TextBox();
+            this.lblEmail = new System.Windows.Forms.Label();
+            this.txtEmail = new System.Windows.Forms.TextBox();
+            this.lblHireDate = new System.Windows.Forms.Label();
+            this.dtpHireDate = new System.Windows.Forms.DateTimePicker();
+            this.lblSalary = new System.Windows.Forms.Label();
+            this.numSalary = new System.Windows.Forms.NumericUpDown();
+            this.chkActive = new System.Windows.Forms.CheckBox();
+            this.btnSave = new System.Windows.Forms.Button();
+            this.btnCancel = new System.Windows.Forms.Button();
+            ((System.ComponentModel.ISupportInitialize)(this.numSalary)).BeginInit();
+            this.SuspendLayout();
+            // 
+            // lblLastName
+            // 
+            this.lblLastName.AutoSize = true;
+            this.lblLastName.Location = new System.Drawing.Point(30, 25);
+            this.lblLastName.Name = "lblLastName";
+            this.lblLastName.Size = new System.Drawing.Size(59, 13);
+            this.lblLastName.TabIndex = 0;
+            this.lblLastName.Text = "Фамилия:";
+            // 
+            // txtLastName
+            // 
+            this.txtLastName.Location = new System.Drawing.Point(120, 22);
+            this.txtLastName.Name = "txtLastName";
+            this.txtLastName.Size = new System.Drawing.Size(250, 20);
+            this.txtLastName.TabIndex = 1;
+            // 
+            // lblFirstName
+            // 
+            this.lblFirstName.AutoSize = true;
+            this.lblFirstName.Location = new System.Drawing.Point(30, 55);
+            this.lblFirstName.Name = "lblFirstName";
+            this.lblFirstName.Size = new System.Drawing.Size(32, 13);
+            this.lblFirstName.TabIndex = 2;
+            this.lblFirstName.Text = "Имя:";
+            // 
+            // txtFirstName
+            // 
+            this.txtFirstName.Location = new System.Drawing.Point(120, 52);
+            this.txtFirstName.Name = "txtFirstName";
+            this.txtFirstName.Size = new System.Drawing.Size(250, 20);
+            this.txtFirstName.TabIndex = 3;
+            // 
+            // lblMiddleName
+            // 
+            this.lblMiddleName.AutoSize = true;
+            this.lblMiddleName.Location = new System.Drawing.Point(30, 85);
+            this.lblMiddleName.Name = "lblMiddleName";
+            this.lblMiddleName.Size = new System.Drawing.Size(57, 13);
+            this.lblMiddleName.TabIndex = 4;
+            this.lblMiddleName.Text = "Отчество:";
+            // 
+            // txtMiddleName
+            // 
+            this.txtMiddleName.Location = new System.Drawing.Point(120, 82);
+            this.txtMiddleName.Name = "txtMiddleName";
+            this.txtMiddleName.Size = new System.Drawing.Size(250, 20);
+            this.txtMiddleName.TabIndex = 5;
+            // 
+            // lblPosition
+            // 
+            this.lblPosition.AutoSize = true;
+            this.lblPosition.Location = new System.Drawing.Point(30, 115);
+            this.lblPosition.Name = "lblPosition";
+            this.lblPosition.Size = new System.Drawing.Size(68, 13);
+            this.lblPosition.TabIndex = 6;
+            this.lblPosition.Text = "Должность:";
+            // 
+            // cmbPosition
+            // 
+            this.cmbPosition.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbPosition.Location = new System.Drawing.Point(120, 112);
+            this.cmbPosition.Name = "cmbPosition";
+            this.cmbPosition.Size = new System.Drawing.Size(250, 21);
+            this.cmbPosition.TabIndex = 7;
+            // 
+            // lblPhone
+            // 
+            this.lblPhone.AutoSize = true;
+            this.lblPhone.Location = new System.Drawing.Point(30, 145);
+            this.lblPhone.Name = "lblPhone";
+            this.lblPhone.Size = new System.Drawing.Size(55, 13);
+            this.lblPhone.TabIndex = 8;
+            this.lblPhone.Text = "Телефон:";
+            // 
+            // txtPhone
+            // 
+            this.txtPhone.Location = new System.Drawing.Point(120, 142);
+            this.txtPhone.Name = "txtPhone";
+            this.txtPhone.Size = new System.Drawing.Size(250, 20);
+            this.txtPhone.TabIndex = 9;
+            // 
+            // lblEmail
+            // 
+            this.lblEmail.AutoSize = true;
+            this.lblEmail.Location = new System.Drawing.Point(30, 175);
+            this.lblEmail.Name = "lblEmail";
+            this.lblEmail.Size = new System.Drawing.Size(35, 13);
+            this.lblEmail.TabIndex = 10;
+            this.lblEmail.Text = "Email:";
+            // 
+            // txtEmail
+            // 
+            this.txtEmail.Location = new System.Drawing.Point(120, 172);
+            this.txtEmail.Name = "txtEmail";
+            this.txtEmail.Size = new System.Drawing.Size(250, 20);
+            this.txtEmail.TabIndex = 11;
+            // 
+            // lblHireDate
+            // 
+            this.lblHireDate.AutoSize = true;
+            this.lblHireDate.Location = new System.Drawing.Point(30, 205);
+            this.lblHireDate.Name = "lblHireDate";
+            this.lblHireDate.Size = new System.Drawing.Size(77, 13);
+            this.lblHireDate.TabIndex = 12;
+            this.lblHireDate.Text = "Дата приема:";
+            // 
+            // dtpHireDate
+            // 
+            this.dtpHireDate.Format = System.Windows.Forms.DateTimePickerFormat.Short;
+            this.dtpHireDate.Location = new System.Drawing.Point(120, 202);
+            this.dtpHireDate.Name = "dtpHireDate";
+            this.dtpHireDate.Size = new System.Drawing.Size(150, 20);
+            this.dtpHireDate.TabIndex = 13;
+            // 
+            // lblSalary
+            // 
+            this.lblSalary.AutoSize = true;
+            this.lblSalary.Location = new System.Drawing.Point(30, 235);
+            this.lblSalary.Name = "lblSalary";
+            this.lblSalary.Size = new System.Drawing.Size(42, 13);
+            this.lblSalary.TabIndex = 14;
+            this.lblSalary.Text = "Оклад:";
+            // 
+            // numSalary
+            // 
+            this.numSalary.DecimalPlaces = 2;
+            this.numSalary.Location = new System.Drawing.Point(120, 233);
+            this.numSalary.Maximum = new decimal(new int[] { 1000000, 0, 0, 0 });
+            this.numSalary.Name = "numSalary";
+            this.numSalary.Size = new System.Drawing.Size(150, 20);
+            this.numSalary.TabIndex = 15;
+            // 
+            // chkActive
+            // 
+            this.chkActive.AutoSize = true;
+            this.chkActive.Location = new System.Drawing.Point(120, 265);
+            this.chkActive.Name = "chkActive";
+            this.chkActive.Size = new System.Drawing.Size(80, 17);
+            this.chkActive.TabIndex = 16;
+            this.chkActive.Text = "Активен";
+            this.chkActive.UseVisualStyleBackColor = true;
+            // 
+            // btnSave
+            // 
+            this.btnSave.BackColor = System.Drawing.Color.LimeGreen;
+            this.btnSave.Location = new System.Drawing.Point(120, 300);
+            this.btnSave.Name = "btnSave";
+            this.btnSave.Size = new System.Drawing.Size(100, 30);
+            this.btnSave.TabIndex = 17;
+            this.btnSave.Text = "Сохранить";
+            this.btnSave.UseVisualStyleBackColor = false;
+            // 
+            // btnCancel
+            // 
+            this.btnCancel.Location = new System.Drawing.Point(240, 300);
+            this.btnCancel.Name = "btnCancel";
+            this.btnCancel.Size = new System.Drawing.Size(100, 30);
+            this.btnCancel.TabIndex = 18;
+            this.btnCancel.Text = "Отмена";
+            this.btnCancel.UseVisualStyleBackColor = true;
+            // 
+            // EmployeeEditForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(420, 360);
+            this.Controls.Add(this.btnCancel);
+            this.Controls.Add(this.btnSave);
+            this.Controls.Add(this.chkActive);
+            this.Controls.Add(this.numSalary);
+            this.Controls.Add(this.lblSalary);
+            this.Controls.Add(this.dtpHireDate);
+            this.Controls.Add(this.lblHireDate);
+            this.Controls.Add(this.txtEmail);
+            this.Controls.Add(this.lblEmail);
+            this.Controls.Add(this.txtPhone);
+            this.Controls.Add(this.lblPhone);
+            this.Controls.Add(this.cmbPosition);
+            this.Controls.Add(this.lblPosition);
+            this.Controls.Add(this.txtMiddleName);
+            this.Controls.Add(this.lblMiddleName);
+            this.Controls.Add(this.txtFirstName);
+            this.Controls.Add(this.lblFirstName);
+            this.Controls.Add(this.txtLastName);
+            this.Controls.Add(this.lblLastName);
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+            this.MaximizeBox = false;
+            this.MinimizeBox = false;
+            this.Name = "EmployeeEditForm";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+            this.Text = "Сотрудник";
+            ((System.ComponentModel.ISupportInitialize)(this.numSalary)).EndInit();
+            this.ResumeLayout(false);
+            this.PerformLayout();
+        }
+
+        private System.Windows.Forms.Label lblLastName;
+        private System.Windows.Forms.TextBox txtLastName;
+        private System.Windows.Forms.Label lblFirstName;
+        private System.Windows.Forms.TextBox txtFirstName;
+        private System.Windows.Forms.Label lblMiddleName;
+        private System.Windows.Forms.TextBox txtMiddleName;
+        private System.Windows.Forms.Label lblPosition;
+        private System.Windows.Forms.ComboBox cmbPosition;
+        private System.Windows.Forms.Label lblPhone;
+        private System.Windows.Forms.TextBox txtPhone;
+        private System.Windows.Forms.Label lblEmail;
+        private System.Windows.Forms.TextBox txtEmail;
+        private System.Windows.Forms.Label lblHireDate;
+        private System.Windows.Forms.DateTimePicker dtpHireDate;
+        private System.Windows.Forms.Label lblSalary;
+        private System.Windows.Forms.NumericUpDown numSalary;
+        private System.Windows.Forms.CheckBox chkActive;
+        private System.Windows.Forms.Button btnSave;
+        private System.Windows.Forms.Button btnCancel;
+    }
+}

+ 110 - 0
EmployeeEditForm.cs

@@ -0,0 +1,110 @@
+using System;
+using System.Data;
+using System.Linq;
+using System.Windows.Forms;
+using RestaurantApp.DAL;
+using RestaurantApp.Models;
+
+namespace RestaurantApp
+{
+    public partial class EmployeeEditForm : Form
+    {
+        private EmployeeRepository repo = new EmployeeRepository();
+        private Employee employee;
+        private bool isEdit = false;
+
+        public EmployeeEditForm()
+        {
+            InitializeComponent();
+            isEdit = false;
+            employee = new Employee();
+            this.Text = "Новый сотрудник";
+            LoadPositions();
+            dtpHireDate.Value = DateTime.Now;
+            chkActive.Checked = true;
+
+            btnSave.Click += BtnSave_Click;
+            btnCancel.Click += (s, e) => DialogResult = DialogResult.Cancel;
+        }
+
+        public EmployeeEditForm(Employee existingEmployee)
+        {
+            InitializeComponent();
+            isEdit = true;
+            employee = existingEmployee;
+            this.Text = "Редактирование сотрудника";
+            LoadPositions();
+            LoadData();
+
+            btnSave.Click += BtnSave_Click;
+            btnCancel.Click += (s, e) => DialogResult = DialogResult.Cancel;
+        }
+
+        private void LoadPositions()
+        {
+            var dtPositions = repo.GetPositions();
+
+            var list = dtPositions.AsEnumerable().Select(r => new
+            {
+                position_id = r.Field<int>("position_id"),
+                title = r.Field<string>("title")
+            }).ToList();
+
+            cmbPosition.DataSource = list;
+            cmbPosition.DisplayMember = "title";
+            cmbPosition.ValueMember = "position_id";
+        }
+
+        private void LoadData()
+        {
+            cmbPosition.SelectedValue = employee.PositionId;
+            txtLastName.Text = employee.LastName;
+            txtFirstName.Text = employee.FirstName;
+            txtMiddleName.Text = employee.MiddleName;
+            txtPhone.Text = employee.Phone;
+            txtEmail.Text = employee.Email;
+            dtpHireDate.Value = employee.HireDate;
+            numSalary.Value = employee.Salary;
+            chkActive.Checked = employee.IsActive;
+        }
+
+        private void SaveData()
+        {
+            employee.PositionId = (int)cmbPosition.SelectedValue;
+            employee.LastName = txtLastName.Text.Trim();
+            employee.FirstName = txtFirstName.Text.Trim();
+            employee.MiddleName = txtMiddleName.Text.Trim();
+            employee.Phone = txtPhone.Text.Trim();
+            employee.Email = txtEmail.Text.Trim();
+            employee.HireDate = dtpHireDate.Value;
+            employee.Salary = numSalary.Value;
+            employee.IsActive = chkActive.Checked;
+
+            if (string.IsNullOrWhiteSpace(employee.LastName))
+                throw new Exception("Введите фамилию");
+            if (string.IsNullOrWhiteSpace(employee.FirstName))
+                throw new Exception("Введите имя");
+            if (employee.Salary <= 0)
+                throw new Exception("Оклад должен быть больше 0");
+
+            if (isEdit)
+                repo.Update(employee);
+            else
+                repo.Insert(employee);
+        }
+
+        private void BtnSave_Click(object sender, EventArgs e)
+        {
+            try
+            {
+                SaveData();
+                DialogResult = DialogResult.OK;
+                Close();
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show(ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
+            }
+        }
+    }
+}

+ 120 - 0
EmployeeEditForm.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>

+ 172 - 0
EmployeeForm.Designer.cs

@@ -0,0 +1,172 @@
+namespace RestaurantApp
+{
+    partial class EmployeeForm
+    {
+        private System.ComponentModel.IContainer components = null;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.dgvEmployees = new System.Windows.Forms.DataGridView();
+            this.panelTop = new System.Windows.Forms.Panel();
+            this.chkActive = new System.Windows.Forms.CheckBox();
+            this.cmbPosition = new System.Windows.Forms.ComboBox();
+            this.lblPosition = new System.Windows.Forms.Label();
+            this.txtSearch = new System.Windows.Forms.TextBox();
+            this.btnSearch = new System.Windows.Forms.Button();
+            this.btnRefresh = new System.Windows.Forms.Button();
+            this.btnAdd = new System.Windows.Forms.Button();
+            this.btnEdit = new System.Windows.Forms.Button();
+            this.btnDelete = new System.Windows.Forms.Button();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvEmployees)).BeginInit();
+            this.panelTop.SuspendLayout();
+            this.SuspendLayout();
+            // 
+            // dgvEmployees
+            // 
+            this.dgvEmployees.AllowUserToAddRows = false;
+            this.dgvEmployees.AllowUserToDeleteRows = false;
+            this.dgvEmployees.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
+            this.dgvEmployees.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
+            this.dgvEmployees.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.dgvEmployees.Location = new System.Drawing.Point(0, 50);
+            this.dgvEmployees.Name = "dgvEmployees";
+            this.dgvEmployees.ReadOnly = true;
+            this.dgvEmployees.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
+            this.dgvEmployees.Size = new System.Drawing.Size(1100, 550);
+            this.dgvEmployees.TabIndex = 0;
+            // 
+            // panelTop
+            // 
+            this.panelTop.Controls.Add(this.chkActive);
+            this.panelTop.Controls.Add(this.cmbPosition);
+            this.panelTop.Controls.Add(this.lblPosition);
+            this.panelTop.Controls.Add(this.txtSearch);
+            this.panelTop.Controls.Add(this.btnSearch);
+            this.panelTop.Controls.Add(this.btnRefresh);
+            this.panelTop.Controls.Add(this.btnAdd);
+            this.panelTop.Controls.Add(this.btnEdit);
+            this.panelTop.Controls.Add(this.btnDelete);
+            this.panelTop.Dock = System.Windows.Forms.DockStyle.Top;
+            this.panelTop.Location = new System.Drawing.Point(0, 0);
+            this.panelTop.Name = "panelTop";
+            this.panelTop.Size = new System.Drawing.Size(1100, 50);
+            this.panelTop.TabIndex = 1;
+            // 
+            // chkActive
+            // 
+            this.chkActive.AutoSize = true;
+            this.chkActive.Location = new System.Drawing.Point(610, 17);
+            this.chkActive.Name = "chkActive";
+            this.chkActive.Size = new System.Drawing.Size(106, 17);
+            this.chkActive.TabIndex = 8;
+            this.chkActive.Text = "Только активные";
+            this.chkActive.UseVisualStyleBackColor = true;
+            // 
+            // cmbPosition
+            // 
+            this.cmbPosition.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbPosition.Location = new System.Drawing.Point(100, 15);
+            this.cmbPosition.Name = "cmbPosition";
+            this.cmbPosition.Size = new System.Drawing.Size(150, 21);
+            this.cmbPosition.TabIndex = 7;
+            // 
+            // lblPosition
+            // 
+            this.lblPosition.AutoSize = true;
+            this.lblPosition.Location = new System.Drawing.Point(15, 18);
+            this.lblPosition.Name = "lblPosition";
+            this.lblPosition.Size = new System.Drawing.Size(68, 13);
+            this.lblPosition.TabIndex = 6;
+            this.lblPosition.Text = "Должность:";
+            // 
+            // txtSearch
+            // 
+            this.txtSearch.Location = new System.Drawing.Point(270, 15);
+            this.txtSearch.Name = "txtSearch";
+            this.txtSearch.Size = new System.Drawing.Size(150, 20);
+            this.txtSearch.TabIndex = 4;
+            // 
+            // btnSearch
+            // 
+            this.btnSearch.Location = new System.Drawing.Point(430, 13);
+            this.btnSearch.Name = "btnSearch";
+            this.btnSearch.Size = new System.Drawing.Size(75, 23);
+            this.btnSearch.TabIndex = 3;
+            this.btnSearch.Text = "Поиск";
+            this.btnSearch.UseVisualStyleBackColor = true;
+            // 
+            // btnRefresh
+            // 
+            this.btnRefresh.Location = new System.Drawing.Point(515, 13);
+            this.btnRefresh.Name = "btnRefresh";
+            this.btnRefresh.Size = new System.Drawing.Size(75, 23);
+            this.btnRefresh.TabIndex = 2;
+            this.btnRefresh.Text = "Обновить";
+            this.btnRefresh.UseVisualStyleBackColor = true;
+            // 
+            // btnAdd
+            // 
+            this.btnAdd.BackColor = System.Drawing.Color.LimeGreen;
+            this.btnAdd.Location = new System.Drawing.Point(750, 13);
+            this.btnAdd.Name = "btnAdd";
+            this.btnAdd.Size = new System.Drawing.Size(80, 23);
+            this.btnAdd.TabIndex = 1;
+            this.btnAdd.Text = "Добавить";
+            this.btnAdd.UseVisualStyleBackColor = false;
+            // 
+            // btnEdit
+            // 
+            this.btnEdit.Location = new System.Drawing.Point(840, 13);
+            this.btnEdit.Name = "btnEdit";
+            this.btnEdit.Size = new System.Drawing.Size(80, 23);
+            this.btnEdit.TabIndex = 1;
+            this.btnEdit.Text = "Изменить";
+            this.btnEdit.UseVisualStyleBackColor = true;
+            // 
+            // btnDelete
+            // 
+            this.btnDelete.Location = new System.Drawing.Point(930, 13);
+            this.btnDelete.Name = "btnDelete";
+            this.btnDelete.Size = new System.Drawing.Size(80, 23);
+            this.btnDelete.TabIndex = 0;
+            this.btnDelete.Text = "Уволить";
+            this.btnDelete.UseVisualStyleBackColor = true;
+            // 
+            // EmployeeForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(1100, 600);
+            this.Controls.Add(this.dgvEmployees);
+            this.Controls.Add(this.panelTop);
+            this.Name = "EmployeeForm";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+            this.Text = "Управление сотрудниками";
+            ((System.ComponentModel.ISupportInitialize)(this.dgvEmployees)).EndInit();
+            this.panelTop.ResumeLayout(false);
+            this.panelTop.PerformLayout();
+            this.ResumeLayout(false);
+        }
+
+        private System.Windows.Forms.DataGridView dgvEmployees;
+        private System.Windows.Forms.Panel panelTop;
+        private System.Windows.Forms.TextBox txtSearch;
+        private System.Windows.Forms.Button btnSearch;
+        private System.Windows.Forms.Button btnRefresh;
+        private System.Windows.Forms.Button btnAdd;
+        private System.Windows.Forms.Button btnEdit;
+        private System.Windows.Forms.Button btnDelete;
+        private System.Windows.Forms.ComboBox cmbPosition;
+        private System.Windows.Forms.Label lblPosition;
+        private System.Windows.Forms.CheckBox chkActive;
+    }
+}

+ 131 - 0
EmployeeForm.cs

@@ -0,0 +1,131 @@
+using System;
+using System.Data;
+using System.Linq;
+using System.Windows.Forms;
+using RestaurantApp.DAL;
+using RestaurantApp.Models;
+
+namespace RestaurantApp
+{
+    public partial class EmployeeForm : Form
+    {
+        private EmployeeRepository repo = new EmployeeRepository();
+
+        public EmployeeForm()
+        {
+            InitializeComponent();
+            LoadPositions();
+            LoadEmployees();
+
+            btnSearch.Click += (s, e) => LoadEmployees();
+            btnRefresh.Click += (s, e) => LoadEmployees();
+            btnAdd.Click += BtnAdd_Click;
+            btnEdit.Click += BtnEdit_Click;
+            btnDelete.Click += BtnDelete_Click;
+            cmbPosition.SelectedIndexChanged += (s, e) => LoadEmployees();
+            chkActive.CheckedChanged += (s, e) => LoadEmployees();
+            dgvEmployees.DoubleClick += (s, e) => BtnEdit_Click(s, e);
+        }
+
+        private void LoadPositions()
+        {
+            var dtPositions = repo.GetPositions();
+
+            var list = dtPositions.AsEnumerable().Select(r => new
+            {
+                position_id = r.Field<int>("position_id"),
+                title = r.Field<string>("title")
+            }).ToList();
+
+            list.Insert(0, new { position_id = 0, title = "Все должности" });
+
+            cmbPosition.DataSource = list;
+            cmbPosition.DisplayMember = "title";
+            cmbPosition.ValueMember = "position_id";
+        }
+
+        private void LoadEmployees()
+        {
+            int positionId = (int)cmbPosition.SelectedValue;
+            string positionFilter = positionId > 0 ? cmbPosition.Text : "";
+
+            bool? active = chkActive.Checked ? (bool?)true : null;
+
+            var employees = repo.GetAll(txtSearch.Text, active);
+
+            // Фильтрация по должности на клиенте
+            if (positionId > 0)
+            {
+                employees = employees.FindAll(e => e.PositionId == positionId);
+            }
+
+            dgvEmployees.DataSource = employees;
+
+            if (dgvEmployees.Columns.Count > 0)
+            {
+                dgvEmployees.Columns["EmployeeId"].HeaderText = "ID";
+                dgvEmployees.Columns["FullName"].HeaderText = "ФИО";
+                dgvEmployees.Columns["PositionTitle"].HeaderText = "Должность";
+                dgvEmployees.Columns["Phone"].HeaderText = "Телефон";
+                dgvEmployees.Columns["Email"].HeaderText = "Email";
+                dgvEmployees.Columns["HireDate"].HeaderText = "Дата приема";
+                dgvEmployees.Columns["Salary"].HeaderText = "Оклад";
+                dgvEmployees.Columns["IsActive"].HeaderText = "Активен";
+                dgvEmployees.Columns["PositionId"].Visible = false;
+                dgvEmployees.Columns["FirstName"].Visible = false;
+                dgvEmployees.Columns["LastName"].Visible = false;
+                dgvEmployees.Columns["MiddleName"].Visible = false;
+            }
+        }
+
+        private void BtnAdd_Click(object sender, EventArgs e)
+        {
+            var form = new EmployeeEditForm();
+            if (form.ShowDialog() == DialogResult.OK)
+            {
+                LoadEmployees();
+            }
+        }
+
+        private void BtnEdit_Click(object sender, EventArgs e)
+        {
+            if (dgvEmployees.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите сотрудника для редактирования");
+                return;
+            }
+
+            var employee = (Employee)dgvEmployees.CurrentRow.DataBoundItem;
+            var form = new EmployeeEditForm(employee);
+            if (form.ShowDialog() == DialogResult.OK)
+            {
+                LoadEmployees();
+            }
+        }
+
+        private void BtnDelete_Click(object sender, EventArgs e)
+        {
+            if (dgvEmployees.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите сотрудника");
+                return;
+            }
+
+            var employee = (Employee)dgvEmployees.CurrentRow.DataBoundItem;
+            if (MessageBox.Show($"Уволить сотрудника {employee.FullName}?", "Подтверждение",
+                MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
+            {
+                try
+                {
+                    repo.Delete(employee.EmployeeId);
+                    LoadEmployees();
+                    MessageBox.Show("Сотрудник уволен");
+                }
+                catch (Exception ex)
+                {
+                    MessageBox.Show($"Ошибка: {ex.Message}");
+                }
+            }
+        }
+    }
+}

+ 120 - 0
EmployeeForm.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>

+ 128 - 0
EmployeeRepository.cs

@@ -0,0 +1,128 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using Npgsql;
+using RestaurantApp.Models;
+
+namespace RestaurantApp.DAL
+{
+    public class EmployeeRepository
+    {
+        public List<Employee> GetAll(string search = "", bool? active = null)
+        {
+            var list = new List<Employee>();
+            using (var conn = Database.GetConnection())
+            {
+                var sql = @"SELECT e.employee_id,e.position_id,p.title,
+                                   e.first_name,e.last_name,e.middle_name,
+                                   e.phone,e.email,e.hire_date,e.salary,e.is_active
+                            FROM employee e JOIN position p ON p.position_id=e.position_id
+                            WHERE 1=1 ";
+                if (!string.IsNullOrWhiteSpace(search))
+                    sql += " AND (LOWER(e.last_name) LIKE LOWER(@s) OR LOWER(e.first_name) LIKE LOWER(@s) OR e.phone LIKE @s)";
+                if (active.HasValue)
+                    sql += " AND e.is_active=@a";
+                sql += " ORDER BY e.last_name, e.first_name";
+
+                using (var cmd = new NpgsqlCommand(sql, conn))
+                {
+                    if (!string.IsNullOrWhiteSpace(search))
+                        cmd.Parameters.AddWithValue("s", $"%{search}%");
+                    if (active.HasValue)
+                        cmd.Parameters.AddWithValue("a", active.Value);
+                    using (var r = cmd.ExecuteReader())
+                        while (r.Read()) list.Add(MapEmployee(r));
+                }
+            }
+            return list;
+        }
+
+        public void Insert(Employee e)
+        {
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                @"INSERT INTO employee(position_id,first_name,last_name,middle_name,phone,email,hire_date,salary,is_active)
+                  VALUES(@p,@fn,@ln,@mn,@ph,@em,@hd,@sal,@act)", conn))
+            {
+                SetParams(cmd, e);
+                cmd.ExecuteNonQuery();
+            }
+        }
+
+        public void Update(Employee e)
+        {
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                @"UPDATE employee SET position_id=@p,first_name=@fn,last_name=@ln,middle_name=@mn,
+                  phone=@ph,email=@em,hire_date=@hd,salary=@sal,is_active=@act
+                  WHERE employee_id=@id", conn))
+            {
+                cmd.Parameters.AddWithValue("id", e.EmployeeId);
+                SetParams(cmd, e);
+                cmd.ExecuteNonQuery();
+            }
+        }
+
+        public void Delete(int id)
+        {
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand("UPDATE employee SET is_active=false WHERE employee_id=@id", conn))
+            {
+                cmd.Parameters.AddWithValue("id", id);
+                cmd.ExecuteNonQuery();
+            }
+        }
+
+        public DataTable GetPositions()
+        {
+            var dt = new DataTable();
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand("SELECT position_id, title FROM position ORDER BY title", conn))
+            using (var da = new NpgsqlDataAdapter(cmd))
+                da.Fill(dt);
+            return dt;
+        }
+
+        public List<Employee> GetWaiters()
+        {
+            var list = new List<Employee>();
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                @"SELECT e.employee_id,e.position_id,p.title,e.first_name,e.last_name,e.middle_name,
+                         e.phone,e.email,e.hire_date,e.salary,e.is_active
+                  FROM employee e JOIN position p ON p.position_id=e.position_id
+                  WHERE e.is_active=true ORDER BY e.last_name", conn))
+            using (var r = cmd.ExecuteReader())
+                while (r.Read()) list.Add(MapEmployee(r));
+            return list;
+        }
+
+        private void SetParams(NpgsqlCommand cmd, Employee e)
+        {
+            cmd.Parameters.AddWithValue("p", e.PositionId);
+            cmd.Parameters.AddWithValue("fn", e.FirstName);
+            cmd.Parameters.AddWithValue("ln", e.LastName);
+            cmd.Parameters.AddWithValue("mn", string.IsNullOrEmpty(e.MiddleName) ? (object)DBNull.Value : e.MiddleName);
+            cmd.Parameters.AddWithValue("ph", string.IsNullOrEmpty(e.Phone) ? (object)DBNull.Value : e.Phone);
+            cmd.Parameters.AddWithValue("em", string.IsNullOrEmpty(e.Email) ? (object)DBNull.Value : e.Email);
+            cmd.Parameters.AddWithValue("hd", e.HireDate);
+            cmd.Parameters.AddWithValue("sal", e.Salary);
+            cmd.Parameters.AddWithValue("act", e.IsActive);
+        }
+
+        private Employee MapEmployee(NpgsqlDataReader r) => new Employee
+        {
+            EmployeeId = r.GetInt32(0),
+            PositionId = r.GetInt32(1),
+            PositionTitle = r.GetString(2),
+            FirstName = r.GetString(3),
+            LastName = r.GetString(4),
+            MiddleName = r.IsDBNull(5) ? "" : r.GetString(5),
+            Phone = r.IsDBNull(6) ? "" : r.GetString(6),
+            Email = r.IsDBNull(7) ? "" : r.GetString(7),
+            HireDate = r.GetDateTime(8),
+            Salary = r.GetDecimal(9),
+            IsActive = r.GetBoolean(10)
+        };
+    }
+}

+ 105 - 0
LoginForm.Designer.cs

@@ -0,0 +1,105 @@
+namespace RestaurantApp
+{
+    partial class LoginForm
+    {
+        private System.ComponentModel.IContainer components = null;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.txtLogin = new System.Windows.Forms.TextBox();
+            this.txtPassword = new System.Windows.Forms.TextBox();
+            this.btnLogin = new System.Windows.Forms.Button();
+            this.btnCancel = new System.Windows.Forms.Button();
+            this.lblLogin = new System.Windows.Forms.Label();
+            this.lblPassword = new System.Windows.Forms.Label();
+            this.SuspendLayout();
+            // 
+            // txtLogin
+            // 
+            this.txtLogin.Location = new System.Drawing.Point(50, 55);
+            this.txtLogin.Name = "txtLogin";
+            this.txtLogin.Size = new System.Drawing.Size(200, 20);
+            this.txtLogin.TabIndex = 1;
+            // 
+            // txtPassword
+            // 
+            this.txtPassword.Location = new System.Drawing.Point(50, 105);
+            this.txtPassword.Name = "txtPassword";
+            this.txtPassword.PasswordChar = '*';
+            this.txtPassword.Size = new System.Drawing.Size(200, 20);
+            this.txtPassword.TabIndex = 3;
+            // 
+            // btnLogin
+            // 
+            this.btnLogin.Location = new System.Drawing.Point(50, 145);
+            this.btnLogin.Name = "btnLogin";
+            this.btnLogin.Size = new System.Drawing.Size(90, 30);
+            this.btnLogin.TabIndex = 4;
+            this.btnLogin.Text = "Вход";
+            this.btnLogin.UseVisualStyleBackColor = true;
+            // 
+            // btnCancel
+            // 
+            this.btnCancel.Location = new System.Drawing.Point(160, 145);
+            this.btnCancel.Name = "btnCancel";
+            this.btnCancel.Size = new System.Drawing.Size(90, 30);
+            this.btnCancel.TabIndex = 5;
+            this.btnCancel.Text = "Отмена";
+            this.btnCancel.UseVisualStyleBackColor = true;
+            // 
+            // lblLogin
+            // 
+            this.lblLogin.AutoSize = true;
+            this.lblLogin.Location = new System.Drawing.Point(50, 35);
+            this.lblLogin.Name = "lblLogin";
+            this.lblLogin.Size = new System.Drawing.Size(41, 13);
+            this.lblLogin.TabIndex = 0;
+            this.lblLogin.Text = "Логин:";
+            // 
+            // lblPassword
+            // 
+            this.lblPassword.AutoSize = true;
+            this.lblPassword.Location = new System.Drawing.Point(50, 85);
+            this.lblPassword.Name = "lblPassword";
+            this.lblPassword.Size = new System.Drawing.Size(48, 13);
+            this.lblPassword.TabIndex = 2;
+            this.lblPassword.Text = "Пароль:";
+            // 
+            // LoginForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(300, 210);
+            this.Controls.Add(this.btnCancel);
+            this.Controls.Add(this.btnLogin);
+            this.Controls.Add(this.txtPassword);
+            this.Controls.Add(this.lblPassword);
+            this.Controls.Add(this.txtLogin);
+            this.Controls.Add(this.lblLogin);
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
+            this.MaximizeBox = false;
+            this.Name = "LoginForm";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+            this.Text = "Авторизация";
+            this.ResumeLayout(false);
+            this.PerformLayout();
+
+        }
+
+        private System.Windows.Forms.TextBox txtLogin;
+        private System.Windows.Forms.TextBox txtPassword;
+        private System.Windows.Forms.Button btnLogin;
+        private System.Windows.Forms.Button btnCancel;
+        private System.Windows.Forms.Label lblLogin;
+        private System.Windows.Forms.Label lblPassword;
+    }
+}

+ 58 - 0
LoginForm.cs

@@ -0,0 +1,58 @@
+using System;
+using System.Windows.Forms;
+using Npgsql;
+using RestaurantApp.DAL;
+
+namespace RestaurantApp
+{
+    public partial class LoginForm : Form
+    {
+        public LoginForm()
+        {
+            InitializeComponent();
+            btnLogin.Click += BtnLogin_Click;
+            btnCancel.Click += (s, e) => Application.Exit();
+        }
+
+        private void BtnLogin_Click(object sender, EventArgs e)
+        {
+            if (string.IsNullOrWhiteSpace(txtLogin.Text))
+            {
+                MessageBox.Show("Введите логин", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+                return;
+            }
+
+            try
+            {
+                using (var conn = Database.GetConnection())
+                using (var cmd = new NpgsqlCommand(
+                    "SELECT full_name, role FROM app_user WHERE login = @login", conn))
+                {
+                    cmd.Parameters.AddWithValue("login", txtLogin.Text);
+
+                    using (var r = cmd.ExecuteReader())
+                    {
+                        if (r.Read())
+                        {
+                            Program.CurrentUser = r.GetString(0);
+                            Program.CurrentRole = r.GetString(1);
+
+                            this.Hide();
+                            var mainForm = new MainForm();
+                            mainForm.Closed += (s, args) => this.Close();
+                            mainForm.Show();
+                        }
+                        else
+                        {
+                            MessageBox.Show("Неверный логин", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка подключения: {ex.Message}", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
+            }
+        }
+    }
+}

+ 120 - 0
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>

+ 135 - 0
MainForm.Designer.cs

@@ -0,0 +1,135 @@
+namespace RestaurantApp
+{
+    partial class MainForm
+    {
+        private System.ComponentModel.IContainer components = null;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.menuStrip = new System.Windows.Forms.MenuStrip();
+            this.клиентыToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.блюдаToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.сотрудникиToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.заказыToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.бронированияToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.отчётыToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.выходToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.statusStrip = new System.Windows.Forms.StatusStrip();
+            this.lblUser = new System.Windows.Forms.ToolStripStatusLabel();
+            this.menuStrip.SuspendLayout();
+            this.statusStrip.SuspendLayout();
+            this.SuspendLayout();
+            // 
+            // menuStrip
+            // 
+            this.menuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+            this.клиентыToolStripMenuItem,
+            this.блюдаToolStripMenuItem,
+            this.сотрудникиToolStripMenuItem,
+            this.заказыToolStripMenuItem,
+            this.бронированияToolStripMenuItem,
+            this.отчётыToolStripMenuItem,
+            this.выходToolStripMenuItem});
+            this.menuStrip.Location = new System.Drawing.Point(0, 0);
+            this.menuStrip.Name = "menuStrip";
+            this.menuStrip.Size = new System.Drawing.Size(984, 24);
+            this.menuStrip.TabIndex = 0;
+            // 
+            // клиентыToolStripMenuItem
+            // 
+            this.клиентыToolStripMenuItem.Name = "клиентыToolStripMenuItem";
+            this.клиентыToolStripMenuItem.Size = new System.Drawing.Size(67, 20);
+            this.клиентыToolStripMenuItem.Text = "Клиенты";
+            // 
+            // блюдаToolStripMenuItem
+            // 
+            this.блюдаToolStripMenuItem.Name = "блюдаToolStripMenuItem";
+            this.блюдаToolStripMenuItem.Size = new System.Drawing.Size(56, 20);
+            this.блюдаToolStripMenuItem.Text = "Блюда";
+            // 
+            // сотрудникиToolStripMenuItem
+            // 
+            this.сотрудникиToolStripMenuItem.Name = "сотрудникиToolStripMenuItem";
+            this.сотрудникиToolStripMenuItem.Size = new System.Drawing.Size(84, 20);
+            this.сотрудникиToolStripMenuItem.Text = "Сотрудники";
+            // 
+            // заказыToolStripMenuItem
+            // 
+            this.заказыToolStripMenuItem.Name = "заказыToolStripMenuItem";
+            this.заказыToolStripMenuItem.Size = new System.Drawing.Size(61, 20);
+            this.заказыToolStripMenuItem.Text = "Заказы";
+            // 
+            // бронированияToolStripMenuItem
+            // 
+            this.бронированияToolStripMenuItem.Name = "бронированияToolStripMenuItem";
+            this.бронированияToolStripMenuItem.Size = new System.Drawing.Size(98, 20);
+            this.бронированияToolStripMenuItem.Text = "Бронирования";
+            // 
+            // отчётыToolStripMenuItem
+            // 
+            this.отчётыToolStripMenuItem.Name = "отчётыToolStripMenuItem";
+            this.отчётыToolStripMenuItem.Size = new System.Drawing.Size(60, 20);
+            this.отчётыToolStripMenuItem.Text = "Отчёты";
+            // 
+            // выходToolStripMenuItem
+            // 
+            this.выходToolStripMenuItem.Name = "выходToolStripMenuItem";
+            this.выходToolStripMenuItem.Size = new System.Drawing.Size(53, 20);
+            this.выходToolStripMenuItem.Text = "Выход";
+            // 
+            // statusStrip
+            // 
+            this.statusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+            this.lblUser});
+            this.statusStrip.Location = new System.Drawing.Point(0, 528);
+            this.statusStrip.Name = "statusStrip";
+            this.statusStrip.Size = new System.Drawing.Size(984, 22);
+            this.statusStrip.TabIndex = 1;
+            // 
+            // lblUser
+            // 
+            this.lblUser.Name = "lblUser";
+            this.lblUser.Size = new System.Drawing.Size(0, 17);
+            // 
+            // MainForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(984, 550);
+            this.Controls.Add(this.statusStrip);
+            this.Controls.Add(this.menuStrip);
+            this.IsMdiContainer = true;
+            this.MainMenuStrip = this.menuStrip;
+            this.Name = "MainForm";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+            this.Text = "Ресторан - Система управления";
+            this.WindowState = System.Windows.Forms.FormWindowState.Maximized;
+            this.menuStrip.ResumeLayout(false);
+            this.menuStrip.PerformLayout();
+            this.statusStrip.ResumeLayout(false);
+            this.statusStrip.PerformLayout();
+            this.ResumeLayout(false);
+            this.PerformLayout();
+        }
+
+        private System.Windows.Forms.MenuStrip menuStrip;
+        private System.Windows.Forms.ToolStripMenuItem клиентыToolStripMenuItem;
+        private System.Windows.Forms.ToolStripMenuItem блюдаToolStripMenuItem;
+        private System.Windows.Forms.ToolStripMenuItem сотрудникиToolStripMenuItem;
+        private System.Windows.Forms.ToolStripMenuItem заказыToolStripMenuItem;
+        private System.Windows.Forms.ToolStripMenuItem бронированияToolStripMenuItem;
+        private System.Windows.Forms.ToolStripMenuItem отчётыToolStripMenuItem;
+        private System.Windows.Forms.ToolStripMenuItem выходToolStripMenuItem;
+        private System.Windows.Forms.StatusStrip statusStrip;
+        private System.Windows.Forms.ToolStripStatusLabel lblUser;
+    }
+}

+ 41 - 0
MainForm.cs

@@ -0,0 +1,41 @@
+using System;
+using System.Windows.Forms;
+
+namespace RestaurantApp
+{
+    public partial class MainForm : Form
+    {
+        public MainForm()
+        {
+            InitializeComponent();
+
+            this.Text = $"Ресторан - {Program.CurrentUser}";
+            lblUser.Text = $"{Program.CurrentUser} ({Program.CurrentRole})";
+
+            // Разграничение прав доступа
+            bool isAdmin = Program.CurrentRole == "admin";
+            bool isManager = Program.CurrentRole == "manager";
+            bool isWaiter = Program.CurrentRole == "waiter";
+
+            // Только admin и manager могут редактировать сотрудников и смотреть отчёты
+            сотрудникиToolStripMenuItem.Enabled = isAdmin || isManager;
+            отчётыToolStripMenuItem.Enabled = isAdmin || isManager;
+
+            // Подписка на события меню
+            клиентыToolStripMenuItem.Click += (s, e) => OpenForm(new ClientForm());
+            блюдаToolStripMenuItem.Click += (s, e) => OpenForm(new DishForm());
+            сотрудникиToolStripMenuItem.Click += (s, e) => OpenForm(new EmployeeForm());
+            заказыToolStripMenuItem.Click += (s, e) => OpenForm(new OrderForm());
+            бронированияToolStripMenuItem.Click += (s, e) => OpenForm(new ReservationForm());
+            отчётыToolStripMenuItem.Click += (s, e) => OpenForm(new ReportForm());
+            выходToolStripMenuItem.Click += (s, e) => { this.Close(); Application.Exit(); };
+        }
+
+        private void OpenForm(Form form)
+        {
+            form.MdiParent = this;
+            form.WindowState = FormWindowState.Maximized;
+            form.Show();
+        }
+    }
+}

+ 126 - 0
MainForm.resx

@@ -0,0 +1,126 @@
+<?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>
+  <metadata name="menuStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>17, 17</value>
+  </metadata>
+  <metadata name="statusStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>126, 17</value>
+  </metadata>
+</root>

+ 155 - 0
OPERATOR_GUIDE.md

@@ -0,0 +1,155 @@
+# Руководство оператора программы RestaurantApp
+
+# Аннотация
+
+Настоящее руководство оператора описывает порядок работы с программой RestaurantApp. Документ предназначен для пользователей, которые выполняют учет клиентов, блюд, сотрудников, заказов, платежей и бронирований в информационной системе ресторана.
+
+RestaurantApp представляет собой настольное приложение Windows Forms, разработанное на языке C#. Программа работает с базой данных PostgreSQL и предоставляет пользователю графический интерфейс для выполнения основных операций ресторана.
+
+# Введение
+
+## Область применения
+
+Программа применяется в предметной области "Ресторан" и предназначена для автоматизации учета данных, связанных с обслуживанием посетителей. Система может использоваться администратором, менеджером и официантом.
+
+## Основные возможности
+
+- вход пользователя в систему;
+- отображение доступных разделов в зависимости от роли пользователя;
+- ведение клиентской базы;
+- ведение справочника блюд;
+- ведение списка сотрудников;
+- создание и сопровождение заказов;
+- добавление блюд в заказ;
+- проведение оплаты;
+- закрытие заказов;
+- учет бронирований столов;
+- просмотр аналитических отчетов.
+
+# Подготовка к работе
+
+## Порядок установки
+
+1. Убедиться, что рабочее место соответствует условиям эксплуатации:
+
+- установлена операционная система Windows;
+- установлен .NET Framework 4.7.2 или новее;
+- есть сетевой доступ к серверу PostgreSQL;
+- база данных приложения создана и заполнена необходимыми справочниками;
+- пользователь добавлен в таблицу `app_user`.
+
+2. Запустить файл установки `RestaurantAppSetup.exe`.
+3. В мастере установки выбрать папку установки или оставить значение по умолчанию.
+4. При необходимости включить создание ярлыка на рабочем столе.
+5. Дождаться завершения установки.
+6. Запустить программу через меню "Пуск" или ярлык на рабочем столе.
+
+## Порядок проверки работоспособности
+
+1. Запустить приложение RestaurantApp.
+2. Убедиться, что открылась форма авторизации.
+3. Ввести логин существующего пользователя, например `admin`, `manager` или `waiter`.
+4. Проверить, что после входа открывается главное окно программы.
+5. Открыть раздел "Клиенты" и убедиться, что список клиентов загружается из базы данных.
+6. Открыть раздел "Заказы" и проверить отображение заказов.
+7. Для пользователя с ролью `admin` или `manager` открыть раздел "Отчеты" и убедиться, что отчеты формируются без ошибок.
+8. Если данные отображаются и ошибки подключения отсутствуют, программа готова к работе.
+
+# Описание операций
+
+## Вход в систему
+
+Для начала работы оператор запускает программу и вводит логин в форме авторизации. После успешной проверки логина система открывает главное окно и показывает имя пользователя и его роль.
+
+Если логин отсутствует в базе данных, программа выводит сообщение об ошибке. В этом случае необходимо проверить правильность ввода или обратиться к администратору базы данных.
+
+## Работа в главном окне
+
+Главное окно содержит меню для перехода к разделам программы:
+
+- "Клиенты";
+- "Блюда";
+- "Сотрудники";
+- "Заказы";
+- "Бронирования";
+- "Отчеты";
+- "Выход".
+
+Доступность некоторых разделов зависит от роли пользователя. Разделы "Сотрудники" и "Отчеты" доступны только пользователям с ролями `admin` и `manager`.
+
+## Управление клиентами
+
+В разделе "Клиенты" оператор может просматривать таблицу клиентов, искать записи по строке поиска, добавлять нового клиента, редактировать выбранную запись и удалять клиента после подтверждения операции.
+
+Для добавления клиента необходимо нажать кнопку добавления, заполнить поля формы и сохранить запись. Для редактирования нужно выбрать клиента в таблице и открыть форму изменения данных.
+
+## Управление блюдами
+
+В разделе "Блюда" оператор ведет меню ресторана. Доступны поиск, фильтрация по категории, фильтрация по доступности, добавление, редактирование и удаление блюд.
+
+При добавлении или редактировании блюда указываются категория, название, описание, цена, вес, время приготовления и признак доступности.
+
+## Управление сотрудниками
+
+Раздел "Сотрудники" используется для учета работников ресторана. Оператор с правами `admin` или `manager` может добавлять сотрудников, изменять их должность, контактные данные, дату приема, оклад и статус активности.
+
+Удаление сотрудника в программе используется как операция увольнения или деактивации записи.
+
+## Управление заказами
+
+В разделе "Заказы" оператор может создать новый заказ, выбрать существующий заказ, добавить блюдо, изменить количество или примечание к позиции, удалить позицию, провести оплату и закрыть заказ.
+
+При добавлении блюда система пересчитывает сумму заказа. Оплата выполняется через отдельную форму, где выбирается способ оплаты и при наличии клиента могут использоваться бонусы.
+
+## Управление бронированиями
+
+В разделе "Бронирования" оператор просматривает записи о резервировании столов, выполняет поиск, фильтрует записи по статусу, добавляет новые бронирования, редактирует и удаляет выбранные записи.
+
+При выборе бронирования в таблице отображаются сведения о столе, клиенте, количестве гостей, дате, времени, длительности и примечании.
+
+## Просмотр отчетов
+
+Раздел "Отчеты" доступен пользователям с ролями `admin` и `manager`. Оператор выбирает период отчета и нажимает кнопку отображения данных.
+
+В программе доступны следующие отчеты:
+
+- дневная выручка;
+- продажи блюд;
+- статистика официантов;
+- способы оплаты;
+- ингредиенты с низким остатком.
+
+## Завершение работы
+
+Для завершения работы оператор выбирает пункт "Выход" в меню или закрывает главное окно приложения. После этого программа завершает сеанс работы.
+
+# Аварийные ситуации
+
+| Ситуация | Возможная причина | Действия оператора |
+| - | - | - |
+| Программа не запускается | Не установлен .NET Framework 4.7.2 или повреждены файлы приложения | Установить .NET Framework, переустановить приложение |
+| Ошибка подключения к базе данных | Нет сети, недоступен сервер PostgreSQL, неверные параметры подключения | Проверить интернет/сеть, повторить запуск, обратиться к администратору |
+| Логин не найден | Пользователь отсутствует в таблице `app_user` или введен неверный логин | Проверить ввод логина, обратиться к администратору |
+| Данные не загружаются | Ошибка SQL-запроса, нет доступа к таблице, отсутствует нужная структура БД | Сообщить администратору БД текст ошибки |
+| Нельзя удалить запись | Запись связана с другими таблицами или ограничена правилами БД | Проверить связанные данные, отменить удаление или обратиться к администратору |
+| Раздел недоступен | У пользователя недостаточно прав | Войти под пользователем с ролью `admin` или `manager` |
+| Отчет не формируется | Нет данных за выбранный период или возникла ошибка запроса | Изменить период отчета, повторить операцию, сообщить администратору |
+
+# Термины и сокращения
+
+| Термин | Описание |
+| - | - |
+| Оператор | Пользователь, работающий с программой |
+| Администратор | Пользователь с полным доступом к функциям системы |
+| Менеджер | Пользователь с доступом к управлению сотрудниками и отчетам |
+| Официант | Пользователь, работающий с заказами и клиентами |
+| ИС | Информационная система |
+| БД | База данных |
+| СУБД | Система управления базами данных |
+| PostgreSQL | СУБД, используемая для хранения данных ресторана |
+| Windows Forms | Технология разработки графических приложений Windows |
+| Npgsql | Драйвер подключения приложения C# к PostgreSQL |
+| Заказ | Операция обслуживания клиента или стола |
+| Платеж | Операция оплаты заказа |
+| Бронирование | Резервирование стола на дату и время |
+| Роль | Набор прав пользователя в системе |

+ 34 - 0
Order.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+
+namespace RestaurantApp.Models
+{
+    public class Order
+    {
+        public int OrderId { get; set; }
+        public int TableId { get; set; }
+        public string TableInfo { get; set; }
+        public int EmployeeId { get; set; }
+        public string EmployeeName { get; set; }
+        public int? ClientId { get; set; }
+        public string ClientName { get; set; }
+        public DateTime OpenedAt { get; set; }
+        public DateTime? ClosedAt { get; set; }
+        public string Status { get; set; }
+        public decimal DiscountPct { get; set; }
+        public decimal TotalAmount { get; set; }
+    }
+
+    public class OrderItem
+    {
+        public int OrderItemId { get; set; }
+        public int OrderId { get; set; }
+        public int DishId { get; set; }
+        public string DishName { get; set; }
+        public int Quantity { get; set; }
+        public decimal UnitPrice { get; set; }
+        public string Notes { get; set; }
+        public string Status { get; set; }
+        public decimal Total => Quantity * UnitPrice;
+    }
+}

+ 170 - 0
OrderEditForm.Designer.cs

@@ -0,0 +1,170 @@
+namespace RestaurantApp
+{
+    partial class OrderEditForm
+    {
+        private System.ComponentModel.IContainer components = null;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.lblTable = new System.Windows.Forms.Label();
+            this.cmbTable = new System.Windows.Forms.ComboBox();
+            this.lblWaiter = new System.Windows.Forms.Label();
+            this.cmbWaiter = new System.Windows.Forms.ComboBox();
+            this.lblClient = new System.Windows.Forms.Label();
+            this.cmbClient = new System.Windows.Forms.ComboBox();
+            this.lblDiscount = new System.Windows.Forms.Label();
+            this.numDiscount = new System.Windows.Forms.NumericUpDown();
+            this.btnSave = new System.Windows.Forms.Button();
+            this.btnCancel = new System.Windows.Forms.Button();
+            this.chkNoClient = new System.Windows.Forms.CheckBox();
+            ((System.ComponentModel.ISupportInitialize)(this.numDiscount)).BeginInit();
+            this.SuspendLayout();
+            // 
+            // lblTable
+            // 
+            this.lblTable.AutoSize = true;
+            this.lblTable.Location = new System.Drawing.Point(30, 30);
+            this.lblTable.Name = "lblTable";
+            this.lblTable.Size = new System.Drawing.Size(38, 13);
+            this.lblTable.TabIndex = 0;
+            this.lblTable.Text = "Стол:";
+            // 
+            // cmbTable
+            // 
+            this.cmbTable.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbTable.FormattingEnabled = true;
+            this.cmbTable.Location = new System.Drawing.Point(120, 27);
+            this.cmbTable.Name = "cmbTable";
+            this.cmbTable.Size = new System.Drawing.Size(250, 21);
+            this.cmbTable.TabIndex = 1;
+            // 
+            // lblWaiter
+            // 
+            this.lblWaiter.AutoSize = true;
+            this.lblWaiter.Location = new System.Drawing.Point(30, 60);
+            this.lblWaiter.Name = "lblWaiter";
+            this.lblWaiter.Size = new System.Drawing.Size(59, 13);
+            this.lblWaiter.TabIndex = 2;
+            this.lblWaiter.Text = "Официант:";
+            // 
+            // cmbWaiter
+            // 
+            this.cmbWaiter.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbWaiter.FormattingEnabled = true;
+            this.cmbWaiter.Location = new System.Drawing.Point(120, 57);
+            this.cmbWaiter.Name = "cmbWaiter";
+            this.cmbWaiter.Size = new System.Drawing.Size(250, 21);
+            this.cmbWaiter.TabIndex = 3;
+            // 
+            // lblClient
+            // 
+            this.lblClient.AutoSize = true;
+            this.lblClient.Location = new System.Drawing.Point(30, 90);
+            this.lblClient.Name = "lblClient";
+            this.lblClient.Size = new System.Drawing.Size(46, 13);
+            this.lblClient.TabIndex = 4;
+            this.lblClient.Text = "Клиент:";
+            // 
+            // cmbClient
+            // 
+            this.cmbClient.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbClient.FormattingEnabled = true;
+            this.cmbClient.Location = new System.Drawing.Point(120, 87);
+            this.cmbClient.Name = "cmbClient";
+            this.cmbClient.Size = new System.Drawing.Size(250, 21);
+            this.cmbClient.TabIndex = 5;
+            // 
+            // chkNoClient
+            // 
+            this.chkNoClient.AutoSize = true;
+            this.chkNoClient.Location = new System.Drawing.Point(120, 115);
+            this.chkNoClient.Name = "chkNoClient";
+            this.chkNoClient.Size = new System.Drawing.Size(103, 17);
+            this.chkNoClient.TabIndex = 6;
+            this.chkNoClient.Text = "Без клиента";
+            this.chkNoClient.UseVisualStyleBackColor = true;
+            // 
+            // lblDiscount
+            // 
+            this.lblDiscount.AutoSize = true;
+            this.lblDiscount.Location = new System.Drawing.Point(30, 145);
+            this.lblDiscount.Name = "lblDiscount";
+            this.lblDiscount.Size = new System.Drawing.Size(52, 13);
+            this.lblDiscount.TabIndex = 7;
+            this.lblDiscount.Text = "Скидка %:";
+            // 
+            // numDiscount
+            // 
+            this.numDiscount.Location = new System.Drawing.Point(120, 143);
+            this.numDiscount.Name = "numDiscount";
+            this.numDiscount.Size = new System.Drawing.Size(100, 20);
+            this.numDiscount.TabIndex = 8;
+            // 
+            // btnSave
+            // 
+            this.btnSave.BackColor = System.Drawing.Color.LimeGreen;
+            this.btnSave.Location = new System.Drawing.Point(120, 190);
+            this.btnSave.Name = "btnSave";
+            this.btnSave.Size = new System.Drawing.Size(100, 30);
+            this.btnSave.TabIndex = 9;
+            this.btnSave.Text = "Создать заказ";
+            this.btnSave.UseVisualStyleBackColor = false;
+            // 
+            // btnCancel
+            // 
+            this.btnCancel.Location = new System.Drawing.Point(240, 190);
+            this.btnCancel.Name = "btnCancel";
+            this.btnCancel.Size = new System.Drawing.Size(100, 30);
+            this.btnCancel.TabIndex = 10;
+            this.btnCancel.Text = "Отмена";
+            this.btnCancel.UseVisualStyleBackColor = true;
+            // 
+            // OrderEditForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(400, 250);
+            this.Controls.Add(this.btnCancel);
+            this.Controls.Add(this.btnSave);
+            this.Controls.Add(this.numDiscount);
+            this.Controls.Add(this.lblDiscount);
+            this.Controls.Add(this.chkNoClient);
+            this.Controls.Add(this.cmbClient);
+            this.Controls.Add(this.lblClient);
+            this.Controls.Add(this.cmbWaiter);
+            this.Controls.Add(this.lblWaiter);
+            this.Controls.Add(this.cmbTable);
+            this.Controls.Add(this.lblTable);
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+            this.MaximizeBox = false;
+            this.MinimizeBox = false;
+            this.Name = "OrderEditForm";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+            this.Text = "Новый заказ";
+            ((System.ComponentModel.ISupportInitialize)(this.numDiscount)).EndInit();
+            this.ResumeLayout(false);
+            this.PerformLayout();
+        }
+
+        private System.Windows.Forms.Label lblTable;
+        private System.Windows.Forms.ComboBox cmbTable;
+        private System.Windows.Forms.Label lblWaiter;
+        private System.Windows.Forms.ComboBox cmbWaiter;
+        private System.Windows.Forms.Label lblClient;
+        private System.Windows.Forms.ComboBox cmbClient;
+        private System.Windows.Forms.CheckBox chkNoClient;
+        private System.Windows.Forms.Label lblDiscount;
+        private System.Windows.Forms.NumericUpDown numDiscount;
+        private System.Windows.Forms.Button btnSave;
+        private System.Windows.Forms.Button btnCancel;
+    }
+}

+ 96 - 0
OrderEditForm.cs

@@ -0,0 +1,96 @@
+using RestaurantApp.DAL;
+using RestaurantApp.Models;
+using System;
+using System.Data;
+using System.Linq;
+using System.Net;
+using System.Windows.Forms;
+
+namespace RestaurantApp
+{
+    public partial class OrderEditForm : Form
+    {
+        private OrderRepository repo = new OrderRepository();
+        private EmployeeRepository empRepo = new EmployeeRepository();
+        private ClientRepository clientRepo = new ClientRepository();
+
+        public OrderEditForm()
+        {
+            InitializeComponent();
+            LoadData();
+
+            chkNoClient.CheckedChanged += (s, e) =>
+            {
+                cmbClient.Enabled = !chkNoClient.Checked;
+                if (chkNoClient.Checked) cmbClient.SelectedIndex = -1;
+            };
+
+            btnSave.Click += BtnSave_Click;
+            btnCancel.Click += (s, e) => DialogResult = DialogResult.Cancel;
+        }
+
+        private void LoadData()
+        {
+            // Загружаем столы
+            var tables = repo.GetTables();
+            cmbTable.DisplayMember = "info";
+            cmbTable.ValueMember = "table_id";
+            cmbTable.DataSource = tables;
+
+            // Загружаем официантов
+            var waiters = empRepo.GetWaiters();
+            cmbWaiter.DisplayMember = "FullName";
+            cmbWaiter.ValueMember = "EmployeeId";
+            cmbWaiter.DataSource = waiters;
+
+            // Загружаем клиентов
+            var clients = clientRepo.GetAll();
+            var clientList = clients.Select(c => new { c.ClientId, FullName = $"{c.LastName} {c.FirstName} ({c.Phone})" }).ToList();
+            cmbClient.DisplayMember = "FullName";
+            cmbClient.ValueMember = "ClientId";
+            cmbClient.DataSource = clientList;
+
+            // Выбираем текущего пользователя как официанта
+            if (!string.IsNullOrEmpty(Program.CurrentRole) && Program.CurrentRole == "waiter")
+            {
+                var currentWaiter = waiters.FirstOrDefault(w => w.FullName == Program.CurrentUser);
+                if (currentWaiter != null)
+                    cmbWaiter.SelectedValue = currentWaiter.EmployeeId;
+            }
+        }
+
+        private void BtnSave_Click(object sender, EventArgs e)
+        {
+            if (cmbTable.SelectedItem == null)
+            {
+                MessageBox.Show("Выберите стол");
+                return;
+            }
+
+            if (cmbWaiter.SelectedItem == null)
+            {
+                MessageBox.Show("Выберите официанта");
+                return;
+            }
+
+            var order = new Order
+            {
+                TableId = (int)cmbTable.SelectedValue,
+                EmployeeId = (int)cmbWaiter.SelectedValue,
+                ClientId = chkNoClient.Checked ? (int?)null : (int?)cmbClient.SelectedValue,
+                DiscountPct = numDiscount.Value
+            };
+
+            try
+            {
+                int orderId = repo.InsertOrder(order);
+                DialogResult = DialogResult.OK;
+                Close();
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}");
+            }
+        }
+    }
+}

+ 120 - 0
OrderEditForm.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>

+ 468 - 0
OrderForm.Designer.cs

@@ -0,0 +1,468 @@
+namespace RestaurantApp
+{
+    partial class OrderForm
+    {
+        private System.ComponentModel.IContainer components = null;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.splitContainer = new System.Windows.Forms.SplitContainer();
+            this.groupOrders = new System.Windows.Forms.GroupBox();
+            this.dgvOrders = new System.Windows.Forms.DataGridView();
+            this.panelOrdersTop = new System.Windows.Forms.Panel();
+            this.cmbStatusFilter = new System.Windows.Forms.ComboBox();
+            this.txtOrderSearch = new System.Windows.Forms.TextBox();
+            this.btnOrderSearch = new System.Windows.Forms.Button();
+            this.btnRefreshOrders = new System.Windows.Forms.Button();
+            this.btnNewOrder = new System.Windows.Forms.Button();
+            this.groupOrderDetails = new System.Windows.Forms.GroupBox();
+            this.dgvOrderItems = new System.Windows.Forms.DataGridView();
+            this.panelOrderInfo = new System.Windows.Forms.Panel();
+            this.btnCloseOrder = new System.Windows.Forms.Button();
+            this.btnPay = new System.Windows.Forms.Button();
+            this.btnRemoveItem = new System.Windows.Forms.Button();
+            this.btnEditItem = new System.Windows.Forms.Button();
+            this.btnAddItem = new System.Windows.Forms.Button();
+            this.lblStatus = new System.Windows.Forms.Label();
+            this.lblTotal = new System.Windows.Forms.Label();
+            this.lblClient = new System.Windows.Forms.Label();
+            this.lblOpenedAt = new System.Windows.Forms.Label();
+            this.lblWaiter = new System.Windows.Forms.Label();
+            this.lblTable = new System.Windows.Forms.Label();
+            this.groupAddItem = new System.Windows.Forms.GroupBox();
+            this.btnAddDish = new System.Windows.Forms.Button();
+            this.lblNotes = new System.Windows.Forms.Label();
+            this.lblQuantity = new System.Windows.Forms.Label();
+            this.lblDish = new System.Windows.Forms.Label();
+            this.txtItemNotes = new System.Windows.Forms.TextBox();
+            this.numQuantity = new System.Windows.Forms.NumericUpDown();
+            this.cmbDish = new System.Windows.Forms.ComboBox();
+            ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit();
+            this.splitContainer.Panel1.SuspendLayout();
+            this.splitContainer.Panel2.SuspendLayout();
+            this.splitContainer.SuspendLayout();
+            this.groupOrders.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvOrders)).BeginInit();
+            this.panelOrdersTop.SuspendLayout();
+            this.groupOrderDetails.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvOrderItems)).BeginInit();
+            this.panelOrderInfo.SuspendLayout();
+            this.groupAddItem.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.numQuantity)).BeginInit();
+            this.SuspendLayout();
+            // 
+            // splitContainer
+            // 
+            this.splitContainer.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.splitContainer.Location = new System.Drawing.Point(0, 0);
+            this.splitContainer.Name = "splitContainer";
+            this.splitContainer.Orientation = System.Windows.Forms.Orientation.Horizontal;
+            // 
+            // splitContainer.Panel1
+            // 
+            this.splitContainer.Panel1.Controls.Add(this.groupOrders);
+            // 
+            // splitContainer.Panel2
+            // 
+            this.splitContainer.Panel2.Controls.Add(this.groupOrderDetails);
+            this.splitContainer.Size = new System.Drawing.Size(1100, 700);
+            this.splitContainer.SplitterDistance = 350;
+            this.splitContainer.TabIndex = 0;
+            // 
+            // groupOrders
+            // 
+            this.groupOrders.Controls.Add(this.dgvOrders);
+            this.groupOrders.Controls.Add(this.panelOrdersTop);
+            this.groupOrders.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.groupOrders.Location = new System.Drawing.Point(0, 0);
+            this.groupOrders.Name = "groupOrders";
+            this.groupOrders.Size = new System.Drawing.Size(1100, 350);
+            this.groupOrders.TabIndex = 0;
+            this.groupOrders.TabStop = false;
+            this.groupOrders.Text = "Заказы";
+            // 
+            // dgvOrders
+            // 
+            this.dgvOrders.AllowUserToAddRows = false;
+            this.dgvOrders.AllowUserToDeleteRows = false;
+            this.dgvOrders.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
+            this.dgvOrders.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
+            this.dgvOrders.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.dgvOrders.Location = new System.Drawing.Point(3, 56);
+            this.dgvOrders.Name = "dgvOrders";
+            this.dgvOrders.ReadOnly = true;
+            this.dgvOrders.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
+            this.dgvOrders.Size = new System.Drawing.Size(1094, 291);
+            this.dgvOrders.TabIndex = 1;
+            // 
+            // panelOrdersTop
+            // 
+            this.panelOrdersTop.Controls.Add(this.cmbStatusFilter);
+            this.panelOrdersTop.Controls.Add(this.txtOrderSearch);
+            this.panelOrdersTop.Controls.Add(this.btnOrderSearch);
+            this.panelOrdersTop.Controls.Add(this.btnRefreshOrders);
+            this.panelOrdersTop.Controls.Add(this.btnNewOrder);
+            this.panelOrdersTop.Dock = System.Windows.Forms.DockStyle.Top;
+            this.panelOrdersTop.Location = new System.Drawing.Point(3, 16);
+            this.panelOrdersTop.Name = "panelOrdersTop";
+            this.panelOrdersTop.Size = new System.Drawing.Size(1094, 40);
+            this.panelOrdersTop.TabIndex = 0;
+            // 
+            // cmbStatusFilter
+            // 
+            this.cmbStatusFilter.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbStatusFilter.Items.AddRange(new object[] {
+            "Все",
+            "open",
+            "in_progress",
+            "ready",
+            "closed",
+            "cancelled"});
+            this.cmbStatusFilter.Location = new System.Drawing.Point(10, 10);
+            this.cmbStatusFilter.Name = "cmbStatusFilter";
+            this.cmbStatusFilter.Size = new System.Drawing.Size(120, 21);
+            this.cmbStatusFilter.TabIndex = 4;
+            // 
+            // txtOrderSearch
+            // 
+            this.txtOrderSearch.Location = new System.Drawing.Point(140, 10);
+            this.txtOrderSearch.Name = "txtOrderSearch";
+            this.txtOrderSearch.Size = new System.Drawing.Size(150, 20);
+            this.txtOrderSearch.TabIndex = 3;
+            // 
+            // btnOrderSearch
+            // 
+            this.btnOrderSearch.Location = new System.Drawing.Point(300, 8);
+            this.btnOrderSearch.Name = "btnOrderSearch";
+            this.btnOrderSearch.Size = new System.Drawing.Size(75, 23);
+            this.btnOrderSearch.TabIndex = 2;
+            this.btnOrderSearch.Text = "Поиск";
+            this.btnOrderSearch.UseVisualStyleBackColor = true;
+            // 
+            // btnRefreshOrders
+            // 
+            this.btnRefreshOrders.Location = new System.Drawing.Point(385, 8);
+            this.btnRefreshOrders.Name = "btnRefreshOrders";
+            this.btnRefreshOrders.Size = new System.Drawing.Size(75, 23);
+            this.btnRefreshOrders.TabIndex = 1;
+            this.btnRefreshOrders.Text = "Обновить";
+            this.btnRefreshOrders.UseVisualStyleBackColor = true;
+            // 
+            // btnNewOrder
+            // 
+            this.btnNewOrder.BackColor = System.Drawing.Color.DodgerBlue;
+            this.btnNewOrder.ForeColor = System.Drawing.Color.White;
+            this.btnNewOrder.Location = new System.Drawing.Point(1000, 8);
+            this.btnNewOrder.Name = "btnNewOrder";
+            this.btnNewOrder.Size = new System.Drawing.Size(90, 23);
+            this.btnNewOrder.TabIndex = 0;
+            this.btnNewOrder.Text = "Новый заказ";
+            this.btnNewOrder.UseVisualStyleBackColor = false;
+            // 
+            // groupOrderDetails
+            // 
+            this.groupOrderDetails.Controls.Add(this.dgvOrderItems);
+            this.groupOrderDetails.Controls.Add(this.panelOrderInfo);
+            this.groupOrderDetails.Controls.Add(this.groupAddItem);
+            this.groupOrderDetails.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.groupOrderDetails.Location = new System.Drawing.Point(0, 0);
+            this.groupOrderDetails.Name = "groupOrderDetails";
+            this.groupOrderDetails.Size = new System.Drawing.Size(1100, 346);
+            this.groupOrderDetails.TabIndex = 0;
+            this.groupOrderDetails.TabStop = false;
+            this.groupOrderDetails.Text = "Детали заказа";
+            // 
+            // dgvOrderItems
+            // 
+            this.dgvOrderItems.AllowUserToAddRows = false;
+            this.dgvOrderItems.AllowUserToDeleteRows = false;
+            this.dgvOrderItems.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
+            this.dgvOrderItems.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
+            this.dgvOrderItems.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.dgvOrderItems.Location = new System.Drawing.Point(3, 130);
+            this.dgvOrderItems.Name = "dgvOrderItems";
+            this.dgvOrderItems.ReadOnly = true;
+            this.dgvOrderItems.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
+            this.dgvOrderItems.Size = new System.Drawing.Size(694, 213);
+            this.dgvOrderItems.TabIndex = 2;
+            // 
+            // panelOrderInfo
+            // 
+            this.panelOrderInfo.Controls.Add(this.btnCloseOrder);
+            this.panelOrderInfo.Controls.Add(this.btnPay);
+            this.panelOrderInfo.Controls.Add(this.btnRemoveItem);
+            this.panelOrderInfo.Controls.Add(this.btnEditItem);
+            this.panelOrderInfo.Controls.Add(this.btnAddItem);
+            this.panelOrderInfo.Controls.Add(this.lblStatus);
+            this.panelOrderInfo.Controls.Add(this.lblTotal);
+            this.panelOrderInfo.Controls.Add(this.lblClient);
+            this.panelOrderInfo.Controls.Add(this.lblOpenedAt);
+            this.panelOrderInfo.Controls.Add(this.lblWaiter);
+            this.panelOrderInfo.Controls.Add(this.lblTable);
+            this.panelOrderInfo.Dock = System.Windows.Forms.DockStyle.Top;
+            this.panelOrderInfo.Location = new System.Drawing.Point(3, 16);
+            this.panelOrderInfo.Name = "panelOrderInfo";
+            this.panelOrderInfo.Size = new System.Drawing.Size(694, 114);
+            this.panelOrderInfo.TabIndex = 1;
+            // 
+            // btnCloseOrder
+            // 
+            this.btnCloseOrder.BackColor = System.Drawing.Color.Orange;
+            this.btnCloseOrder.Location = new System.Drawing.Point(500, 55);
+            this.btnCloseOrder.Name = "btnCloseOrder";
+            this.btnCloseOrder.Size = new System.Drawing.Size(90, 35);
+            this.btnCloseOrder.TabIndex = 10;
+            this.btnCloseOrder.Text = "Закрыть заказ";
+            this.btnCloseOrder.UseVisualStyleBackColor = false;
+            // 
+            // btnPay
+            // 
+            this.btnPay.BackColor = System.Drawing.Color.Green;
+            this.btnPay.ForeColor = System.Drawing.Color.White;
+            this.btnPay.Location = new System.Drawing.Point(500, 10);
+            this.btnPay.Name = "btnPay";
+            this.btnPay.Size = new System.Drawing.Size(90, 35);
+            this.btnPay.TabIndex = 9;
+            this.btnPay.Text = "Оплата";
+            this.btnPay.UseVisualStyleBackColor = false;
+            // 
+            // btnRemoveItem
+            // 
+            this.btnRemoveItem.Location = new System.Drawing.Point(300, 80);
+            this.btnRemoveItem.Name = "btnRemoveItem";
+            this.btnRemoveItem.Size = new System.Drawing.Size(90, 23);
+            this.btnRemoveItem.TabIndex = 8;
+            this.btnRemoveItem.Text = "Удалить";
+            this.btnRemoveItem.UseVisualStyleBackColor = true;
+            // 
+            // btnEditItem
+            // 
+            this.btnEditItem.Location = new System.Drawing.Point(300, 55);
+            this.btnEditItem.Name = "btnEditItem";
+            this.btnEditItem.Size = new System.Drawing.Size(90, 23);
+            this.btnEditItem.TabIndex = 7;
+            this.btnEditItem.Text = "Изменить";
+            this.btnEditItem.UseVisualStyleBackColor = true;
+            // 
+            // btnAddItem
+            // 
+            this.btnAddItem.Location = new System.Drawing.Point(300, 30);
+            this.btnAddItem.Name = "btnAddItem";
+            this.btnAddItem.Size = new System.Drawing.Size(90, 23);
+            this.btnAddItem.TabIndex = 6;
+            this.btnAddItem.Text = "Добавить";
+            this.btnAddItem.UseVisualStyleBackColor = true;
+            // 
+            // lblStatus
+            // 
+            this.lblStatus.AutoSize = true;
+            this.lblStatus.Location = new System.Drawing.Point(300, 10);
+            this.lblStatus.Name = "lblStatus";
+            this.lblStatus.Size = new System.Drawing.Size(44, 13);
+            this.lblStatus.TabIndex = 5;
+            this.lblStatus.Text = "Статус:";
+            // 
+            // lblTotal
+            // 
+            this.lblTotal.AutoSize = true;
+            this.lblTotal.Font = new System.Drawing.Font("Microsoft Sans Serif", 10F, System.Drawing.FontStyle.Bold);
+            this.lblTotal.Location = new System.Drawing.Point(10, 90);
+            this.lblTotal.Name = "lblTotal";
+            this.lblTotal.Size = new System.Drawing.Size(88, 17);
+            this.lblTotal.TabIndex = 4;
+            this.lblTotal.Text = "Сумма: 0 ₽";
+            // 
+            // lblClient
+            // 
+            this.lblClient.AutoSize = true;
+            this.lblClient.Location = new System.Drawing.Point(10, 70);
+            this.lblClient.Name = "lblClient";
+            this.lblClient.Size = new System.Drawing.Size(46, 13);
+            this.lblClient.TabIndex = 3;
+            this.lblClient.Text = "Клиент:";
+            // 
+            // lblOpenedAt
+            // 
+            this.lblOpenedAt.AutoSize = true;
+            this.lblOpenedAt.Location = new System.Drawing.Point(10, 50);
+            this.lblOpenedAt.Name = "lblOpenedAt";
+            this.lblOpenedAt.Size = new System.Drawing.Size(51, 13);
+            this.lblOpenedAt.TabIndex = 2;
+            this.lblOpenedAt.Text = "Открыт: ";
+            // 
+            // lblWaiter
+            // 
+            this.lblWaiter.AutoSize = true;
+            this.lblWaiter.Location = new System.Drawing.Point(10, 30);
+            this.lblWaiter.Name = "lblWaiter";
+            this.lblWaiter.Size = new System.Drawing.Size(61, 13);
+            this.lblWaiter.TabIndex = 1;
+            this.lblWaiter.Text = "Официант:";
+            // 
+            // lblTable
+            // 
+            this.lblTable.AutoSize = true;
+            this.lblTable.Location = new System.Drawing.Point(10, 10);
+            this.lblTable.Name = "lblTable";
+            this.lblTable.Size = new System.Drawing.Size(37, 13);
+            this.lblTable.TabIndex = 0;
+            this.lblTable.Text = "Стол: ";
+            // 
+            // groupAddItem
+            // 
+            this.groupAddItem.Controls.Add(this.btnAddDish);
+            this.groupAddItem.Controls.Add(this.lblNotes);
+            this.groupAddItem.Controls.Add(this.lblQuantity);
+            this.groupAddItem.Controls.Add(this.lblDish);
+            this.groupAddItem.Controls.Add(this.txtItemNotes);
+            this.groupAddItem.Controls.Add(this.numQuantity);
+            this.groupAddItem.Controls.Add(this.cmbDish);
+            this.groupAddItem.Dock = System.Windows.Forms.DockStyle.Right;
+            this.groupAddItem.Location = new System.Drawing.Point(697, 16);
+            this.groupAddItem.Name = "groupAddItem";
+            this.groupAddItem.Size = new System.Drawing.Size(400, 327);
+            this.groupAddItem.TabIndex = 0;
+            this.groupAddItem.TabStop = false;
+            this.groupAddItem.Text = "Добавить блюдо";
+            // 
+            // btnAddDish
+            // 
+            this.btnAddDish.BackColor = System.Drawing.Color.LimeGreen;
+            this.btnAddDish.Location = new System.Drawing.Point(20, 250);
+            this.btnAddDish.Name = "btnAddDish";
+            this.btnAddDish.Size = new System.Drawing.Size(120, 30);
+            this.btnAddDish.TabIndex = 6;
+            this.btnAddDish.Text = "Добавить в заказ";
+            this.btnAddDish.UseVisualStyleBackColor = false;
+            // 
+            // lblNotes
+            // 
+            this.lblNotes.AutoSize = true;
+            this.lblNotes.Location = new System.Drawing.Point(17, 150);
+            this.lblNotes.Name = "lblNotes";
+            this.lblNotes.Size = new System.Drawing.Size(73, 13);
+            this.lblNotes.TabIndex = 5;
+            this.lblNotes.Text = "Примечание:";
+            // 
+            // lblQuantity
+            // 
+            this.lblQuantity.AutoSize = true;
+            this.lblQuantity.Location = new System.Drawing.Point(17, 90);
+            this.lblQuantity.Name = "lblQuantity";
+            this.lblQuantity.Size = new System.Drawing.Size(69, 13);
+            this.lblQuantity.TabIndex = 4;
+            this.lblQuantity.Text = "Количество:";
+            // 
+            // lblDish
+            // 
+            this.lblDish.AutoSize = true;
+            this.lblDish.Location = new System.Drawing.Point(17, 30);
+            this.lblDish.Name = "lblDish";
+            this.lblDish.Size = new System.Drawing.Size(43, 13);
+            this.lblDish.TabIndex = 3;
+            this.lblDish.Text = "Блюдо:";
+            // 
+            // txtItemNotes
+            // 
+            this.txtItemNotes.Location = new System.Drawing.Point(20, 170);
+            this.txtItemNotes.Multiline = true;
+            this.txtItemNotes.Name = "txtItemNotes";
+            this.txtItemNotes.Size = new System.Drawing.Size(360, 60);
+            this.txtItemNotes.TabIndex = 2;
+            // 
+            // numQuantity
+            // 
+            this.numQuantity.Location = new System.Drawing.Point(20, 110);
+            this.numQuantity.Minimum = new decimal(new int[] {
+            1,
+            0,
+            0,
+            0});
+            this.numQuantity.Name = "numQuantity";
+            this.numQuantity.Size = new System.Drawing.Size(100, 20);
+            this.numQuantity.TabIndex = 1;
+            this.numQuantity.Value = new decimal(new int[] {
+            1,
+            0,
+            0,
+            0});
+            // 
+            // cmbDish
+            // 
+            this.cmbDish.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbDish.FormattingEnabled = true;
+            this.cmbDish.Location = new System.Drawing.Point(20, 50);
+            this.cmbDish.Name = "cmbDish";
+            this.cmbDish.Size = new System.Drawing.Size(360, 21);
+            this.cmbDish.TabIndex = 0;
+            // 
+            // OrderForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(1100, 700);
+            this.Controls.Add(this.splitContainer);
+            this.Name = "OrderForm";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+            this.Text = "Управление заказами";
+            this.splitContainer.Panel1.ResumeLayout(false);
+            this.splitContainer.Panel2.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).EndInit();
+            this.splitContainer.ResumeLayout(false);
+            this.groupOrders.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.dgvOrders)).EndInit();
+            this.panelOrdersTop.ResumeLayout(false);
+            this.panelOrdersTop.PerformLayout();
+            this.groupOrderDetails.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.dgvOrderItems)).EndInit();
+            this.panelOrderInfo.ResumeLayout(false);
+            this.panelOrderInfo.PerformLayout();
+            this.groupAddItem.ResumeLayout(false);
+            this.groupAddItem.PerformLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.numQuantity)).EndInit();
+            this.ResumeLayout(false);
+
+        }
+
+        private System.Windows.Forms.SplitContainer splitContainer;
+        private System.Windows.Forms.GroupBox groupOrders;
+        private System.Windows.Forms.DataGridView dgvOrders;
+        private System.Windows.Forms.Panel panelOrdersTop;
+        private System.Windows.Forms.ComboBox cmbStatusFilter;
+        private System.Windows.Forms.TextBox txtOrderSearch;
+        private System.Windows.Forms.Button btnOrderSearch;
+        private System.Windows.Forms.Button btnRefreshOrders;
+        private System.Windows.Forms.Button btnNewOrder;
+        private System.Windows.Forms.GroupBox groupOrderDetails;
+        private System.Windows.Forms.DataGridView dgvOrderItems;
+        private System.Windows.Forms.Panel panelOrderInfo;
+        private System.Windows.Forms.Label lblTable;
+        private System.Windows.Forms.Label lblWaiter;
+        private System.Windows.Forms.Label lblOpenedAt;
+        private System.Windows.Forms.Label lblClient;
+        private System.Windows.Forms.Label lblTotal;
+        private System.Windows.Forms.Label lblStatus;
+        private System.Windows.Forms.Button btnCloseOrder;
+        private System.Windows.Forms.Button btnPay;
+        private System.Windows.Forms.Button btnRemoveItem;
+        private System.Windows.Forms.Button btnEditItem;
+        private System.Windows.Forms.Button btnAddItem;
+        private System.Windows.Forms.GroupBox groupAddItem;
+        private System.Windows.Forms.Button btnAddDish;
+        private System.Windows.Forms.Label lblNotes;
+        private System.Windows.Forms.Label lblQuantity;
+        private System.Windows.Forms.Label lblDish;
+        private System.Windows.Forms.TextBox txtItemNotes;
+        private System.Windows.Forms.NumericUpDown numQuantity;
+        private System.Windows.Forms.ComboBox cmbDish;
+    }
+}

+ 250 - 0
OrderForm.cs

@@ -0,0 +1,250 @@
+using System;
+using System.Data;
+using System.Linq;
+using System.Windows.Forms;
+using RestaurantApp.DAL;
+using RestaurantApp.Models;
+
+namespace RestaurantApp
+{
+    public partial class OrderForm : Form
+    {
+        private OrderRepository repo = new OrderRepository();
+        private OrderRepository dishRepo = new OrderRepository();
+        private ClientRepository clientRepo = new ClientRepository();
+        private EmployeeRepository empRepo = new EmployeeRepository();
+
+        private Order currentOrder = null;
+
+        public OrderForm()
+        {
+            InitializeComponent();
+            LoadOrders();
+            LoadDishes();
+
+            cmbStatusFilter.SelectedIndex = 0;
+
+            btnNewOrder.Click += BtnNewOrder_Click;
+            btnRefreshOrders.Click += (s, e) => LoadOrders();
+            btnOrderSearch.Click += (s, e) => LoadOrders();
+            btnAddDish.Click += BtnAddDish_Click;
+            btnAddItem.Click += (s, e) => groupAddItem.Visible = !groupAddItem.Visible;
+            btnEditItem.Click += BtnEditItem_Click;
+            btnRemoveItem.Click += BtnRemoveItem_Click;
+            btnPay.Click += BtnPay_Click;
+            btnCloseOrder.Click += BtnCloseOrder_Click;
+
+            dgvOrders.SelectionChanged += DgvOrders_SelectionChanged;
+
+            groupAddItem.Visible = false;
+        }
+
+        private void LoadOrders()
+        {
+            string status = cmbStatusFilter.SelectedIndex > 0 ? cmbStatusFilter.Text : "";
+
+            // Если выбран "Все" - передаём пустую строку
+            string statusParam = (status == "Все" || string.IsNullOrEmpty(status)) ? "" : status;
+
+            dgvOrders.DataSource = repo.GetAll(statusParam, txtOrderSearch.Text);
+
+            if (dgvOrders.Columns.Count > 0)
+            {
+                dgvOrders.Columns["OrderId"].HeaderText = "ID";
+                dgvOrders.Columns["TableInfo"].HeaderText = "Стол";
+                dgvOrders.Columns["EmployeeName"].HeaderText = "Официант";
+                dgvOrders.Columns["ClientName"].HeaderText = "Клиент";
+                dgvOrders.Columns["OpenedAt"].HeaderText = "Открыт";
+                dgvOrders.Columns["ClosedAt"].HeaderText = "Закрыт";
+                dgvOrders.Columns["Status"].HeaderText = "Статус";
+                dgvOrders.Columns["TotalAmount"].HeaderText = "Сумма";
+
+                if (dgvOrders.Columns["TableId"] != null)
+                    dgvOrders.Columns["TableId"].Visible = false;
+                if (dgvOrders.Columns["EmployeeId"] != null)
+                    dgvOrders.Columns["EmployeeId"].Visible = false;
+                if (dgvOrders.Columns["ClientId"] != null)
+                    dgvOrders.Columns["ClientId"].Visible = false;
+                if (dgvOrders.Columns["DiscountPct"] != null)
+                    dgvOrders.Columns["DiscountPct"].Visible = false;
+            }
+        }
+
+        private void LoadDishes()
+        {
+            var dishRepo = new DishRepository();
+            var dishes = dishRepo.GetAll("", 0, true);
+            cmbDish.DisplayMember = "Name";
+            cmbDish.ValueMember = "DishId";
+            cmbDish.DataSource = dishes;
+        }
+
+        private void LoadOrderItems(int orderId)
+        {
+            dgvOrderItems.DataSource = repo.GetItems(orderId);
+            if (dgvOrderItems.Columns.Count > 0)
+            {
+                dgvOrderItems.Columns["DishName"].HeaderText = "Блюдо";
+                dgvOrderItems.Columns["Quantity"].HeaderText = "Кол-во";
+                dgvOrderItems.Columns["UnitPrice"].HeaderText = "Цена";
+                dgvOrderItems.Columns["Total"].HeaderText = "Сумма";
+                dgvOrderItems.Columns["Status"].HeaderText = "Статус";
+                dgvOrderItems.Columns["Notes"].HeaderText = "Примечание";
+            }
+
+            // Обновляем общую сумму
+            var order = repo.GetAll("", "").FirstOrDefault(o => o.OrderId == orderId);
+            if (order != null)
+                lblTotal.Text = $"Сумма: {order.TotalAmount} ₽";
+        }
+
+        private void UpdateOrderInfo()
+        {
+            if (currentOrder != null)
+            {
+                lblTable.Text = $"Стол: {currentOrder.TableInfo}";
+                lblWaiter.Text = $"Официант: {currentOrder.EmployeeName}";
+                lblOpenedAt.Text = $"Открыт: {currentOrder.OpenedAt:dd.MM.yyyy HH:mm}";
+                lblClient.Text = $"Клиент: {(string.IsNullOrEmpty(currentOrder.ClientName) ? "Без клиента" : currentOrder.ClientName)}";
+                lblStatus.Text = $"Статус: {currentOrder.Status}";
+                lblTotal.Text = $"Сумма: {currentOrder.TotalAmount} ₽";
+
+                bool isActive = currentOrder.Status != "closed" && currentOrder.Status != "cancelled";
+                btnAddItem.Enabled = isActive;
+                btnAddDish.Enabled = isActive;
+                btnEditItem.Enabled = isActive;
+                btnRemoveItem.Enabled = isActive;
+                btnPay.Enabled = isActive && currentOrder.TotalAmount > 0;
+                btnCloseOrder.Enabled = isActive;
+            }
+        }
+
+        private void DgvOrders_SelectionChanged(object sender, EventArgs e)
+        {
+            if (dgvOrders.CurrentRow != null)
+            {
+                currentOrder = (Order)dgvOrders.CurrentRow.DataBoundItem;
+                UpdateOrderInfo();
+                LoadOrderItems(currentOrder.OrderId);
+            }
+        }
+
+        private void BtnNewOrder_Click(object sender, EventArgs e)
+        {
+            var form = new OrderEditForm();
+            if (form.ShowDialog() == DialogResult.OK)
+            {
+                LoadOrders();
+                // Выбираем созданный заказ (последний)
+                if (dgvOrders.Rows.Count > 0)
+                {
+                    dgvOrders.ClearSelection();
+                    dgvOrders.Rows[dgvOrders.Rows.Count - 1].Selected = true;
+                }
+            }
+        }
+
+        private void BtnAddDish_Click(object sender, EventArgs e)
+        {
+            if (currentOrder == null)
+            {
+                MessageBox.Show("Выберите заказ");
+                return;
+            }
+
+            if (cmbDish.SelectedItem == null)
+            {
+                MessageBox.Show("Выберите блюдо");
+                return;
+            }
+
+            var dish = (Dish)cmbDish.SelectedItem;
+
+            var item = new OrderItem
+            {
+                OrderId = currentOrder.OrderId,
+                DishId = dish.DishId,
+                Quantity = (int)numQuantity.Value,
+                UnitPrice = dish.Price,
+                Notes = txtItemNotes.Text
+            };
+
+            try
+            {
+                repo.AddItem(item);
+                LoadOrderItems(currentOrder.OrderId);
+                LoadOrders(); // обновляем сумму в списке заказов
+                txtItemNotes.Text = "";
+                numQuantity.Value = 1;
+                MessageBox.Show("Блюдо добавлено");
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}");
+            }
+        }
+
+        private void BtnEditItem_Click(object sender, EventArgs e)
+        {
+            if (dgvOrderItems.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите позицию");
+                return;
+            }
+
+            var item = (OrderItem)dgvOrderItems.CurrentRow.DataBoundItem;
+            var form = new OrderItemEditForm(item);
+            if (form.ShowDialog() == DialogResult.OK)
+            {
+                LoadOrderItems(currentOrder.OrderId);
+                LoadOrders();
+            }
+        }
+
+        private void BtnRemoveItem_Click(object sender, EventArgs e)
+        {
+            if (dgvOrderItems.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите позицию");
+                return;
+            }
+
+            var item = (OrderItem)dgvOrderItems.CurrentRow.DataBoundItem;
+            if (MessageBox.Show($"Удалить {item.DishName}?", "Подтверждение",
+                MessageBoxButtons.YesNo) == DialogResult.Yes)
+            {
+                repo.DeleteItem(item.OrderItemId);
+                LoadOrderItems(currentOrder.OrderId);
+                LoadOrders();
+            }
+        }
+
+        private void BtnPay_Click(object sender, EventArgs e)
+        {
+            if (currentOrder == null) return;
+
+            var form = new PaymentForm(currentOrder.OrderId, currentOrder.TotalAmount);
+            if (form.ShowDialog() == DialogResult.OK)
+            {
+                LoadOrders();
+                LoadOrderItems(currentOrder.OrderId);
+                UpdateOrderInfo();
+                MessageBox.Show("Оплата произведена");
+            }
+        }
+
+        private void BtnCloseOrder_Click(object sender, EventArgs e)
+        {
+            if (currentOrder == null) return;
+
+            if (MessageBox.Show($"Закрыть заказ #{currentOrder.OrderId}?", "Подтверждение",
+                MessageBoxButtons.YesNo) == DialogResult.Yes)
+            {
+                repo.UpdateOrderStatus(currentOrder.OrderId, "closed");
+                LoadOrders();
+                LoadOrderItems(currentOrder.OrderId);
+                UpdateOrderInfo();
+            }
+        }
+    }
+}

+ 120 - 0
OrderForm.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>

+ 4 - 0
OrderItem.cs

@@ -0,0 +1,4 @@
+namespace RestaurantApp.Models
+{
+    // OrderItem is defined in Order.cs
+}

+ 173 - 0
OrderItemEditForm.Designer.cs

@@ -0,0 +1,173 @@
+namespace RestaurantApp
+{
+    partial class OrderItemEditForm
+    {
+        private System.ComponentModel.IContainer components = null;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.lblDish = new System.Windows.Forms.Label();
+            this.lblDishName = new System.Windows.Forms.Label();
+            this.lblQuantity = new System.Windows.Forms.Label();
+            this.numQuantity = new System.Windows.Forms.NumericUpDown();
+            this.lblStatus = new System.Windows.Forms.Label();
+            this.cmbStatus = new System.Windows.Forms.ComboBox();
+            this.lblNotes = new System.Windows.Forms.Label();
+            this.txtNotes = new System.Windows.Forms.TextBox();
+            this.btnSave = new System.Windows.Forms.Button();
+            this.btnCancel = new System.Windows.Forms.Button();
+            ((System.ComponentModel.ISupportInitialize)(this.numQuantity)).BeginInit();
+            this.SuspendLayout();
+            // 
+            // lblDish
+            // 
+            this.lblDish.AutoSize = true;
+            this.lblDish.Location = new System.Drawing.Point(30, 30);
+            this.lblDish.Name = "lblDish";
+            this.lblDish.Size = new System.Drawing.Size(43, 13);
+            this.lblDish.TabIndex = 0;
+            this.lblDish.Text = "Блюдо:";
+            // 
+            // lblDishName
+            // 
+            this.lblDishName.AutoSize = true;
+            this.lblDishName.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold);
+            this.lblDishName.Location = new System.Drawing.Point(120, 30);
+            this.lblDishName.Name = "lblDishName";
+            this.lblDishName.Size = new System.Drawing.Size(65, 13);
+            this.lblDishName.TabIndex = 1;
+            this.lblDishName.Text = "Название";
+            // 
+            // lblQuantity
+            // 
+            this.lblQuantity.AutoSize = true;
+            this.lblQuantity.Location = new System.Drawing.Point(30, 65);
+            this.lblQuantity.Name = "lblQuantity";
+            this.lblQuantity.Size = new System.Drawing.Size(69, 13);
+            this.lblQuantity.TabIndex = 2;
+            this.lblQuantity.Text = "Количество:";
+            // 
+            // numQuantity
+            // 
+            this.numQuantity.Location = new System.Drawing.Point(120, 63);
+            this.numQuantity.Minimum = new decimal(new int[] {
+            1,
+            0,
+            0,
+            0});
+            this.numQuantity.Name = "numQuantity";
+            this.numQuantity.Size = new System.Drawing.Size(100, 20);
+            this.numQuantity.TabIndex = 3;
+            this.numQuantity.Value = new decimal(new int[] {
+            1,
+            0,
+            0,
+            0});
+            // 
+            // lblStatus
+            // 
+            this.lblStatus.AutoSize = true;
+            this.lblStatus.Location = new System.Drawing.Point(30, 100);
+            this.lblStatus.Name = "lblStatus";
+            this.lblStatus.Size = new System.Drawing.Size(44, 13);
+            this.lblStatus.TabIndex = 4;
+            this.lblStatus.Text = "Статус:";
+            // 
+            // cmbStatus
+            // 
+            this.cmbStatus.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbStatus.Items.AddRange(new object[] {
+            "ordered",
+            "cooking",
+            "ready",
+            "served",
+            "cancelled"});
+            this.cmbStatus.Location = new System.Drawing.Point(120, 97);
+            this.cmbStatus.Name = "cmbStatus";
+            this.cmbStatus.Size = new System.Drawing.Size(150, 21);
+            this.cmbStatus.TabIndex = 5;
+            // 
+            // lblNotes
+            // 
+            this.lblNotes.AutoSize = true;
+            this.lblNotes.Location = new System.Drawing.Point(30, 135);
+            this.lblNotes.Name = "lblNotes";
+            this.lblNotes.Size = new System.Drawing.Size(73, 13);
+            this.lblNotes.TabIndex = 6;
+            this.lblNotes.Text = "Примечание:";
+            // 
+            // txtNotes
+            // 
+            this.txtNotes.Location = new System.Drawing.Point(120, 132);
+            this.txtNotes.Multiline = true;
+            this.txtNotes.Name = "txtNotes";
+            this.txtNotes.Size = new System.Drawing.Size(250, 60);
+            this.txtNotes.TabIndex = 7;
+            // 
+            // btnSave
+            // 
+            this.btnSave.BackColor = System.Drawing.Color.LimeGreen;
+            this.btnSave.Location = new System.Drawing.Point(120, 210);
+            this.btnSave.Name = "btnSave";
+            this.btnSave.Size = new System.Drawing.Size(100, 30);
+            this.btnSave.TabIndex = 8;
+            this.btnSave.Text = "Сохранить";
+            this.btnSave.UseVisualStyleBackColor = false;
+            // 
+            // btnCancel
+            // 
+            this.btnCancel.Location = new System.Drawing.Point(240, 210);
+            this.btnCancel.Name = "btnCancel";
+            this.btnCancel.Size = new System.Drawing.Size(100, 30);
+            this.btnCancel.TabIndex = 9;
+            this.btnCancel.Text = "Отмена";
+            this.btnCancel.UseVisualStyleBackColor = true;
+            // 
+            // OrderItemEditForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(400, 270);
+            this.Controls.Add(this.btnCancel);
+            this.Controls.Add(this.btnSave);
+            this.Controls.Add(this.txtNotes);
+            this.Controls.Add(this.lblNotes);
+            this.Controls.Add(this.cmbStatus);
+            this.Controls.Add(this.lblStatus);
+            this.Controls.Add(this.numQuantity);
+            this.Controls.Add(this.lblQuantity);
+            this.Controls.Add(this.lblDishName);
+            this.Controls.Add(this.lblDish);
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+            this.MaximizeBox = false;
+            this.MinimizeBox = false;
+            this.Name = "OrderItemEditForm";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+            this.Text = "Редактирование позиции";
+            ((System.ComponentModel.ISupportInitialize)(this.numQuantity)).EndInit();
+            this.ResumeLayout(false);
+            this.PerformLayout();
+
+        }
+
+        private System.Windows.Forms.Label lblDish;
+        private System.Windows.Forms.Label lblDishName;
+        private System.Windows.Forms.Label lblQuantity;
+        private System.Windows.Forms.NumericUpDown numQuantity;
+        private System.Windows.Forms.Label lblStatus;
+        private System.Windows.Forms.ComboBox cmbStatus;
+        private System.Windows.Forms.Label lblNotes;
+        private System.Windows.Forms.TextBox txtNotes;
+        private System.Windows.Forms.Button btnSave;
+        private System.Windows.Forms.Button btnCancel;
+    }
+}

+ 83 - 0
OrderItemEditForm.cs

@@ -0,0 +1,83 @@
+using System;
+using System.Windows.Forms;
+using RestaurantApp.DAL;
+using RestaurantApp.Models;
+
+namespace RestaurantApp
+{
+    public partial class OrderItemEditForm : Form
+    {
+        private OrderRepository repo = new OrderRepository();
+        private OrderItem item;
+
+        public OrderItemEditForm(OrderItem orderItem)
+        {
+            InitializeComponent();
+            this.item = orderItem;
+            LoadData();
+
+            btnSave.Click += BtnSave_Click;
+            btnCancel.Click += (s, e) => DialogResult = DialogResult.Cancel;
+        }
+
+        private void LoadData()
+        {
+            lblDishName.Text = item.DishName;
+            numQuantity.Value = item.Quantity;
+            txtNotes.Text = item.Notes;
+
+            // Выбираем статус в комбобоксе
+            int index = cmbStatus.FindStringExact(item.Status);
+            if (index >= 0) cmbStatus.SelectedIndex = index;
+
+            // Блокируем изменение статуса для закрытых заказов
+            // (можно добавить логику)
+        }
+
+        private void BtnSave_Click(object sender, EventArgs e)
+        {
+            try
+            {
+                // Обновляем количество
+                if (numQuantity.Value != item.Quantity)
+                {
+                    // Тут нужно обновить количество через SQL
+                    using (var conn = Database.GetConnection())
+                    using (var cmd = new Npgsql.NpgsqlCommand(
+                        "UPDATE order_item SET quantity = @q WHERE order_item_id = @id", conn))
+                    {
+                        cmd.Parameters.AddWithValue("q", (int)numQuantity.Value);
+                        cmd.Parameters.AddWithValue("id", item.OrderItemId);
+                        cmd.ExecuteNonQuery();
+                    }
+                }
+
+                // Обновляем статус
+                if (cmbStatus.Text != item.Status)
+                {
+                    repo.UpdateItemStatus(item.OrderItemId, cmbStatus.Text);
+                }
+
+                // Обновляем примечание
+                if (txtNotes.Text != item.Notes)
+                {
+                    using (var conn = Database.GetConnection())
+                    using (var cmd = new Npgsql.NpgsqlCommand(
+                        "UPDATE order_item SET notes = @n WHERE order_item_id = @id", conn))
+                    {
+                        cmd.Parameters.AddWithValue("n", string.IsNullOrEmpty(txtNotes.Text) ? (object)DBNull.Value : txtNotes.Text);
+                        cmd.Parameters.AddWithValue("id", item.OrderItemId);
+                        cmd.ExecuteNonQuery();
+                    }
+                }
+
+                DialogResult = DialogResult.OK;
+                Close();
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}");
+            }
+        }
+    }
+}

+ 120 - 0
OrderItemEditForm.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>

+ 190 - 0
OrderRepository.cs

@@ -0,0 +1,190 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using Npgsql;
+using RestaurantApp.Models;
+
+namespace RestaurantApp.DAL
+{
+    public class OrderRepository
+    {
+        public List<Order> GetAll(string statusFilter = "", string search = "")
+        {
+            var list = new List<Order>();
+            using (var conn = Database.GetConnection())
+            {
+                var sql = @"SELECT o.order_id, o.table_id,
+                           h.name||' стол №'||dt.table_number as table_info,
+                           o.employee_id,
+                           e.last_name||' '||e.first_name as emp_name,
+                           o.client_id,
+                           COALESCE(c.last_name||' '||c.first_name, '') as client_name,
+                           o.opened_at, o.closed_at, o.status,
+                           o.discount_pct, o.total_amount
+                    FROM ""order"" o
+                    JOIN dining_table dt ON dt.table_id = o.table_id
+                    JOIN hall h ON h.hall_id = dt.hall_id
+                    JOIN employee e ON e.employee_id = o.employee_id
+                    LEFT JOIN client c ON c.client_id = o.client_id
+                    WHERE 1=1 ";
+
+                if (!string.IsNullOrWhiteSpace(statusFilter) && statusFilter != "Все")
+                    sql += " AND o.status = @st";
+
+                if (!string.IsNullOrWhiteSpace(search))
+                    sql += " AND (CAST(o.order_id AS TEXT) LIKE @s OR LOWER(e.last_name) LIKE LOWER(@s) OR LOWER(c.last_name) LIKE LOWER(@s))";
+
+                sql += " ORDER BY o.opened_at DESC LIMIT 500";
+
+                using (var cmd = new NpgsqlCommand(sql, conn))
+                {
+                    if (!string.IsNullOrWhiteSpace(statusFilter) && statusFilter != "Все")
+                        cmd.Parameters.AddWithValue("st", statusFilter);
+                    if (!string.IsNullOrWhiteSpace(search))
+                        cmd.Parameters.AddWithValue("s", $"%{search}%");
+
+                    using (var r = cmd.ExecuteReader())
+                        while (r.Read()) list.Add(MapOrder(r));
+                }
+            }
+            return list;
+        }
+
+        public List<OrderItem> GetItems(int orderId)
+        {
+            var list = new List<OrderItem>();
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                @"SELECT oi.order_item_id,oi.order_id,oi.dish_id,d.name,
+                         oi.quantity,oi.unit_price,oi.notes,oi.status
+                  FROM order_item oi JOIN dish d ON d.dish_id=oi.dish_id
+                  WHERE oi.order_id=@id ORDER BY oi.order_item_id", conn))
+            {
+                cmd.Parameters.AddWithValue("id", orderId);
+                using (var r = cmd.ExecuteReader())
+                    while (r.Read())
+                        list.Add(new OrderItem
+                        {
+                            OrderItemId = r.GetInt32(0),
+                            OrderId = r.GetInt32(1),
+                            DishId = r.GetInt32(2),
+                            DishName = r.GetString(3),
+                            Quantity = r.GetInt32(4),
+                            UnitPrice = r.GetDecimal(5),
+                            Notes = r.IsDBNull(6) ? "" : r.GetString(6),
+                            Status = r.GetString(7)
+                        });
+            }
+            return list;
+        }
+
+        public int InsertOrder(Order o)
+        {
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                @"INSERT INTO ""order""(table_id,employee_id,client_id,status,discount_pct,total_amount)
+                  VALUES(@t,@e,@c,'open',@d,0) RETURNING order_id", conn))
+            {
+                cmd.Parameters.AddWithValue("t", o.TableId);
+                cmd.Parameters.AddWithValue("e", o.EmployeeId);
+                cmd.Parameters.AddWithValue("c", o.ClientId.HasValue ? (object)o.ClientId.Value : DBNull.Value);
+                cmd.Parameters.AddWithValue("d", o.DiscountPct);
+                return (int)cmd.ExecuteScalar();
+            }
+        }
+
+        public void AddItem(OrderItem item)
+        {
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                @"INSERT INTO order_item(order_id,dish_id,quantity,unit_price,notes,status)
+                  VALUES(@o,@d,@q,@p,@n,'ordered')", conn))
+            {
+                cmd.Parameters.AddWithValue("o", item.OrderId);
+                cmd.Parameters.AddWithValue("d", item.DishId);
+                cmd.Parameters.AddWithValue("q", item.Quantity);
+                cmd.Parameters.AddWithValue("p", item.UnitPrice);
+                cmd.Parameters.AddWithValue("n", string.IsNullOrEmpty(item.Notes) ? (object)DBNull.Value : item.Notes);
+                cmd.ExecuteNonQuery();
+            }
+        }
+
+        public void UpdateOrderStatus(int orderId, string status)
+        {
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                @"UPDATE ""order"" SET status=@s, closed_at=CASE WHEN @s IN ('closed','cancelled') THEN NOW() ELSE closed_at END
+                  WHERE order_id=@id", conn))
+            {
+                cmd.Parameters.AddWithValue("s", status);
+                cmd.Parameters.AddWithValue("id", orderId);
+                cmd.ExecuteNonQuery();
+            }
+        }
+
+        public void UpdateItemStatus(int itemId, string status)
+        {
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                "UPDATE order_item SET status=@s WHERE order_item_id=@id", conn))
+            {
+                cmd.Parameters.AddWithValue("s", status);
+                cmd.Parameters.AddWithValue("id", itemId);
+                cmd.ExecuteNonQuery();
+            }
+        }
+
+        public void DeleteItem(int itemId)
+        {
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                "UPDATE order_item SET status='cancelled' WHERE order_item_id=@id", conn))
+            {
+                cmd.Parameters.AddWithValue("id", itemId);
+                cmd.ExecuteNonQuery();
+            }
+        }
+
+        public void AddPayment(int orderId, decimal amount, string method)
+        {
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                "INSERT INTO payment(order_id,amount,method) VALUES(@o,@a,@m)", conn))
+            {
+                cmd.Parameters.AddWithValue("o", orderId);
+                cmd.Parameters.AddWithValue("a", amount);
+                cmd.Parameters.AddWithValue("m", method);
+                cmd.ExecuteNonQuery();
+            }
+        }
+
+        public DataTable GetTables()
+        {
+            var dt = new DataTable();
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                @"SELECT dt.table_id, h.name||' — стол №'||dt.table_number||' ('||dt.seats||' мест)' as info
+                  FROM dining_table dt JOIN hall h ON h.hall_id=dt.hall_id
+                  WHERE dt.is_active=true ORDER BY h.name, dt.table_number", conn))
+            using (var da = new NpgsqlDataAdapter(cmd))
+                da.Fill(dt);
+            return dt;
+        }
+
+        private Order MapOrder(NpgsqlDataReader r) => new Order
+        {
+            OrderId = r.GetInt32(0),
+            TableId = r.GetInt32(1),
+            TableInfo = r.GetString(2),
+            EmployeeId = r.GetInt32(3),
+            EmployeeName = r.GetString(4),
+            ClientId = r.IsDBNull(5) ? (int?)null : r.GetInt32(5),
+            ClientName = r.GetString(6),
+            OpenedAt = r.GetDateTime(7),
+            ClosedAt = r.IsDBNull(8) ? (DateTime?)null : r.GetDateTime(8),
+            Status = r.GetString(9),
+            DiscountPct = r.GetDecimal(10),
+            TotalAmount = r.IsDBNull(11) ? 0 : r.GetDecimal(11)
+        };
+    }
+}

+ 210 - 0
PaymentForm.Designer.cs

@@ -0,0 +1,210 @@
+namespace RestaurantApp
+{
+    partial class PaymentForm
+    {
+        private System.ComponentModel.IContainer components = null;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.lblOrderId = new System.Windows.Forms.Label();
+            this.lblOrderIdValue = new System.Windows.Forms.Label();
+            this.lblAmount = new System.Windows.Forms.Label();
+            this.lblAmountValue = new System.Windows.Forms.Label();
+            this.lblMethod = new System.Windows.Forms.Label();
+            this.cmbMethod = new System.Windows.Forms.ComboBox();
+            this.lblBonus = new System.Windows.Forms.Label();
+            this.numBonus = new System.Windows.Forms.NumericUpDown();
+            this.lblBonusInfo = new System.Windows.Forms.Label();
+            this.lblClientBonus = new System.Windows.Forms.Label();
+            this.lblFinalAmount = new System.Windows.Forms.Label();
+            this.lblFinalAmountValue = new System.Windows.Forms.Label();
+            this.btnPay = new System.Windows.Forms.Button();
+            this.btnCancel = new System.Windows.Forms.Button();
+            ((System.ComponentModel.ISupportInitialize)(this.numBonus)).BeginInit();
+            this.SuspendLayout();
+            // 
+            // lblOrderId
+            // 
+            this.lblOrderId.AutoSize = true;
+            this.lblOrderId.Location = new System.Drawing.Point(30, 30);
+            this.lblOrderId.Name = "lblOrderId";
+            this.lblOrderId.Size = new System.Drawing.Size(62, 13);
+            this.lblOrderId.TabIndex = 0;
+            this.lblOrderId.Text = "Заказ №:";
+            // 
+            // lblOrderIdValue
+            // 
+            this.lblOrderIdValue.AutoSize = true;
+            this.lblOrderIdValue.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold);
+            this.lblOrderIdValue.Location = new System.Drawing.Point(150, 30);
+            this.lblOrderIdValue.Name = "lblOrderIdValue";
+            this.lblOrderIdValue.Size = new System.Drawing.Size(14, 13);
+            this.lblOrderIdValue.TabIndex = 1;
+            this.lblOrderIdValue.Text = "0";
+            // 
+            // lblAmount
+            // 
+            this.lblAmount.AutoSize = true;
+            this.lblAmount.Location = new System.Drawing.Point(30, 60);
+            this.lblAmount.Name = "lblAmount";
+            this.lblAmount.Size = new System.Drawing.Size(58, 13);
+            this.lblAmount.TabIndex = 2;
+            this.lblAmount.Text = "Сумма:";
+            // 
+            // lblAmountValue
+            // 
+            this.lblAmountValue.AutoSize = true;
+            this.lblAmountValue.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold);
+            this.lblAmountValue.Location = new System.Drawing.Point(150, 60);
+            this.lblAmountValue.Name = "lblAmountValue";
+            this.lblAmountValue.Size = new System.Drawing.Size(41, 13);
+            this.lblAmountValue.TabIndex = 3;
+            this.lblAmountValue.Text = "0 ₽";
+            // 
+            // lblMethod
+            // 
+            this.lblMethod.AutoSize = true;
+            this.lblMethod.Location = new System.Drawing.Point(30, 90);
+            this.lblMethod.Name = "lblMethod";
+            this.lblMethod.Size = new System.Drawing.Size(82, 13);
+            this.lblMethod.TabIndex = 4;
+            this.lblMethod.Text = "Способ оплаты:";
+            // 
+            // cmbMethod
+            // 
+            this.cmbMethod.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbMethod.Items.AddRange(new object[] { "cash", "card", "online", "bonus" });
+            this.cmbMethod.Location = new System.Drawing.Point(150, 87);
+            this.cmbMethod.Name = "cmbMethod";
+            this.cmbMethod.Size = new System.Drawing.Size(150, 21);
+            this.cmbMethod.TabIndex = 5;
+            // 
+            // lblBonus
+            // 
+            this.lblBonus.AutoSize = true;
+            this.lblBonus.Location = new System.Drawing.Point(30, 125);
+            this.lblBonus.Name = "lblBonus";
+            this.lblBonus.Size = new System.Drawing.Size(114, 13);
+            this.lblBonus.TabIndex = 6;
+            this.lblBonus.Text = "Использовать бонусы:";
+            // 
+            // numBonus
+            // 
+            this.numBonus.Location = new System.Drawing.Point(150, 123);
+            this.numBonus.Maximum = new decimal(new int[] { 999999, 0, 0, 0 });
+            this.numBonus.Name = "numBonus";
+            this.numBonus.Size = new System.Drawing.Size(100, 20);
+            this.numBonus.TabIndex = 7;
+            // 
+            // lblBonusInfo
+            // 
+            this.lblBonusInfo.AutoSize = true;
+            this.lblBonusInfo.Location = new System.Drawing.Point(260, 125);
+            this.lblBonusInfo.Name = "lblBonusInfo";
+            this.lblBonusInfo.Size = new System.Drawing.Size(60, 13);
+            this.lblBonusInfo.TabIndex = 8;
+            this.lblBonusInfo.Text = "(доступно: )";
+            // 
+            // lblClientBonus
+            // 
+            this.lblClientBonus.AutoSize = true;
+            this.lblClientBonus.Location = new System.Drawing.Point(150, 145);
+            this.lblClientBonus.Name = "lblClientBonus";
+            this.lblClientBonus.Size = new System.Drawing.Size(0, 13);
+            this.lblClientBonus.TabIndex = 9;
+            // 
+            // lblFinalAmount
+            // 
+            this.lblFinalAmount.AutoSize = true;
+            this.lblFinalAmount.Font = new System.Drawing.Font("Microsoft Sans Serif", 10F, System.Drawing.FontStyle.Bold);
+            this.lblFinalAmount.Location = new System.Drawing.Point(30, 180);
+            this.lblFinalAmount.Name = "lblFinalAmount";
+            this.lblFinalAmount.Size = new System.Drawing.Size(106, 17);
+            this.lblFinalAmount.TabIndex = 10;
+            this.lblFinalAmount.Text = "К оплате:";
+            // 
+            // lblFinalAmountValue
+            // 
+            this.lblFinalAmountValue.AutoSize = true;
+            this.lblFinalAmountValue.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Bold);
+            this.lblFinalAmountValue.ForeColor = System.Drawing.Color.Green;
+            this.lblFinalAmountValue.Location = new System.Drawing.Point(150, 178);
+            this.lblFinalAmountValue.Name = "lblFinalAmountValue";
+            this.lblFinalAmountValue.Size = new System.Drawing.Size(49, 20);
+            this.lblFinalAmountValue.TabIndex = 11;
+            this.lblFinalAmountValue.Text = "0 ₽";
+            // 
+            // btnPay
+            // 
+            this.btnPay.BackColor = System.Drawing.Color.LimeGreen;
+            this.btnPay.Location = new System.Drawing.Point(100, 220);
+            this.btnPay.Name = "btnPay";
+            this.btnPay.Size = new System.Drawing.Size(100, 35);
+            this.btnPay.TabIndex = 12;
+            this.btnPay.Text = "Оплатить";
+            this.btnPay.UseVisualStyleBackColor = false;
+            // 
+            // btnCancel
+            // 
+            this.btnCancel.Location = new System.Drawing.Point(220, 220);
+            this.btnCancel.Name = "btnCancel";
+            this.btnCancel.Size = new System.Drawing.Size(100, 35);
+            this.btnCancel.TabIndex = 13;
+            this.btnCancel.Text = "Отмена";
+            this.btnCancel.UseVisualStyleBackColor = true;
+            // 
+            // PaymentForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(430, 280);
+            this.Controls.Add(this.btnCancel);
+            this.Controls.Add(this.btnPay);
+            this.Controls.Add(this.lblFinalAmountValue);
+            this.Controls.Add(this.lblFinalAmount);
+            this.Controls.Add(this.lblClientBonus);
+            this.Controls.Add(this.lblBonusInfo);
+            this.Controls.Add(this.numBonus);
+            this.Controls.Add(this.lblBonus);
+            this.Controls.Add(this.cmbMethod);
+            this.Controls.Add(this.lblMethod);
+            this.Controls.Add(this.lblAmountValue);
+            this.Controls.Add(this.lblAmount);
+            this.Controls.Add(this.lblOrderIdValue);
+            this.Controls.Add(this.lblOrderId);
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+            this.MaximizeBox = false;
+            this.MinimizeBox = false;
+            this.Name = "PaymentForm";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+            this.Text = "Оплата заказа";
+            ((System.ComponentModel.ISupportInitialize)(this.numBonus)).EndInit();
+            this.ResumeLayout(false);
+            this.PerformLayout();
+        }
+
+        private System.Windows.Forms.Label lblOrderId;
+        private System.Windows.Forms.Label lblOrderIdValue;
+        private System.Windows.Forms.Label lblAmount;
+        private System.Windows.Forms.Label lblAmountValue;
+        private System.Windows.Forms.Label lblMethod;
+        private System.Windows.Forms.ComboBox cmbMethod;
+        private System.Windows.Forms.Label lblBonus;
+        private System.Windows.Forms.NumericUpDown numBonus;
+        private System.Windows.Forms.Label lblBonusInfo;
+        private System.Windows.Forms.Label lblClientBonus;
+        private System.Windows.Forms.Label lblFinalAmount;
+        private System.Windows.Forms.Label lblFinalAmountValue;
+        private System.Windows.Forms.Button btnPay;
+        private System.Windows.Forms.Button btnCancel;
+    }
+}

+ 110 - 0
PaymentForm.cs

@@ -0,0 +1,110 @@
+using System;
+using System.Data;
+using System.Linq;
+using System.Windows.Forms;
+using RestaurantApp.DAL;
+using RestaurantApp.Models;
+
+namespace RestaurantApp
+{
+    public partial class PaymentForm : Form
+    {
+        private OrderRepository repo = new OrderRepository();
+        private ClientRepository clientRepo = new ClientRepository();
+
+        private int orderId;
+        private decimal totalAmount;
+        private int? clientId;
+        private int availableBonus = 0;
+
+        public PaymentForm(int orderId, decimal totalAmount)
+        {
+            InitializeComponent();
+            this.orderId = orderId;
+            this.totalAmount = totalAmount;
+
+            LoadClientInfo();
+            LoadData();
+
+            cmbMethod.SelectedIndex = 0;
+            cmbMethod.SelectedIndexChanged += (s, e) => UpdateFinalAmount();
+            numBonus.ValueChanged += (s, e) => UpdateFinalAmount();
+
+            btnPay.Click += BtnPay_Click;
+            btnCancel.Click += (s, e) => DialogResult = DialogResult.Cancel;
+        }
+
+        private void LoadClientInfo()
+        {
+            // Получаем клиента из заказа
+            var orders = repo.GetAll("", "");
+            var order = orders.FirstOrDefault(o => o.OrderId == orderId);
+
+            if (order != null && order.ClientId.HasValue)
+            {
+                clientId = order.ClientId;
+                var client = clientRepo.GetById(clientId.Value);
+                if (client != null)
+                {
+                    availableBonus = client.BonusPoints;
+                    lblBonusInfo.Text = $"(доступно: {availableBonus})";
+                    numBonus.Maximum = Math.Min(availableBonus, (int)(totalAmount / 10)); // 1 бонус = 10 руб
+                }
+                else
+                {
+                    lblBonusInfo.Text = "(нет бонусов)";
+                    numBonus.Enabled = false;
+                }
+            }
+            else
+            {
+                lblBonusInfo.Text = "(без клиента)";
+                numBonus.Enabled = false;
+            }
+        }
+
+        private void LoadData()
+        {
+            lblOrderIdValue.Text = orderId.ToString();
+            lblAmountValue.Text = $"{totalAmount:N2} ₽";
+            UpdateFinalAmount();
+        }
+
+        private void UpdateFinalAmount()
+        {
+            decimal bonusDiscount = numBonus.Value * 10; // 1 бонус = 10 рублей
+            decimal finalAmount = totalAmount - bonusDiscount;
+            if (finalAmount < 0) finalAmount = 0;
+
+            lblFinalAmountValue.Text = $"{finalAmount:N2} ₽";
+        }
+
+        private void BtnPay_Click(object sender, EventArgs e)
+        {
+            decimal bonusDiscount = numBonus.Value * 10;
+            decimal finalAmount = totalAmount - bonusDiscount;
+            if (finalAmount < 0) finalAmount = 0;
+
+            if (finalAmount <= 0 && cmbMethod.Text != "bonus")
+            {
+                MessageBox.Show("Сумма к оплате 0. Выберите оплату бонусами");
+                return;
+            }
+
+            try
+            {
+                repo.AddPayment(orderId, finalAmount, cmbMethod.Text);
+
+                // Обновляем статус заказа
+                repo.UpdateOrderStatus(orderId, "closed");
+
+                DialogResult = DialogResult.OK;
+                Close();
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка оплаты: {ex.Message}");
+            }
+        }
+    }
+}

+ 120 - 0
PaymentForm.resx

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

+ 19 - 0
Program.cs

@@ -0,0 +1,19 @@
+using System;
+using System.Windows.Forms;
+
+namespace RestaurantApp
+{
+    static class Program
+    {
+        public static string CurrentUser = "";
+        public static string CurrentRole = "";
+
+        [STAThread]
+        static void Main()
+        {
+            Application.EnableVisualStyles();
+            Application.SetCompatibleTextRenderingDefault(false);
+            Application.Run(new LoginForm());
+        }
+    }
+}

+ 33 - 0
Properties/AssemblyInfo.cs

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

+ 71 - 0
Properties/Resources.Designer.cs

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

+ 117 - 0
Properties/Resources.resx

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

+ 30 - 0
Properties/Settings.Designer.cs

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

+ 7 - 0
Properties/Settings.settings

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

+ 106 - 0
README.md

@@ -0,0 +1,106 @@
+# Аннотация
+
+RestaurantApp - это настольная информационная система для автоматизации работы ресторана. Программа разработана на C# Windows Forms и предназначена для учета клиентов, сотрудников, блюд, заказов, бронирований столов, платежей и формирования отчетов.
+
+Система подключается к базе данных PostgreSQL, использует разграничение доступа по ролям пользователей и предоставляет графический интерфейс для просмотра, добавления, редактирования и удаления данных.
+
+# Введение
+
+Основные возможности программы:
+
+- авторизация пользователя по логину;
+- разграничение доступа для ролей `admin`, `manager`, `waiter`;
+- просмотр и поиск клиентов ресторана;
+- добавление, редактирование и удаление записей о клиентах;
+- управление справочником блюд и фильтрация блюд по категории и доступности;
+- управление сотрудниками ресторана;
+- создание и ведение заказов;
+- добавление блюд в заказ и расчет суммы заказа;
+- проведение оплаты заказа;
+- закрытие заказов;
+- создание, редактирование и удаление бронирований;
+- просмотр отчетов по выручке, продажам блюд, работе официантов, способам оплаты и остаткам ингредиентов.
+
+# Назначение и условия применения
+
+Программа предназначена для использования сотрудниками ресторана при ведении оперативного учета. Система помогает хранить сведения о клиентах, блюдах, сотрудниках, заказах и бронированиях, а также получать аналитические отчеты по работе заведения.
+
+Для работы программы необходимы:
+
+- операционная система Windows;
+- установленный .NET Framework 4.7.2 или новее;
+- доступ к серверу PostgreSQL;
+- наличие базы данных с нужной структурой таблиц, представлений, функций и триггеров;
+- сетевое подключение к серверу базы данных;
+- учетная запись пользователя в таблице `app_user`.
+
+Приложение подключается к удаленной базе данных PostgreSQL через библиотеку `Npgsql`. Параметры подключения заданы в модуле `Database.cs`.
+
+# Установка
+
+1. [x] Убедиться, что на компьютере установлена Windows.
+2. [x] Установить .NET Framework 4.7.2 или более новую версию.
+3. [x] Проверить наличие доступа к серверу PostgreSQL и базе данных приложения.
+4. [x] Собрать проект в Visual Studio в конфигурации `Release`.
+5. [x] Открыть файл `RestaurantAppSetup.iss` в Inno Setup.
+6. [x] Скомпилировать установщик командой:
+
+```powershell
+ISCC.exe RestaurantAppSetup.iss
+```
+
+7. [x] Запустить созданный файл `installer-output\RestaurantAppSetup.exe`.
+8. [x] Следовать шагам мастера установки.
+9. [x] После завершения установки запустить программу через ярлык в меню "Пуск" или на рабочем столе.
+10. [ ] Войти в систему под учетной записью пользователя.
+
+# Описание операций
+
+## Авторизация
+
+После запуска программы открывается форма входа. Пользователь вводит логин. Если логин найден в базе данных, система определяет ФИО пользователя и его роль, после чего открывает главное окно приложения.
+
+## Работа с клиентами
+
+Раздел "Клиенты" позволяет просматривать список клиентов, искать записи, добавлять новых клиентов, редактировать существующие данные и удалять выбранные записи. В таблице отображаются имя, фамилия, телефон, email, дата рождения, бонусные баллы и дата регистрации.
+
+## Работа с блюдами
+
+Раздел "Блюда" предназначен для ведения меню ресторана. Пользователь может искать блюда, фильтровать их по категории и доступности, добавлять новые позиции, редактировать сведения о блюде и удалять выбранные записи.
+
+## Работа с сотрудниками
+
+Раздел "Сотрудники" доступен пользователям с ролями `admin` и `manager`. В нем можно просматривать список сотрудников, фильтровать записи по должности и активности, добавлять сотрудников, редактировать их данные и отмечать сотрудника как уволенного.
+
+## Работа с заказами
+
+Раздел "Заказы" используется для создания и сопровождения заказов. Пользователь может создать новый заказ, выбрать заказ из списка, добавить в него блюда, изменить или удалить позиции заказа, провести оплату и закрыть заказ.
+
+## Работа с бронированиями
+
+Раздел "Бронирования" позволяет вести учет резервирования столов. Пользователь может искать бронирования, фильтровать их по статусу, добавлять новые записи, редактировать существующие и удалять отмененные или ошибочные бронирования.
+
+## Работа с отчетами
+
+Раздел "Отчеты" доступен пользователям с ролями `admin` и `manager`. В нем отображаются отчеты по дневной выручке, продажам блюд, статистике официантов, способам оплаты и ингредиентам с низким остатком.
+
+## Выход из программы
+
+Для завершения работы используется пункт меню "Выход" или стандартная кнопка закрытия окна. После выхода приложение завершает текущий сеанс пользователя.
+
+# Термины и сокращения
+
+| Термин | Описание |
+| - | - |
+| ИС | Информационная система, предназначенная для хранения и обработки данных |
+| БД | База данных, в которой хранятся сведения ресторана |
+| PostgreSQL | Система управления базами данных, используемая приложением |
+| Windows Forms | Технология создания настольных приложений Windows на языке C# |
+| Npgsql | Библиотека для подключения C#-приложения к PostgreSQL |
+| Клиент | Посетитель ресторана, сведения о котором хранятся в системе |
+| Сотрудник | Работник ресторана, например официант или менеджер |
+| Заказ | Набор блюд, оформленный для клиента или стола |
+| Бронирование | Запись о резервировании стола на определенное время |
+| Платеж | Операция оплаты заказа |
+| Роль | Уровень доступа пользователя к функциям системы |
+| Инсталлятор | Программа установки, созданная с помощью Inno Setup |

+ 508 - 0
ReportForm.Designer.cs

@@ -0,0 +1,508 @@
+namespace RestaurantApp
+{
+    partial class ReportForm
+    {
+        private System.ComponentModel.IContainer components = null;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.tabControl = new System.Windows.Forms.TabControl();
+            this.tabRevenue = new System.Windows.Forms.TabPage();
+            this.dgvRevenue = new System.Windows.Forms.DataGridView();
+            this.panelRevenue = new System.Windows.Forms.Panel();
+            this.btnRevenueShow = new System.Windows.Forms.Button();
+            this.dtpRevenueTo = new System.Windows.Forms.DateTimePicker();
+            this.lblRevenueTo = new System.Windows.Forms.Label();
+            this.dtpRevenueFrom = new System.Windows.Forms.DateTimePicker();
+            this.lblRevenueFrom = new System.Windows.Forms.Label();
+            this.tabDishSales = new System.Windows.Forms.TabPage();
+            this.dgvDishSales = new System.Windows.Forms.DataGridView();
+            this.panelDishSales = new System.Windows.Forms.Panel();
+            this.btnDishSalesShow = new System.Windows.Forms.Button();
+            this.dtpDishSalesTo = new System.Windows.Forms.DateTimePicker();
+            this.lblDishSalesTo = new System.Windows.Forms.Label();
+            this.dtpDishSalesFrom = new System.Windows.Forms.DateTimePicker();
+            this.lblDishSalesFrom = new System.Windows.Forms.Label();
+            this.tabWaiterStats = new System.Windows.Forms.TabPage();
+            this.dgvWaiterStats = new System.Windows.Forms.DataGridView();
+            this.panelWaiterStats = new System.Windows.Forms.Panel();
+            this.btnWaiterStatsShow = new System.Windows.Forms.Button();
+            this.dtpWaiterStatsTo = new System.Windows.Forms.DateTimePicker();
+            this.lblWaiterStatsTo = new System.Windows.Forms.Label();
+            this.dtpWaiterStatsFrom = new System.Windows.Forms.DateTimePicker();
+            this.lblWaiterStatsFrom = new System.Windows.Forms.Label();
+            this.tabPaymentMethods = new System.Windows.Forms.TabPage();
+            this.dgvPaymentMethods = new System.Windows.Forms.DataGridView();
+            this.panelPaymentMethods = new System.Windows.Forms.Panel();
+            this.btnPaymentMethodsShow = new System.Windows.Forms.Button();
+            this.dtpPaymentMethodsTo = new System.Windows.Forms.DateTimePicker();
+            this.lblPaymentMethodsTo = new System.Windows.Forms.Label();
+            this.dtpPaymentMethodsFrom = new System.Windows.Forms.DateTimePicker();
+            this.lblPaymentMethodsFrom = new System.Windows.Forms.Label();
+            this.tabLowStock = new System.Windows.Forms.TabPage();
+            this.dgvLowStock = new System.Windows.Forms.DataGridView();
+            this.btnLowStockRefresh = new System.Windows.Forms.Button();
+            this.tabControl.SuspendLayout();
+            this.tabRevenue.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvRevenue)).BeginInit();
+            this.panelRevenue.SuspendLayout();
+            this.tabDishSales.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvDishSales)).BeginInit();
+            this.panelDishSales.SuspendLayout();
+            this.tabWaiterStats.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvWaiterStats)).BeginInit();
+            this.panelWaiterStats.SuspendLayout();
+            this.tabPaymentMethods.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvPaymentMethods)).BeginInit();
+            this.panelPaymentMethods.SuspendLayout();
+            this.tabLowStock.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvLowStock)).BeginInit();
+            this.SuspendLayout();
+            // 
+            // tabControl
+            // 
+            this.tabControl.Controls.Add(this.tabRevenue);
+            this.tabControl.Controls.Add(this.tabDishSales);
+            this.tabControl.Controls.Add(this.tabWaiterStats);
+            this.tabControl.Controls.Add(this.tabPaymentMethods);
+            this.tabControl.Controls.Add(this.tabLowStock);
+            this.tabControl.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.tabControl.Location = new System.Drawing.Point(0, 0);
+            this.tabControl.Name = "tabControl";
+            this.tabControl.SelectedIndex = 0;
+            this.tabControl.Size = new System.Drawing.Size(1000, 600);
+            this.tabControl.TabIndex = 0;
+            // 
+            // tabRevenue
+            // 
+            this.tabRevenue.Controls.Add(this.dgvRevenue);
+            this.tabRevenue.Controls.Add(this.panelRevenue);
+            this.tabRevenue.Location = new System.Drawing.Point(4, 22);
+            this.tabRevenue.Name = "tabRevenue";
+            this.tabRevenue.Padding = new System.Windows.Forms.Padding(3);
+            this.tabRevenue.Size = new System.Drawing.Size(992, 574);
+            this.tabRevenue.TabIndex = 0;
+            this.tabRevenue.Text = "Выручка";
+            this.tabRevenue.UseVisualStyleBackColor = true;
+            // 
+            // dgvRevenue
+            // 
+            this.dgvRevenue.AllowUserToAddRows = false;
+            this.dgvRevenue.AllowUserToDeleteRows = false;
+            this.dgvRevenue.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
+            this.dgvRevenue.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
+            this.dgvRevenue.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.dgvRevenue.Location = new System.Drawing.Point(3, 45);
+            this.dgvRevenue.Name = "dgvRevenue";
+            this.dgvRevenue.ReadOnly = true;
+            this.dgvRevenue.Size = new System.Drawing.Size(986, 526);
+            this.dgvRevenue.TabIndex = 1;
+            // 
+            // panelRevenue
+            // 
+            this.panelRevenue.Controls.Add(this.btnRevenueShow);
+            this.panelRevenue.Controls.Add(this.dtpRevenueTo);
+            this.panelRevenue.Controls.Add(this.lblRevenueTo);
+            this.panelRevenue.Controls.Add(this.dtpRevenueFrom);
+            this.panelRevenue.Controls.Add(this.lblRevenueFrom);
+            this.panelRevenue.Dock = System.Windows.Forms.DockStyle.Top;
+            this.panelRevenue.Location = new System.Drawing.Point(3, 3);
+            this.panelRevenue.Name = "panelRevenue";
+            this.panelRevenue.Size = new System.Drawing.Size(986, 42);
+            this.panelRevenue.TabIndex = 0;
+            // 
+            // btnRevenueShow
+            // 
+            this.btnRevenueShow.Location = new System.Drawing.Point(370, 10);
+            this.btnRevenueShow.Name = "btnRevenueShow";
+            this.btnRevenueShow.Size = new System.Drawing.Size(100, 23);
+            this.btnRevenueShow.TabIndex = 4;
+            this.btnRevenueShow.Text = "Показать";
+            this.btnRevenueShow.UseVisualStyleBackColor = true;
+            // 
+            // dtpRevenueTo
+            // 
+            this.dtpRevenueTo.Format = System.Windows.Forms.DateTimePickerFormat.Short;
+            this.dtpRevenueTo.Location = new System.Drawing.Point(250, 12);
+            this.dtpRevenueTo.Name = "dtpRevenueTo";
+            this.dtpRevenueTo.Size = new System.Drawing.Size(100, 20);
+            this.dtpRevenueTo.TabIndex = 3;
+            // 
+            // lblRevenueTo
+            // 
+            this.lblRevenueTo.AutoSize = true;
+            this.lblRevenueTo.Location = new System.Drawing.Point(220, 15);
+            this.lblRevenueTo.Name = "lblRevenueTo";
+            this.lblRevenueTo.Size = new System.Drawing.Size(22, 13);
+            this.lblRevenueTo.TabIndex = 2;
+            this.lblRevenueTo.Text = "по:";
+            // 
+            // dtpRevenueFrom
+            // 
+            this.dtpRevenueFrom.Format = System.Windows.Forms.DateTimePickerFormat.Short;
+            this.dtpRevenueFrom.Location = new System.Drawing.Point(100, 12);
+            this.dtpRevenueFrom.Name = "dtpRevenueFrom";
+            this.dtpRevenueFrom.Size = new System.Drawing.Size(100, 20);
+            this.dtpRevenueFrom.TabIndex = 1;
+            // 
+            // lblRevenueFrom
+            // 
+            this.lblRevenueFrom.AutoSize = true;
+            this.lblRevenueFrom.Location = new System.Drawing.Point(20, 15);
+            this.lblRevenueFrom.Name = "lblRevenueFrom";
+            this.lblRevenueFrom.Size = new System.Drawing.Size(66, 13);
+            this.lblRevenueFrom.TabIndex = 0;
+            this.lblRevenueFrom.Text = "Период с:";
+            // 
+            // tabDishSales
+            // 
+            this.tabDishSales.Controls.Add(this.dgvDishSales);
+            this.tabDishSales.Controls.Add(this.panelDishSales);
+            this.tabDishSales.Location = new System.Drawing.Point(4, 22);
+            this.tabDishSales.Name = "tabDishSales";
+            this.tabDishSales.Padding = new System.Windows.Forms.Padding(3);
+            this.tabDishSales.Size = new System.Drawing.Size(992, 574);
+            this.tabDishSales.TabIndex = 1;
+            this.tabDishSales.Text = "Продажи блюд";
+            this.tabDishSales.UseVisualStyleBackColor = true;
+            // 
+            // dgvDishSales
+            // 
+            this.dgvDishSales.AllowUserToAddRows = false;
+            this.dgvDishSales.AllowUserToDeleteRows = false;
+            this.dgvDishSales.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
+            this.dgvDishSales.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
+            this.dgvDishSales.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.dgvDishSales.Location = new System.Drawing.Point(3, 45);
+            this.dgvDishSales.Name = "dgvDishSales";
+            this.dgvDishSales.ReadOnly = true;
+            this.dgvDishSales.Size = new System.Drawing.Size(986, 526);
+            this.dgvDishSales.TabIndex = 1;
+            // 
+            // panelDishSales
+            // 
+            this.panelDishSales.Controls.Add(this.btnDishSalesShow);
+            this.panelDishSales.Controls.Add(this.dtpDishSalesTo);
+            this.panelDishSales.Controls.Add(this.lblDishSalesTo);
+            this.panelDishSales.Controls.Add(this.dtpDishSalesFrom);
+            this.panelDishSales.Controls.Add(this.lblDishSalesFrom);
+            this.panelDishSales.Dock = System.Windows.Forms.DockStyle.Top;
+            this.panelDishSales.Location = new System.Drawing.Point(3, 3);
+            this.panelDishSales.Name = "panelDishSales";
+            this.panelDishSales.Size = new System.Drawing.Size(986, 42);
+            this.panelDishSales.TabIndex = 0;
+            // 
+            // btnDishSalesShow
+            // 
+            this.btnDishSalesShow.Location = new System.Drawing.Point(370, 10);
+            this.btnDishSalesShow.Name = "btnDishSalesShow";
+            this.btnDishSalesShow.Size = new System.Drawing.Size(100, 23);
+            this.btnDishSalesShow.TabIndex = 4;
+            this.btnDishSalesShow.Text = "Показать";
+            this.btnDishSalesShow.UseVisualStyleBackColor = true;
+            // 
+            // dtpDishSalesTo
+            // 
+            this.dtpDishSalesTo.Format = System.Windows.Forms.DateTimePickerFormat.Short;
+            this.dtpDishSalesTo.Location = new System.Drawing.Point(250, 12);
+            this.dtpDishSalesTo.Name = "dtpDishSalesTo";
+            this.dtpDishSalesTo.Size = new System.Drawing.Size(100, 20);
+            this.dtpDishSalesTo.TabIndex = 3;
+            // 
+            // lblDishSalesTo
+            // 
+            this.lblDishSalesTo.AutoSize = true;
+            this.lblDishSalesTo.Location = new System.Drawing.Point(220, 15);
+            this.lblDishSalesTo.Name = "lblDishSalesTo";
+            this.lblDishSalesTo.Size = new System.Drawing.Size(22, 13);
+            this.lblDishSalesTo.TabIndex = 2;
+            this.lblDishSalesTo.Text = "по:";
+            // 
+            // dtpDishSalesFrom
+            // 
+            this.dtpDishSalesFrom.Format = System.Windows.Forms.DateTimePickerFormat.Short;
+            this.dtpDishSalesFrom.Location = new System.Drawing.Point(100, 12);
+            this.dtpDishSalesFrom.Name = "dtpDishSalesFrom";
+            this.dtpDishSalesFrom.Size = new System.Drawing.Size(100, 20);
+            this.dtpDishSalesFrom.TabIndex = 1;
+            // 
+            // lblDishSalesFrom
+            // 
+            this.lblDishSalesFrom.AutoSize = true;
+            this.lblDishSalesFrom.Location = new System.Drawing.Point(20, 15);
+            this.lblDishSalesFrom.Name = "lblDishSalesFrom";
+            this.lblDishSalesFrom.Size = new System.Drawing.Size(66, 13);
+            this.lblDishSalesFrom.TabIndex = 0;
+            this.lblDishSalesFrom.Text = "Период с:";
+            // 
+            // tabWaiterStats
+            // 
+            this.tabWaiterStats.Controls.Add(this.dgvWaiterStats);
+            this.tabWaiterStats.Controls.Add(this.panelWaiterStats);
+            this.tabWaiterStats.Location = new System.Drawing.Point(4, 22);
+            this.tabWaiterStats.Name = "tabWaiterStats";
+            this.tabWaiterStats.Size = new System.Drawing.Size(992, 574);
+            this.tabWaiterStats.TabIndex = 2;
+            this.tabWaiterStats.Text = "Официанты";
+            this.tabWaiterStats.UseVisualStyleBackColor = true;
+            // 
+            // dgvWaiterStats
+            // 
+            this.dgvWaiterStats.AllowUserToAddRows = false;
+            this.dgvWaiterStats.AllowUserToDeleteRows = false;
+            this.dgvWaiterStats.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
+            this.dgvWaiterStats.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
+            this.dgvWaiterStats.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.dgvWaiterStats.Location = new System.Drawing.Point(0, 45);
+            this.dgvWaiterStats.Name = "dgvWaiterStats";
+            this.dgvWaiterStats.ReadOnly = true;
+            this.dgvWaiterStats.Size = new System.Drawing.Size(992, 529);
+            this.dgvWaiterStats.TabIndex = 1;
+            // 
+            // panelWaiterStats
+            // 
+            this.panelWaiterStats.Controls.Add(this.btnWaiterStatsShow);
+            this.panelWaiterStats.Controls.Add(this.dtpWaiterStatsTo);
+            this.panelWaiterStats.Controls.Add(this.lblWaiterStatsTo);
+            this.panelWaiterStats.Controls.Add(this.dtpWaiterStatsFrom);
+            this.panelWaiterStats.Controls.Add(this.lblWaiterStatsFrom);
+            this.panelWaiterStats.Dock = System.Windows.Forms.DockStyle.Top;
+            this.panelWaiterStats.Location = new System.Drawing.Point(0, 0);
+            this.panelWaiterStats.Name = "panelWaiterStats";
+            this.panelWaiterStats.Size = new System.Drawing.Size(992, 45);
+            this.panelWaiterStats.TabIndex = 0;
+            // 
+            // btnWaiterStatsShow
+            // 
+            this.btnWaiterStatsShow.Location = new System.Drawing.Point(370, 10);
+            this.btnWaiterStatsShow.Name = "btnWaiterStatsShow";
+            this.btnWaiterStatsShow.Size = new System.Drawing.Size(100, 23);
+            this.btnWaiterStatsShow.TabIndex = 4;
+            this.btnWaiterStatsShow.Text = "Показать";
+            this.btnWaiterStatsShow.UseVisualStyleBackColor = true;
+            // 
+            // dtpWaiterStatsTo
+            // 
+            this.dtpWaiterStatsTo.Format = System.Windows.Forms.DateTimePickerFormat.Short;
+            this.dtpWaiterStatsTo.Location = new System.Drawing.Point(250, 12);
+            this.dtpWaiterStatsTo.Name = "dtpWaiterStatsTo";
+            this.dtpWaiterStatsTo.Size = new System.Drawing.Size(100, 20);
+            this.dtpWaiterStatsTo.TabIndex = 3;
+            // 
+            // lblWaiterStatsTo
+            // 
+            this.lblWaiterStatsTo.AutoSize = true;
+            this.lblWaiterStatsTo.Location = new System.Drawing.Point(220, 15);
+            this.lblWaiterStatsTo.Name = "lblWaiterStatsTo";
+            this.lblWaiterStatsTo.Size = new System.Drawing.Size(22, 13);
+            this.lblWaiterStatsTo.TabIndex = 2;
+            this.lblWaiterStatsTo.Text = "по:";
+            // 
+            // dtpWaiterStatsFrom
+            // 
+            this.dtpWaiterStatsFrom.Format = System.Windows.Forms.DateTimePickerFormat.Short;
+            this.dtpWaiterStatsFrom.Location = new System.Drawing.Point(100, 12);
+            this.dtpWaiterStatsFrom.Name = "dtpWaiterStatsFrom";
+            this.dtpWaiterStatsFrom.Size = new System.Drawing.Size(100, 20);
+            this.dtpWaiterStatsFrom.TabIndex = 1;
+            // 
+            // lblWaiterStatsFrom
+            // 
+            this.lblWaiterStatsFrom.AutoSize = true;
+            this.lblWaiterStatsFrom.Location = new System.Drawing.Point(20, 15);
+            this.lblWaiterStatsFrom.Name = "lblWaiterStatsFrom";
+            this.lblWaiterStatsFrom.Size = new System.Drawing.Size(66, 13);
+            this.lblWaiterStatsFrom.TabIndex = 0;
+            this.lblWaiterStatsFrom.Text = "Период с:";
+            // 
+            // tabPaymentMethods
+            // 
+            this.tabPaymentMethods.Controls.Add(this.dgvPaymentMethods);
+            this.tabPaymentMethods.Controls.Add(this.panelPaymentMethods);
+            this.tabPaymentMethods.Location = new System.Drawing.Point(4, 22);
+            this.tabPaymentMethods.Name = "tabPaymentMethods";
+            this.tabPaymentMethods.Size = new System.Drawing.Size(992, 574);
+            this.tabPaymentMethods.TabIndex = 3;
+            this.tabPaymentMethods.Text = "Способы оплаты";
+            this.tabPaymentMethods.UseVisualStyleBackColor = true;
+            // 
+            // dgvPaymentMethods
+            // 
+            this.dgvPaymentMethods.AllowUserToAddRows = false;
+            this.dgvPaymentMethods.AllowUserToDeleteRows = false;
+            this.dgvPaymentMethods.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
+            this.dgvPaymentMethods.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
+            this.dgvPaymentMethods.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.dgvPaymentMethods.Location = new System.Drawing.Point(0, 45);
+            this.dgvPaymentMethods.Name = "dgvPaymentMethods";
+            this.dgvPaymentMethods.ReadOnly = true;
+            this.dgvPaymentMethods.Size = new System.Drawing.Size(992, 529);
+            this.dgvPaymentMethods.TabIndex = 1;
+            // 
+            // panelPaymentMethods
+            // 
+            this.panelPaymentMethods.Controls.Add(this.btnPaymentMethodsShow);
+            this.panelPaymentMethods.Controls.Add(this.dtpPaymentMethodsTo);
+            this.panelPaymentMethods.Controls.Add(this.lblPaymentMethodsTo);
+            this.panelPaymentMethods.Controls.Add(this.dtpPaymentMethodsFrom);
+            this.panelPaymentMethods.Controls.Add(this.lblPaymentMethodsFrom);
+            this.panelPaymentMethods.Dock = System.Windows.Forms.DockStyle.Top;
+            this.panelPaymentMethods.Location = new System.Drawing.Point(0, 0);
+            this.panelPaymentMethods.Name = "panelPaymentMethods";
+            this.panelPaymentMethods.Size = new System.Drawing.Size(992, 45);
+            this.panelPaymentMethods.TabIndex = 0;
+            // 
+            // btnPaymentMethodsShow
+            // 
+            this.btnPaymentMethodsShow.Location = new System.Drawing.Point(370, 10);
+            this.btnPaymentMethodsShow.Name = "btnPaymentMethodsShow";
+            this.btnPaymentMethodsShow.Size = new System.Drawing.Size(100, 23);
+            this.btnPaymentMethodsShow.TabIndex = 4;
+            this.btnPaymentMethodsShow.Text = "Показать";
+            this.btnPaymentMethodsShow.UseVisualStyleBackColor = true;
+            // 
+            // dtpPaymentMethodsTo
+            // 
+            this.dtpPaymentMethodsTo.Format = System.Windows.Forms.DateTimePickerFormat.Short;
+            this.dtpPaymentMethodsTo.Location = new System.Drawing.Point(250, 12);
+            this.dtpPaymentMethodsTo.Name = "dtpPaymentMethodsTo";
+            this.dtpPaymentMethodsTo.Size = new System.Drawing.Size(100, 20);
+            this.dtpPaymentMethodsTo.TabIndex = 3;
+            // 
+            // lblPaymentMethodsTo
+            // 
+            this.lblPaymentMethodsTo.AutoSize = true;
+            this.lblPaymentMethodsTo.Location = new System.Drawing.Point(220, 15);
+            this.lblPaymentMethodsTo.Name = "lblPaymentMethodsTo";
+            this.lblPaymentMethodsTo.Size = new System.Drawing.Size(22, 13);
+            this.lblPaymentMethodsTo.TabIndex = 2;
+            this.lblPaymentMethodsTo.Text = "по:";
+            // 
+            // dtpPaymentMethodsFrom
+            // 
+            this.dtpPaymentMethodsFrom.Format = System.Windows.Forms.DateTimePickerFormat.Short;
+            this.dtpPaymentMethodsFrom.Location = new System.Drawing.Point(100, 12);
+            this.dtpPaymentMethodsFrom.Name = "dtpPaymentMethodsFrom";
+            this.dtpPaymentMethodsFrom.Size = new System.Drawing.Size(100, 20);
+            this.dtpPaymentMethodsFrom.TabIndex = 1;
+            // 
+            // lblPaymentMethodsFrom
+            // 
+            this.lblPaymentMethodsFrom.AutoSize = true;
+            this.lblPaymentMethodsFrom.Location = new System.Drawing.Point(20, 15);
+            this.lblPaymentMethodsFrom.Name = "lblPaymentMethodsFrom";
+            this.lblPaymentMethodsFrom.Size = new System.Drawing.Size(66, 13);
+            this.lblPaymentMethodsFrom.TabIndex = 0;
+            this.lblPaymentMethodsFrom.Text = "Период с:";
+            // 
+            // tabLowStock
+            // 
+            this.tabLowStock.Controls.Add(this.dgvLowStock);
+            this.tabLowStock.Controls.Add(this.btnLowStockRefresh);
+            this.tabLowStock.Location = new System.Drawing.Point(4, 22);
+            this.tabLowStock.Name = "tabLowStock";
+            this.tabLowStock.Size = new System.Drawing.Size(992, 574);
+            this.tabLowStock.TabIndex = 4;
+            this.tabLowStock.Text = "Остатки";
+            this.tabLowStock.UseVisualStyleBackColor = true;
+            // 
+            // dgvLowStock
+            // 
+            this.dgvLowStock.AllowUserToAddRows = false;
+            this.dgvLowStock.AllowUserToDeleteRows = false;
+            this.dgvLowStock.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
+            this.dgvLowStock.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
+            this.dgvLowStock.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.dgvLowStock.Location = new System.Drawing.Point(0, 45);
+            this.dgvLowStock.Name = "dgvLowStock";
+            this.dgvLowStock.ReadOnly = true;
+            this.dgvLowStock.Size = new System.Drawing.Size(992, 529);
+            this.dgvLowStock.TabIndex = 1;
+            // 
+            // btnLowStockRefresh
+            // 
+            this.btnLowStockRefresh.Location = new System.Drawing.Point(20, 12);
+            this.btnLowStockRefresh.Name = "btnLowStockRefresh";
+            this.btnLowStockRefresh.Size = new System.Drawing.Size(100, 23);
+            this.btnLowStockRefresh.TabIndex = 0;
+            this.btnLowStockRefresh.Text = "Обновить";
+            this.btnLowStockRefresh.UseVisualStyleBackColor = true;
+            // 
+            // ReportForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(1000, 600);
+            this.Controls.Add(this.tabControl);
+            this.Name = "ReportForm";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+            this.Text = "Отчёты";
+            this.tabControl.ResumeLayout(false);
+            this.tabRevenue.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.dgvRevenue)).EndInit();
+            this.panelRevenue.ResumeLayout(false);
+            this.panelRevenue.PerformLayout();
+            this.tabDishSales.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.dgvDishSales)).EndInit();
+            this.panelDishSales.ResumeLayout(false);
+            this.panelDishSales.PerformLayout();
+            this.tabWaiterStats.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.dgvWaiterStats)).EndInit();
+            this.panelWaiterStats.ResumeLayout(false);
+            this.panelWaiterStats.PerformLayout();
+            this.tabPaymentMethods.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.dgvPaymentMethods)).EndInit();
+            this.panelPaymentMethods.ResumeLayout(false);
+            this.panelPaymentMethods.PerformLayout();
+            this.tabLowStock.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.dgvLowStock)).EndInit();
+            this.ResumeLayout(false);
+        }
+
+        private System.Windows.Forms.TabControl tabControl;
+        private System.Windows.Forms.TabPage tabRevenue;
+        private System.Windows.Forms.TabPage tabDishSales;
+        private System.Windows.Forms.TabPage tabWaiterStats;
+        private System.Windows.Forms.TabPage tabPaymentMethods;
+        private System.Windows.Forms.TabPage tabLowStock;
+        private System.Windows.Forms.Panel panelRevenue;
+        private System.Windows.Forms.DateTimePicker dtpRevenueFrom;
+        private System.Windows.Forms.Label lblRevenueFrom;
+        private System.Windows.Forms.DateTimePicker dtpRevenueTo;
+        private System.Windows.Forms.Label lblRevenueTo;
+        private System.Windows.Forms.Button btnRevenueShow;
+        private System.Windows.Forms.DataGridView dgvRevenue;
+        private System.Windows.Forms.Panel panelDishSales;
+        private System.Windows.Forms.Button btnDishSalesShow;
+        private System.Windows.Forms.DateTimePicker dtpDishSalesTo;
+        private System.Windows.Forms.Label lblDishSalesTo;
+        private System.Windows.Forms.DateTimePicker dtpDishSalesFrom;
+        private System.Windows.Forms.Label lblDishSalesFrom;
+        private System.Windows.Forms.DataGridView dgvDishSales;
+        private System.Windows.Forms.Panel panelWaiterStats;
+        private System.Windows.Forms.Button btnWaiterStatsShow;
+        private System.Windows.Forms.DateTimePicker dtpWaiterStatsTo;
+        private System.Windows.Forms.Label lblWaiterStatsTo;
+        private System.Windows.Forms.DateTimePicker dtpWaiterStatsFrom;
+        private System.Windows.Forms.Label lblWaiterStatsFrom;
+        private System.Windows.Forms.DataGridView dgvWaiterStats;
+        private System.Windows.Forms.Panel panelPaymentMethods;
+        private System.Windows.Forms.Button btnPaymentMethodsShow;
+        private System.Windows.Forms.DateTimePicker dtpPaymentMethodsTo;
+        private System.Windows.Forms.Label lblPaymentMethodsTo;
+        private System.Windows.Forms.DateTimePicker dtpPaymentMethodsFrom;
+        private System.Windows.Forms.Label lblPaymentMethodsFrom;
+        private System.Windows.Forms.DataGridView dgvPaymentMethods;
+        private System.Windows.Forms.DataGridView dgvLowStock;
+        private System.Windows.Forms.Button btnLowStockRefresh;
+    }
+}

+ 146 - 0
ReportForm.cs

@@ -0,0 +1,146 @@
+using System;
+using System.Windows.Forms;
+using RestaurantApp.DAL;
+
+namespace RestaurantApp
+{
+    public partial class ReportForm : Form
+    {
+        private ReportRepository repo = new ReportRepository();
+
+        public ReportForm()
+        {
+            InitializeComponent();
+
+            // Устанавливаем даты по умолчанию
+            dtpRevenueFrom.Value = DateTime.Now.AddMonths(-1);
+            dtpRevenueTo.Value = DateTime.Now;
+            dtpDishSalesFrom.Value = DateTime.Now.AddMonths(-1);
+            dtpDishSalesTo.Value = DateTime.Now;
+            dtpWaiterStatsFrom.Value = DateTime.Now.AddMonths(-1);
+            dtpWaiterStatsTo.Value = DateTime.Now;
+            dtpPaymentMethodsFrom.Value = DateTime.Now.AddMonths(-1);
+            dtpPaymentMethodsTo.Value = DateTime.Now;
+
+            // Подписываем события
+            btnRevenueShow.Click += (s, e) => LoadRevenueReport();
+            btnDishSalesShow.Click += (s, e) => LoadDishSalesReport();
+            btnWaiterStatsShow.Click += (s, e) => LoadWaiterStatsReport();
+            btnPaymentMethodsShow.Click += (s, e) => LoadPaymentMethodsReport();
+            btnLowStockRefresh.Click += (s, e) => LoadLowStockReport();
+
+            // Загружаем данные
+            LoadRevenueReport();
+            LoadDishSalesReport();
+            LoadWaiterStatsReport();
+            LoadPaymentMethodsReport();
+            LoadLowStockReport();
+        }
+
+        private void LoadRevenueReport()
+        {
+            try
+            {
+                var dt = repo.GetDailyRevenue(dtpRevenueFrom.Value, dtpRevenueTo.Value);
+                dgvRevenue.DataSource = dt;
+
+                if (dgvRevenue.Columns.Count > 0)
+                {
+                    dgvRevenue.Columns["Дата"].HeaderText = "Дата";
+                    dgvRevenue.Columns["Платежей"].HeaderText = "Кол-во платежей";
+                    dgvRevenue.Columns["Выручка (руб.)"].HeaderText = "Выручка (руб.)";
+                    dgvRevenue.Columns["Средний чек (руб.)"].HeaderText = "Средний чек (руб.)";
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}");
+            }
+        }
+
+        private void LoadDishSalesReport()
+        {
+            try
+            {
+                var dt = repo.GetDishSales(dtpDishSalesFrom.Value, dtpDishSalesTo.Value);
+                dgvDishSales.DataSource = dt;
+
+                if (dgvDishSales.Columns.Count > 0)
+                {
+                    dgvDishSales.Columns["Блюдо"].HeaderText = "Блюдо";
+                    dgvDishSales.Columns["Категория"].HeaderText = "Категория";
+                    dgvDishSales.Columns["Кол-во"].HeaderText = "Кол-во продаж";
+                    dgvDishSales.Columns["Выручка (руб.)"].HeaderText = "Выручка (руб.)";
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}");
+            }
+        }
+
+        private void LoadWaiterStatsReport()
+        {
+            try
+            {
+                var dt = repo.GetWaiterStats(dtpWaiterStatsFrom.Value, dtpWaiterStatsTo.Value);
+                dgvWaiterStats.DataSource = dt;
+
+                if (dgvWaiterStats.Columns.Count > 0)
+                {
+                    dgvWaiterStats.Columns["Официант"].HeaderText = "Официант";
+                    dgvWaiterStats.Columns["Заказов"].HeaderText = "Кол-во заказов";
+                    dgvWaiterStats.Columns["Сумма (руб.)"].HeaderText = "Сумма (руб.)";
+                    dgvWaiterStats.Columns["Средний заказ (руб.)"].HeaderText = "Средний чек (руб.)";
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}");
+            }
+        }
+
+        private void LoadPaymentMethodsReport()
+        {
+            try
+            {
+                var dt = repo.GetPaymentMethods(dtpPaymentMethodsFrom.Value, dtpPaymentMethodsTo.Value);
+                dgvPaymentMethods.DataSource = dt;
+
+                if (dgvPaymentMethods.Columns.Count > 0)
+                {
+                    dgvPaymentMethods.Columns["Способ оплаты"].HeaderText = "Способ оплаты";
+                    dgvPaymentMethods.Columns["Кол-во"].HeaderText = "Кол-во";
+                    dgvPaymentMethods.Columns["Сумма (руб.)"].HeaderText = "Сумма (руб.)";
+                    dgvPaymentMethods.Columns["%"].HeaderText = "Доля (%)";
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}");
+            }
+        }
+
+        private void LoadLowStockReport()
+        {
+            try
+            {
+                var dt = repo.GetLowStock();
+                dgvLowStock.DataSource = dt;
+
+                if (dgvLowStock.Columns.Count > 0)
+                {
+                    dgvLowStock.Columns["Ингредиент"].HeaderText = "Ингредиент";
+                    dgvLowStock.Columns["Ед. изм."].HeaderText = "Ед. изм.";
+                    dgvLowStock.Columns["Остаток"].HeaderText = "Остаток";
+                    dgvLowStock.Columns["Минимум"].HeaderText = "Минимальный запас";
+                    dgvLowStock.Columns["Поставщик"].HeaderText = "Поставщик";
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}");
+            }
+        }
+    }
+}

+ 120 - 0
ReportForm.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>

+ 115 - 0
ReportRepository.cs

@@ -0,0 +1,115 @@
+using System;
+using System.Data;
+using Npgsql;
+
+namespace RestaurantApp.DAL
+{
+    public class ReportRepository
+    {
+        public DataTable GetDailyRevenue(DateTime dateFrom, DateTime dateTo)
+        {
+            var dt = new DataTable();
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                @"SELECT date(paid_at) as ""Дата"",
+                         COUNT(*) as ""Платежей"",
+                         SUM(amount) as ""Выручка (руб.)"",
+                         AVG(amount) as ""Средний чек (руб.)""
+                  FROM payment
+                  WHERE paid_at::date BETWEEN @df AND @dt
+                  GROUP BY date(paid_at) ORDER BY date(paid_at) DESC", conn))
+            {
+                cmd.Parameters.AddWithValue("df", dateFrom.Date);
+                cmd.Parameters.AddWithValue("dt", dateTo.Date);
+                using (var da = new NpgsqlDataAdapter(cmd)) da.Fill(dt);
+            }
+            return dt;
+        }
+
+        public DataTable GetDishSales(DateTime dateFrom, DateTime dateTo)
+        {
+            var dt = new DataTable();
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                @"SELECT d.name as ""Блюдо"",
+                         dc.name as ""Категория"",
+                         SUM(oi.quantity) as ""Кол-во"",
+                         SUM(oi.quantity*oi.unit_price) as ""Выручка (руб.)""
+                  FROM order_item oi
+                  JOIN dish d ON d.dish_id=oi.dish_id
+                  JOIN dish_category dc ON dc.category_id=d.category_id
+                  JOIN ""order"" o ON o.order_id=oi.order_id
+                  WHERE oi.status NOT IN ('cancelled')
+                    AND o.opened_at::date BETWEEN @df AND @dt
+                  GROUP BY d.name, dc.name
+                  ORDER BY SUM(oi.quantity) DESC", conn))
+            {
+                cmd.Parameters.AddWithValue("df", dateFrom.Date);
+                cmd.Parameters.AddWithValue("dt", dateTo.Date);
+                using (var da = new NpgsqlDataAdapter(cmd)) da.Fill(dt);
+            }
+            return dt;
+        }
+
+        public DataTable GetWaiterStats(DateTime dateFrom, DateTime dateTo)
+        {
+            var dt = new DataTable();
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                @"SELECT e.last_name||' '||e.first_name as ""Официант"",
+                         COUNT(DISTINCT o.order_id) as ""Заказов"",
+                         SUM(o.total_amount) as ""Сумма (руб.)"",
+                         AVG(o.total_amount) as ""Средний заказ (руб.)""
+                  FROM ""order"" o
+                  JOIN employee e ON e.employee_id=o.employee_id
+                  WHERE o.status='closed'
+                    AND o.opened_at::date BETWEEN @df AND @dt
+                  GROUP BY e.last_name, e.first_name
+                  ORDER BY SUM(o.total_amount) DESC", conn))
+            {
+                cmd.Parameters.AddWithValue("df", dateFrom.Date);
+                cmd.Parameters.AddWithValue("dt", dateTo.Date);
+                using (var da = new NpgsqlDataAdapter(cmd)) da.Fill(dt);
+            }
+            return dt;
+        }
+
+        public DataTable GetPaymentMethods(DateTime dateFrom, DateTime dateTo)
+        {
+            var dt = new DataTable();
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                @"SELECT method as ""Способ оплаты"",
+                         COUNT(*) as ""Кол-во"",
+                         SUM(amount) as ""Сумма (руб.)"",
+                         ROUND(100.0*COUNT(*)/SUM(COUNT(*)) OVER(),1) as ""%""
+                  FROM payment
+                  WHERE paid_at::date BETWEEN @df AND @dt
+                  GROUP BY method ORDER BY SUM(amount) DESC", conn))
+            {
+                cmd.Parameters.AddWithValue("df", dateFrom.Date);
+                cmd.Parameters.AddWithValue("dt", dateTo.Date);
+                using (var da = new NpgsqlDataAdapter(cmd)) da.Fill(dt);
+            }
+            return dt;
+        }
+
+        public DataTable GetLowStock()
+        {
+            var dt = new DataTable();
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                @"SELECT i.name as ""Ингредиент"",
+                         i.unit as ""Ед. изм."",
+                         i.stock_qty as ""Остаток"",
+                         i.min_stock as ""Минимум"",
+                         COALESCE(s.name,'') as ""Поставщик""
+                  FROM ingredient i
+                  LEFT JOIN supplier s ON s.supplier_id=i.supplier_id
+                  WHERE i.stock_qty < i.min_stock
+                  ORDER BY (i.stock_qty - i.min_stock)", conn))
+            using (var da = new NpgsqlDataAdapter(cmd)) da.Fill(dt);
+            return dt;
+        }
+    }
+}

+ 21 - 0
Reservation.cs

@@ -0,0 +1,21 @@
+using System;
+
+namespace RestaurantApp.Models
+{
+    public class Reservation
+    {
+        public int ReservationId { get; set; }
+        public int TableId { get; set; }
+        public string TableInfo { get; set; }
+        public int? ClientId { get; set; }
+        public string ClientName { get; set; }
+        public int? EmployeeId { get; set; }
+        public string EmployeeName { get; set; }
+        public DateTime ReservedAt { get; set; }
+        public int DurationMin { get; set; }
+        public int GuestsCount { get; set; }
+        public string Status { get; set; }
+        public string Notes { get; set; }
+        public DateTime CreatedAt { get; set; }
+    }
+}

+ 267 - 0
ReservationEditForm.Designer.cs

@@ -0,0 +1,267 @@
+namespace RestaurantApp
+{
+    partial class ReservationEditForm
+    {
+        private System.ComponentModel.IContainer components = null;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.lblTable = new System.Windows.Forms.Label();
+            this.cmbTable = new System.Windows.Forms.ComboBox();
+            this.lblClient = new System.Windows.Forms.Label();
+            this.cmbClient = new System.Windows.Forms.ComboBox();
+            this.lblEmployee = new System.Windows.Forms.Label();
+            this.cmbEmployee = new System.Windows.Forms.ComboBox();
+            this.lblDateTime = new System.Windows.Forms.Label();
+            this.dtpReservedAt = new System.Windows.Forms.DateTimePicker();
+            this.lblDuration = new System.Windows.Forms.Label();
+            this.numDuration = new System.Windows.Forms.NumericUpDown();
+            this.lblGuests = new System.Windows.Forms.Label();
+            this.numGuests = new System.Windows.Forms.NumericUpDown();
+            this.lblStatus = new System.Windows.Forms.Label();
+            this.cmbStatus = new System.Windows.Forms.ComboBox();
+            this.lblNotes = new System.Windows.Forms.Label();
+            this.txtNotes = new System.Windows.Forms.TextBox();
+            this.btnSave = new System.Windows.Forms.Button();
+            this.btnCancel = new System.Windows.Forms.Button();
+            this.chkNoClient = new System.Windows.Forms.CheckBox();
+            ((System.ComponentModel.ISupportInitialize)(this.numDuration)).BeginInit();
+            ((System.ComponentModel.ISupportInitialize)(this.numGuests)).BeginInit();
+            this.SuspendLayout();
+            // 
+            // lblTable
+            // 
+            this.lblTable.AutoSize = true;
+            this.lblTable.Location = new System.Drawing.Point(30, 25);
+            this.lblTable.Name = "lblTable";
+            this.lblTable.Size = new System.Drawing.Size(38, 13);
+            this.lblTable.TabIndex = 0;
+            this.lblTable.Text = "Стол:";
+            // 
+            // cmbTable
+            // 
+            this.cmbTable.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbTable.Location = new System.Drawing.Point(120, 22);
+            this.cmbTable.Name = "cmbTable";
+            this.cmbTable.Size = new System.Drawing.Size(250, 21);
+            this.cmbTable.TabIndex = 1;
+            // 
+            // lblClient
+            // 
+            this.lblClient.AutoSize = true;
+            this.lblClient.Location = new System.Drawing.Point(30, 55);
+            this.lblClient.Name = "lblClient";
+            this.lblClient.Size = new System.Drawing.Size(46, 13);
+            this.lblClient.TabIndex = 2;
+            this.lblClient.Text = "Клиент:";
+            // 
+            // cmbClient
+            // 
+            this.cmbClient.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbClient.Location = new System.Drawing.Point(120, 52);
+            this.cmbClient.Name = "cmbClient";
+            this.cmbClient.Size = new System.Drawing.Size(250, 21);
+            this.cmbClient.TabIndex = 3;
+            // 
+            // lblEmployee
+            // 
+            this.lblEmployee.AutoSize = true;
+            this.lblEmployee.Location = new System.Drawing.Point(30, 85);
+            this.lblEmployee.Name = "lblEmployee";
+            this.lblEmployee.Size = new System.Drawing.Size(66, 13);
+            this.lblEmployee.TabIndex = 4;
+            this.lblEmployee.Text = "Официант:";
+            // 
+            // cmbEmployee
+            // 
+            this.cmbEmployee.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbEmployee.Location = new System.Drawing.Point(120, 82);
+            this.cmbEmployee.Name = "cmbEmployee";
+            this.cmbEmployee.Size = new System.Drawing.Size(250, 21);
+            this.cmbEmployee.TabIndex = 5;
+            // 
+            // chkNoClient
+            // 
+            this.chkNoClient.AutoSize = true;
+            this.chkNoClient.Location = new System.Drawing.Point(120, 110);
+            this.chkNoClient.Name = "chkNoClient";
+            this.chkNoClient.Size = new System.Drawing.Size(103, 17);
+            this.chkNoClient.TabIndex = 6;
+            this.chkNoClient.Text = "Без клиента";
+            this.chkNoClient.UseVisualStyleBackColor = true;
+            // 
+            // lblDateTime
+            // 
+            this.lblDateTime.AutoSize = true;
+            this.lblDateTime.Location = new System.Drawing.Point(30, 140);
+            this.lblDateTime.Name = "lblDateTime";
+            this.lblDateTime.Size = new System.Drawing.Size(77, 13);
+            this.lblDateTime.TabIndex = 7;
+            this.lblDateTime.Text = "Дата и время:";
+            // 
+            // dtpReservedAt
+            // 
+            this.dtpReservedAt.CustomFormat = "dd.MM.yyyy HH:mm";
+            this.dtpReservedAt.Format = System.Windows.Forms.DateTimePickerFormat.Custom;
+            this.dtpReservedAt.Location = new System.Drawing.Point(120, 137);
+            this.dtpReservedAt.Name = "dtpReservedAt";
+            this.dtpReservedAt.Size = new System.Drawing.Size(150, 20);
+            this.dtpReservedAt.TabIndex = 8;
+            // 
+            // lblDuration
+            // 
+            this.lblDuration.AutoSize = true;
+            this.lblDuration.Location = new System.Drawing.Point(30, 170);
+            this.lblDuration.Name = "lblDuration";
+            this.lblDuration.Size = new System.Drawing.Size(76, 13);
+            this.lblDuration.TabIndex = 9;
+            this.lblDuration.Text = "Длительность:";
+            // 
+            // numDuration
+            // 
+            this.numDuration.Location = new System.Drawing.Point(120, 168);
+            this.numDuration.Maximum = new decimal(new int[] { 480, 0, 0, 0 });
+            this.numDuration.Minimum = new decimal(new int[] { 30, 0, 0, 0 });
+            this.numDuration.Name = "numDuration";
+            this.numDuration.Size = new System.Drawing.Size(100, 20);
+            this.numDuration.TabIndex = 10;
+            this.numDuration.Value = new decimal(new int[] { 120, 0, 0, 0 });
+            // 
+            // lblGuests
+            // 
+            this.lblGuests.AutoSize = true;
+            this.lblGuests.Location = new System.Drawing.Point(30, 200);
+            this.lblGuests.Name = "lblGuests";
+            this.lblGuests.Size = new System.Drawing.Size(67, 13);
+            this.lblGuests.TabIndex = 11;
+            this.lblGuests.Text = "Кол-во гостей:";
+            // 
+            // numGuests
+            // 
+            this.numGuests.Location = new System.Drawing.Point(120, 198);
+            this.numGuests.Minimum = new decimal(new int[] { 1, 0, 0, 0 });
+            this.numGuests.Name = "numGuests";
+            this.numGuests.Size = new System.Drawing.Size(100, 20);
+            this.numGuests.TabIndex = 12;
+            this.numGuests.Value = new decimal(new int[] { 2, 0, 0, 0 });
+            // 
+            // lblStatus
+            // 
+            this.lblStatus.AutoSize = true;
+            this.lblStatus.Location = new System.Drawing.Point(30, 230);
+            this.lblStatus.Name = "lblStatus";
+            this.lblStatus.Size = new System.Drawing.Size(44, 13);
+            this.lblStatus.TabIndex = 13;
+            this.lblStatus.Text = "Статус:";
+            // 
+            // cmbStatus
+            // 
+            this.cmbStatus.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbStatus.Items.AddRange(new object[] { "confirmed", "cancelled", "completed" });
+            this.cmbStatus.Location = new System.Drawing.Point(120, 227);
+            this.cmbStatus.Name = "cmbStatus";
+            this.cmbStatus.Size = new System.Drawing.Size(150, 21);
+            this.cmbStatus.TabIndex = 14;
+            // 
+            // lblNotes
+            // 
+            this.lblNotes.AutoSize = true;
+            this.lblNotes.Location = new System.Drawing.Point(30, 260);
+            this.lblNotes.Name = "lblNotes";
+            this.lblNotes.Size = new System.Drawing.Size(73, 13);
+            this.lblNotes.TabIndex = 15;
+            this.lblNotes.Text = "Примечание:";
+            // 
+            // txtNotes
+            // 
+            this.txtNotes.Location = new System.Drawing.Point(120, 257);
+            this.txtNotes.Multiline = true;
+            this.txtNotes.Name = "txtNotes";
+            this.txtNotes.Size = new System.Drawing.Size(250, 50);
+            this.txtNotes.TabIndex = 16;
+            // 
+            // btnSave
+            // 
+            this.btnSave.BackColor = System.Drawing.Color.LimeGreen;
+            this.btnSave.Location = new System.Drawing.Point(120, 320);
+            this.btnSave.Name = "btnSave";
+            this.btnSave.Size = new System.Drawing.Size(100, 30);
+            this.btnSave.TabIndex = 17;
+            this.btnSave.Text = "Сохранить";
+            this.btnSave.UseVisualStyleBackColor = false;
+            // 
+            // btnCancel
+            // 
+            this.btnCancel.Location = new System.Drawing.Point(240, 320);
+            this.btnCancel.Name = "btnCancel";
+            this.btnCancel.Size = new System.Drawing.Size(100, 30);
+            this.btnCancel.TabIndex = 18;
+            this.btnCancel.Text = "Отмена";
+            this.btnCancel.UseVisualStyleBackColor = true;
+            // 
+            // ReservationEditForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(420, 380);
+            this.Controls.Add(this.btnCancel);
+            this.Controls.Add(this.btnSave);
+            this.Controls.Add(this.txtNotes);
+            this.Controls.Add(this.lblNotes);
+            this.Controls.Add(this.cmbStatus);
+            this.Controls.Add(this.lblStatus);
+            this.Controls.Add(this.numGuests);
+            this.Controls.Add(this.lblGuests);
+            this.Controls.Add(this.numDuration);
+            this.Controls.Add(this.lblDuration);
+            this.Controls.Add(this.dtpReservedAt);
+            this.Controls.Add(this.lblDateTime);
+            this.Controls.Add(this.chkNoClient);
+            this.Controls.Add(this.cmbEmployee);
+            this.Controls.Add(this.lblEmployee);
+            this.Controls.Add(this.cmbClient);
+            this.Controls.Add(this.lblClient);
+            this.Controls.Add(this.cmbTable);
+            this.Controls.Add(this.lblTable);
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+            this.MaximizeBox = false;
+            this.MinimizeBox = false;
+            this.Name = "ReservationEditForm";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+            this.Text = "Бронирование";
+            ((System.ComponentModel.ISupportInitialize)(this.numDuration)).EndInit();
+            ((System.ComponentModel.ISupportInitialize)(this.numGuests)).EndInit();
+            this.ResumeLayout(false);
+            this.PerformLayout();
+        }
+
+        private System.Windows.Forms.Label lblTable;
+        private System.Windows.Forms.ComboBox cmbTable;
+        private System.Windows.Forms.Label lblClient;
+        private System.Windows.Forms.ComboBox cmbClient;
+        private System.Windows.Forms.Label lblEmployee;
+        private System.Windows.Forms.ComboBox cmbEmployee;
+        private System.Windows.Forms.CheckBox chkNoClient;
+        private System.Windows.Forms.Label lblDateTime;
+        private System.Windows.Forms.DateTimePicker dtpReservedAt;
+        private System.Windows.Forms.Label lblDuration;
+        private System.Windows.Forms.NumericUpDown numDuration;
+        private System.Windows.Forms.Label lblGuests;
+        private System.Windows.Forms.NumericUpDown numGuests;
+        private System.Windows.Forms.Label lblStatus;
+        private System.Windows.Forms.ComboBox cmbStatus;
+        private System.Windows.Forms.Label lblNotes;
+        private System.Windows.Forms.TextBox txtNotes;
+        private System.Windows.Forms.Button btnSave;
+        private System.Windows.Forms.Button btnCancel;
+    }
+}

+ 133 - 0
ReservationEditForm.cs

@@ -0,0 +1,133 @@
+using RestaurantApp.DAL;
+using RestaurantApp.Models;
+using System;
+using System.Data;
+using System.Linq;
+using System.Net;
+using System.Windows.Forms;
+
+namespace RestaurantApp
+{
+    public partial class ReservationEditForm : Form
+    {
+        private ReservationRepository repo = new ReservationRepository();
+        private OrderRepository orderRepo = new OrderRepository();
+        private EmployeeRepository empRepo = new EmployeeRepository();
+        private ClientRepository clientRepo = new ClientRepository();
+        private Reservation reservation;
+        private bool isEdit = false;
+
+        public ReservationEditForm()
+        {
+            InitializeComponent();
+            isEdit = false;
+            reservation = new Reservation();
+            this.Text = "Новое бронирование";
+            LoadData();
+            cmbStatus.SelectedIndex = 0;
+            chkNoClient.CheckedChanged += (s, e) => cmbClient.Enabled = !chkNoClient.Checked;
+
+            btnSave.Click += BtnSave_Click;
+            btnCancel.Click += (s, e) => DialogResult = DialogResult.Cancel;
+        }
+
+        public ReservationEditForm(Reservation existingReservation)
+        {
+            InitializeComponent();
+            isEdit = true;
+            reservation = existingReservation;
+            this.Text = "Редактирование бронирования";
+            LoadData();
+            LoadReservationData();
+
+            btnSave.Click += BtnSave_Click;
+            btnCancel.Click += (s, e) => DialogResult = DialogResult.Cancel;
+        }
+
+        private void LoadData()
+        {
+            // Загружаем столы
+            var tables = orderRepo.GetTables();
+            cmbTable.DisplayMember = "info";
+            cmbTable.ValueMember = "table_id";
+            cmbTable.DataSource = tables;
+
+            // Загружаем официантов
+            var employees = empRepo.GetWaiters();
+            cmbEmployee.DisplayMember = "FullName";
+            cmbEmployee.ValueMember = "EmployeeId";
+            cmbEmployee.DataSource = employees;
+
+            // Загружаем клиентов
+            var clients = clientRepo.GetAll();
+            var clientList = clients.Select(c => new { c.ClientId, FullName = $"{c.LastName} {c.FirstName} ({c.Phone})" }).ToList();
+            cmbClient.DisplayMember = "FullName";
+            cmbClient.ValueMember = "ClientId";
+            cmbClient.DataSource = clientList;
+
+            // Выбираем текущего пользователя как официанта
+            if (!string.IsNullOrEmpty(Program.CurrentRole) && Program.CurrentRole == "waiter")
+            {
+                var currentEmp = employees.FirstOrDefault(e => e.FullName == Program.CurrentUser);
+                if (currentEmp != null)
+                    cmbEmployee.SelectedValue = currentEmp.EmployeeId;
+            }
+        }
+
+        private void LoadReservationData()
+        {
+            cmbTable.SelectedValue = reservation.TableId;
+            if (reservation.ClientId.HasValue)
+            {
+                cmbClient.SelectedValue = reservation.ClientId.Value;
+                chkNoClient.Checked = false;
+            }
+            else
+            {
+                chkNoClient.Checked = true;
+                cmbClient.Enabled = false;
+            }
+            if (reservation.EmployeeId.HasValue)
+                cmbEmployee.SelectedValue = reservation.EmployeeId.Value;
+            dtpReservedAt.Value = reservation.ReservedAt;
+            numDuration.Value = reservation.DurationMin;
+            numGuests.Value = reservation.GuestsCount;
+            cmbStatus.Text = reservation.Status;
+            txtNotes.Text = reservation.Notes;
+        }
+
+        private void SaveData()
+        {
+            reservation.TableId = (int)cmbTable.SelectedValue;
+            reservation.ClientId = chkNoClient.Checked ? (int?)null : (int?)cmbClient.SelectedValue;
+            reservation.EmployeeId = (int?)cmbEmployee.SelectedValue;
+            reservation.ReservedAt = dtpReservedAt.Value;
+            reservation.DurationMin = (int)numDuration.Value;
+            reservation.GuestsCount = (int)numGuests.Value;
+            reservation.Status = cmbStatus.Text;
+            reservation.Notes = txtNotes.Text;
+
+            if (reservation.GuestsCount <= 0)
+                throw new Exception("Количество гостей должно быть больше 0");
+
+            if (isEdit)
+                repo.Update(reservation);
+            else
+                repo.Insert(reservation);
+        }
+
+        private void BtnSave_Click(object sender, EventArgs e)
+        {
+            try
+            {
+                SaveData();
+                DialogResult = DialogResult.OK;
+                Close();
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show(ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
+            }
+        }
+    }
+}

+ 120 - 0
ReservationEditForm.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>

+ 261 - 0
ReservationForm.Designer.cs

@@ -0,0 +1,261 @@
+namespace RestaurantApp
+{
+    partial class ReservationForm
+    {
+        private System.ComponentModel.IContainer components = null;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.dgvReservations = new System.Windows.Forms.DataGridView();
+            this.panelTop = new System.Windows.Forms.Panel();
+            this.cmbStatusFilter = new System.Windows.Forms.ComboBox();
+            this.lblStatusFilter = new System.Windows.Forms.Label();
+            this.txtSearch = new System.Windows.Forms.TextBox();
+            this.btnSearch = new System.Windows.Forms.Button();
+            this.btnRefresh = new System.Windows.Forms.Button();
+            this.btnAdd = new System.Windows.Forms.Button();
+            this.btnEdit = new System.Windows.Forms.Button();
+            this.btnDelete = new System.Windows.Forms.Button();
+            this.groupDetails = new System.Windows.Forms.GroupBox();
+            this.lblNotes = new System.Windows.Forms.Label();
+            this.lblDuration = new System.Windows.Forms.Label();
+            this.lblDateTime = new System.Windows.Forms.Label();
+            this.lblGuests = new System.Windows.Forms.Label();
+            this.lblPhone = new System.Windows.Forms.Label();
+            this.lblClient = new System.Windows.Forms.Label();
+            this.lblTable = new System.Windows.Forms.Label();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvReservations)).BeginInit();
+            this.panelTop.SuspendLayout();
+            this.groupDetails.SuspendLayout();
+            this.SuspendLayout();
+            // 
+            // dgvReservations
+            // 
+            this.dgvReservations.AllowUserToAddRows = false;
+            this.dgvReservations.AllowUserToDeleteRows = false;
+            this.dgvReservations.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
+            this.dgvReservations.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
+            this.dgvReservations.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.dgvReservations.Location = new System.Drawing.Point(0, 50);
+            this.dgvReservations.Name = "dgvReservations";
+            this.dgvReservations.ReadOnly = true;
+            this.dgvReservations.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
+            this.dgvReservations.Size = new System.Drawing.Size(900, 400);
+            this.dgvReservations.TabIndex = 0;
+            // 
+            // panelTop
+            // 
+            this.panelTop.Controls.Add(this.cmbStatusFilter);
+            this.panelTop.Controls.Add(this.lblStatusFilter);
+            this.panelTop.Controls.Add(this.txtSearch);
+            this.panelTop.Controls.Add(this.btnSearch);
+            this.panelTop.Controls.Add(this.btnRefresh);
+            this.panelTop.Controls.Add(this.btnAdd);
+            this.panelTop.Controls.Add(this.btnEdit);
+            this.panelTop.Controls.Add(this.btnDelete);
+            this.panelTop.Dock = System.Windows.Forms.DockStyle.Top;
+            this.panelTop.Location = new System.Drawing.Point(0, 0);
+            this.panelTop.Name = "panelTop";
+            this.panelTop.Size = new System.Drawing.Size(900, 50);
+            this.panelTop.TabIndex = 1;
+            // 
+            // cmbStatusFilter
+            // 
+            this.cmbStatusFilter.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbStatusFilter.Items.AddRange(new object[] { "Все", "confirmed", "cancelled", "completed" });
+            this.cmbStatusFilter.Location = new System.Drawing.Point(60, 14);
+            this.cmbStatusFilter.Name = "cmbStatusFilter";
+            this.cmbStatusFilter.Size = new System.Drawing.Size(100, 21);
+            this.cmbStatusFilter.TabIndex = 10;
+            // 
+            // lblStatusFilter
+            // 
+            this.lblStatusFilter.AutoSize = true;
+            this.lblStatusFilter.Location = new System.Drawing.Point(10, 17);
+            this.lblStatusFilter.Name = "lblStatusFilter";
+            this.lblStatusFilter.Size = new System.Drawing.Size(44, 13);
+            this.lblStatusFilter.TabIndex = 9;
+            this.lblStatusFilter.Text = "Статус:";
+            // 
+            // txtSearch
+            // 
+            this.txtSearch.Location = new System.Drawing.Point(180, 15);
+            this.txtSearch.Name = "txtSearch";
+            this.txtSearch.Size = new System.Drawing.Size(150, 20);
+            this.txtSearch.TabIndex = 8;
+            // 
+            // btnSearch
+            // 
+            this.btnSearch.Location = new System.Drawing.Point(340, 13);
+            this.btnSearch.Name = "btnSearch";
+            this.btnSearch.Size = new System.Drawing.Size(70, 23);
+            this.btnSearch.TabIndex = 7;
+            this.btnSearch.Text = "Поиск";
+            this.btnSearch.UseVisualStyleBackColor = true;
+            // 
+            // btnRefresh
+            // 
+            this.btnRefresh.Location = new System.Drawing.Point(420, 13);
+            this.btnRefresh.Name = "btnRefresh";
+            this.btnRefresh.Size = new System.Drawing.Size(80, 23);
+            this.btnRefresh.TabIndex = 6;
+            this.btnRefresh.Text = "Обновить";
+            this.btnRefresh.UseVisualStyleBackColor = true;
+            // 
+            // btnAdd
+            // 
+            this.btnAdd.BackColor = System.Drawing.Color.LimeGreen;
+            this.btnAdd.Location = new System.Drawing.Point(620, 13);
+            this.btnAdd.Name = "btnAdd";
+            this.btnAdd.Size = new System.Drawing.Size(80, 23);
+            this.btnAdd.TabIndex = 5;
+            this.btnAdd.Text = "Добавить";
+            this.btnAdd.UseVisualStyleBackColor = false;
+            // 
+            // btnEdit
+            // 
+            this.btnEdit.Location = new System.Drawing.Point(710, 13);
+            this.btnEdit.Name = "btnEdit";
+            this.btnEdit.Size = new System.Drawing.Size(80, 23);
+            this.btnEdit.TabIndex = 4;
+            this.btnEdit.Text = "Изменить";
+            this.btnEdit.UseVisualStyleBackColor = true;
+            // 
+            // btnDelete
+            // 
+            this.btnDelete.BackColor = System.Drawing.Color.IndianRed;
+            this.btnDelete.Location = new System.Drawing.Point(800, 13);
+            this.btnDelete.Name = "btnDelete";
+            this.btnDelete.Size = new System.Drawing.Size(80, 23);
+            this.btnDelete.TabIndex = 3;
+            this.btnDelete.Text = "Удалить";
+            this.btnDelete.UseVisualStyleBackColor = false;
+            // 
+            // groupDetails
+            // 
+            this.groupDetails.Controls.Add(this.lblNotes);
+            this.groupDetails.Controls.Add(this.lblDuration);
+            this.groupDetails.Controls.Add(this.lblDateTime);
+            this.groupDetails.Controls.Add(this.lblGuests);
+            this.groupDetails.Controls.Add(this.lblPhone);
+            this.groupDetails.Controls.Add(this.lblClient);
+            this.groupDetails.Controls.Add(this.lblTable);
+            this.groupDetails.Dock = System.Windows.Forms.DockStyle.Bottom;
+            this.groupDetails.Location = new System.Drawing.Point(0, 450);
+            this.groupDetails.Name = "groupDetails";
+            this.groupDetails.Size = new System.Drawing.Size(900, 120);
+            this.groupDetails.TabIndex = 2;
+            this.groupDetails.TabStop = false;
+            this.groupDetails.Text = "Детали бронирования";
+            // 
+            // lblTable
+            // 
+            this.lblTable.AutoSize = true;
+            this.lblTable.Location = new System.Drawing.Point(15, 25);
+            this.lblTable.Name = "lblTable";
+            this.lblTable.Size = new System.Drawing.Size(38, 13);
+            this.lblTable.TabIndex = 0;
+            this.lblTable.Text = "Стол: ";
+            // 
+            // lblClient
+            // 
+            this.lblClient.AutoSize = true;
+            this.lblClient.Location = new System.Drawing.Point(15, 45);
+            this.lblClient.Name = "lblClient";
+            this.lblClient.Size = new System.Drawing.Size(46, 13);
+            this.lblClient.TabIndex = 1;
+            this.lblClient.Text = "Клиент:";
+            // 
+            // lblPhone
+            // 
+            this.lblPhone.AutoSize = true;
+            this.lblPhone.Location = new System.Drawing.Point(15, 65);
+            this.lblPhone.Name = "lblPhone";
+            this.lblPhone.Size = new System.Drawing.Size(55, 13);
+            this.lblPhone.TabIndex = 2;
+            this.lblPhone.Text = "Телефон:";
+            // 
+            // lblGuests
+            // 
+            this.lblGuests.AutoSize = true;
+            this.lblGuests.Location = new System.Drawing.Point(15, 85);
+            this.lblGuests.Name = "lblGuests";
+            this.lblGuests.Size = new System.Drawing.Size(67, 13);
+            this.lblGuests.TabIndex = 3;
+            this.lblGuests.Text = "Кол-во гостей:";
+            // 
+            // lblDateTime
+            // 
+            this.lblDateTime.AutoSize = true;
+            this.lblDateTime.Location = new System.Drawing.Point(400, 25);
+            this.lblDateTime.Name = "lblDateTime";
+            this.lblDateTime.Size = new System.Drawing.Size(67, 13);
+            this.lblDateTime.TabIndex = 4;
+            this.lblDateTime.Text = "Дата/время:";
+            // 
+            // lblDuration
+            // 
+            this.lblDuration.AutoSize = true;
+            this.lblDuration.Location = new System.Drawing.Point(400, 45);
+            this.lblDuration.Name = "lblDuration";
+            this.lblDuration.Size = new System.Drawing.Size(76, 13);
+            this.lblDuration.TabIndex = 5;
+            this.lblDuration.Text = "Длительность:";
+            // 
+            // lblNotes
+            // 
+            this.lblNotes.AutoSize = true;
+            this.lblNotes.Location = new System.Drawing.Point(400, 65);
+            this.lblNotes.Name = "lblNotes";
+            this.lblNotes.Size = new System.Drawing.Size(73, 13);
+            this.lblNotes.TabIndex = 6;
+            this.lblNotes.Text = "Примечание:";
+            // 
+            // ReservationForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(900, 570);
+            this.Controls.Add(this.dgvReservations);
+            this.Controls.Add(this.groupDetails);
+            this.Controls.Add(this.panelTop);
+            this.Name = "ReservationForm";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+            this.Text = "Управление бронированиями";
+            ((System.ComponentModel.ISupportInitialize)(this.dgvReservations)).EndInit();
+            this.panelTop.ResumeLayout(false);
+            this.panelTop.PerformLayout();
+            this.groupDetails.ResumeLayout(false);
+            this.groupDetails.PerformLayout();
+            this.ResumeLayout(false);
+        }
+
+        private System.Windows.Forms.DataGridView dgvReservations;
+        private System.Windows.Forms.Panel panelTop;
+        private System.Windows.Forms.ComboBox cmbStatusFilter;
+        private System.Windows.Forms.Label lblStatusFilter;
+        private System.Windows.Forms.TextBox txtSearch;
+        private System.Windows.Forms.Button btnSearch;
+        private System.Windows.Forms.Button btnRefresh;
+        private System.Windows.Forms.Button btnAdd;
+        private System.Windows.Forms.Button btnEdit;
+        private System.Windows.Forms.Button btnDelete;
+        private System.Windows.Forms.GroupBox groupDetails;
+        private System.Windows.Forms.Label lblTable;
+        private System.Windows.Forms.Label lblClient;
+        private System.Windows.Forms.Label lblPhone;
+        private System.Windows.Forms.Label lblGuests;
+        private System.Windows.Forms.Label lblDateTime;
+        private System.Windows.Forms.Label lblDuration;
+        private System.Windows.Forms.Label lblNotes;
+    }
+}

+ 134 - 0
ReservationForm.cs

@@ -0,0 +1,134 @@
+using RestaurantApp.DAL;
+using RestaurantApp.Models;
+using System;
+using System.Linq;
+using System.Windows.Forms;
+
+namespace RestaurantApp
+{
+    public partial class ReservationForm : Form
+    {
+        private ReservationRepository repo = new ReservationRepository();
+
+        public ReservationForm()
+        {
+            InitializeComponent();
+
+            cmbStatusFilter.SelectedIndex = 0;
+
+            LoadReservations();
+
+            btnSearch.Click += (s, e) => LoadReservations();
+            btnRefresh.Click += (s, e) => LoadReservations();
+            btnAdd.Click += BtnAdd_Click;
+            btnEdit.Click += BtnEdit_Click;
+            btnDelete.Click += BtnDelete_Click;
+            dgvReservations.SelectionChanged += DgvReservations_SelectionChanged;
+        }
+
+        private void LoadReservations()
+        {
+            try
+            {
+                string status = cmbStatusFilter.SelectedIndex > 0 ? cmbStatusFilter.Text : "";
+
+                // Если выбран "Все" - передаём пустую строку или null
+                string statusParam = (status == "Все" || string.IsNullOrEmpty(status)) ? null : status;
+
+                var reservations = repo.GetAll(statusParam, null, null);
+
+                if (!string.IsNullOrWhiteSpace(txtSearch.Text))
+                {
+                    var search = txtSearch.Text.ToLower();
+                    reservations = reservations.FindAll(r =>
+                        r.ClientName.ToLower().Contains(search) ||
+                        r.TableInfo.ToLower().Contains(search));
+                }
+
+                dgvReservations.DataSource = null;
+                dgvReservations.DataSource = reservations;
+
+                // ... остальное
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки: {ex.Message}");
+            }
+        }
+
+        private void DgvReservations_SelectionChanged(object sender, EventArgs e)
+        {
+            if (dgvReservations.CurrentRow != null)
+            {
+                var res = (Reservation)dgvReservations.CurrentRow.DataBoundItem;
+                lblTable.Text = $"Стол: {res.TableInfo}";
+                lblClient.Text = $"Клиент: {res.ClientName}";
+                lblPhone.Text = $"Телефон: {(string.IsNullOrEmpty(res.ClientName) ? "-" : "скрыт")}";
+                lblGuests.Text = $"Кол-во гостей: {res.GuestsCount}";
+                lblDateTime.Text = $"Дата/время: {res.ReservedAt:dd.MM.yyyy HH:mm}";
+                lblDuration.Text = $"Длительность: {res.DurationMin} мин";
+                lblNotes.Text = $"Примечание: {res.Notes}";
+            }
+        }
+
+        private void BtnAdd_Click(object sender, EventArgs e)
+        {
+            var form = new ReservationEditForm();
+            if (form.ShowDialog() == DialogResult.OK)
+            {
+                LoadReservations();
+            }
+        }
+
+        private void BtnEdit_Click(object sender, EventArgs e)
+        {
+            if (dgvReservations.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите бронирование");
+                return;
+            }
+
+            var res = (Reservation)dgvReservations.CurrentRow.DataBoundItem;
+            var form = new ReservationEditForm(res);
+            if (form.ShowDialog() == DialogResult.OK)
+            {
+                LoadReservations();
+            }
+        }
+
+        private void BtnDelete_Click(object sender, EventArgs e)
+        {
+            if (dgvReservations.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите бронирование");
+                return;
+            }
+
+            var res = (Reservation)dgvReservations.CurrentRow.DataBoundItem;
+
+            if (MessageBox.Show($"Удалить бронирование для {res.ClientName} на {res.ReservedAt:dd.MM.yyyy HH:mm}?",
+                "Подтверждение", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
+            {
+                try
+                {
+                    repo.Delete(res.ReservationId);
+                    LoadReservations();
+
+                    lblTable.Text = "Стол: ";
+                    lblClient.Text = "Клиент: ";
+                    lblPhone.Text = "Телефон: ";
+                    lblGuests.Text = "Кол-во гостей: ";
+                    lblDateTime.Text = "Дата/время: ";
+                    lblDuration.Text = "Длительность: ";
+                    lblNotes.Text = "Примечание: ";
+
+                    MessageBox.Show("Бронирование удалено", "Успех", MessageBoxButtons.OK, MessageBoxIcon.Information);
+                }
+                catch (Exception ex)
+                {
+                    MessageBox.Show($"Ошибка: {ex.Message}");
+                }
+            }
+        }
+    }
+}

+ 120 - 0
ReservationForm.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>

+ 135 - 0
ReservationRepository.cs

@@ -0,0 +1,135 @@
+using System;
+using System.Collections.Generic;
+using Npgsql;
+using RestaurantApp.Models;
+
+namespace RestaurantApp.DAL
+{
+    public class ReservationRepository
+    {
+        public List<Reservation> GetAll(string statusFilter = "", DateTime? dateFrom = null, DateTime? dateTo = null)
+        {
+            var list = new List<Reservation>();
+            using (var conn = Database.GetConnection())
+            {
+                var sql = @"SELECT r.reservation_id, r.table_id,
+                           h.name||' стол №'||dt.table_number as table_info,
+                           r.client_id, COALESCE(c.last_name||' '||c.first_name,'') as client_name,
+                           r.employee_id, COALESCE(e.last_name||' '||e.first_name,'') as emp_name,
+                           r.reserved_at, r.duration_min, r.guests_count,
+                           r.status, r.notes, r.created_at
+                    FROM reservation r
+                    JOIN dining_table dt ON dt.table_id = r.table_id
+                    JOIN hall h ON h.hall_id = dt.hall_id
+                    LEFT JOIN client c ON c.client_id = r.client_id
+                    LEFT JOIN employee e ON e.employee_id = r.employee_id
+                    WHERE 1=1 ";
+
+                if (!string.IsNullOrWhiteSpace(statusFilter) && statusFilter != "Все")
+                    sql += " AND r.status = @st";
+
+                if (dateFrom.HasValue)
+                    sql += " AND r.reserved_at >= @df";
+                if (dateTo.HasValue)
+                    sql += " AND r.reserved_at <= @dt";
+
+                sql += " ORDER BY r.reserved_at DESC LIMIT 500";
+
+                using (var cmd = new NpgsqlCommand(sql, conn))
+                {
+                    if (!string.IsNullOrWhiteSpace(statusFilter) && statusFilter != "Все")
+                        cmd.Parameters.AddWithValue("st", statusFilter);
+                    if (dateFrom.HasValue)
+                        cmd.Parameters.AddWithValue("df", dateFrom.Value);
+                    if (dateTo.HasValue)
+                        cmd.Parameters.AddWithValue("dt", dateTo.Value);
+
+                    using (var r = cmd.ExecuteReader())
+                        while (r.Read()) list.Add(MapRes(r));
+                }
+            }
+            return list;
+        }
+
+        public void Insert(Reservation res)
+        {
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                @"INSERT INTO reservation(table_id, client_id, employee_id, reserved_at, duration_min, guests_count, status, notes)
+          VALUES(@t, @c, @e, @ra, @dur, @g, @st, @n)", conn))
+            {
+                cmd.Parameters.AddWithValue("t", res.TableId);
+                cmd.Parameters.AddWithValue("c", res.ClientId.HasValue ? (object)res.ClientId.Value : DBNull.Value);
+                cmd.Parameters.AddWithValue("e", res.EmployeeId.HasValue ? (object)res.EmployeeId.Value : DBNull.Value);
+                cmd.Parameters.AddWithValue("ra", res.ReservedAt);
+                cmd.Parameters.AddWithValue("dur", res.DurationMin);
+                cmd.Parameters.AddWithValue("g", res.GuestsCount);
+                cmd.Parameters.AddWithValue("st", res.Status);
+                cmd.Parameters.AddWithValue("n", string.IsNullOrEmpty(res.Notes) ? (object)DBNull.Value : res.Notes);
+                cmd.ExecuteNonQuery();
+            }
+        }
+
+        public void Update(Reservation res)
+        {
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                @"UPDATE reservation SET table_id=@t, client_id=@c, employee_id=@e, 
+          reserved_at=@ra, duration_min=@dur, guests_count=@g, 
+          status=@st, notes=@n
+          WHERE reservation_id=@id", conn))
+            {
+                cmd.Parameters.AddWithValue("id", res.ReservationId);
+                cmd.Parameters.AddWithValue("t", res.TableId);
+                cmd.Parameters.AddWithValue("c", res.ClientId.HasValue ? (object)res.ClientId.Value : DBNull.Value);
+                cmd.Parameters.AddWithValue("e", res.EmployeeId.HasValue ? (object)res.EmployeeId.Value : DBNull.Value);
+                cmd.Parameters.AddWithValue("ra", res.ReservedAt);
+                cmd.Parameters.AddWithValue("dur", res.DurationMin);
+                cmd.Parameters.AddWithValue("g", res.GuestsCount);
+                cmd.Parameters.AddWithValue("st", res.Status);
+                cmd.Parameters.AddWithValue("n", string.IsNullOrEmpty(res.Notes) ? (object)DBNull.Value : res.Notes);
+                cmd.ExecuteNonQuery();
+            }
+        }
+
+        public void Delete(int id)
+        {
+            using (var conn = Database.GetConnection())
+            using (var cmd = new NpgsqlCommand(
+                "UPDATE reservation SET status = 'cancelled' WHERE reservation_id = @id", conn))
+            {
+                cmd.Parameters.AddWithValue("id", id);
+                cmd.ExecuteNonQuery();
+            }
+        }
+
+        private void SetParams(NpgsqlCommand cmd, Reservation res)
+        {
+            cmd.Parameters.AddWithValue("t", res.TableId);
+            cmd.Parameters.AddWithValue("c", res.ClientId.HasValue ? (object)res.ClientId.Value : DBNull.Value);
+            cmd.Parameters.AddWithValue("e", res.EmployeeId.HasValue ? (object)res.EmployeeId.Value : DBNull.Value);
+            cmd.Parameters.AddWithValue("ra", res.ReservedAt);
+            cmd.Parameters.AddWithValue("dur", res.DurationMin);
+            cmd.Parameters.AddWithValue("g", res.GuestsCount);
+            cmd.Parameters.AddWithValue("st", res.Status);
+            cmd.Parameters.AddWithValue("n", string.IsNullOrEmpty(res.Notes) ? (object)DBNull.Value : res.Notes);
+        }
+
+        private Reservation MapRes(NpgsqlDataReader r) => new Reservation
+        {
+            ReservationId = r.GetInt32(0),
+            TableId = r.GetInt32(1),
+            TableInfo = r.GetString(2),
+            ClientId = r.IsDBNull(3) ? (int?)null : r.GetInt32(3),
+            ClientName = r.GetString(4),
+            EmployeeId = r.IsDBNull(5) ? (int?)null : r.GetInt32(5),
+            EmployeeName = r.GetString(6),
+            ReservedAt = r.GetDateTime(7),
+            DurationMin = r.GetInt32(8),
+            GuestsCount = r.GetInt32(9),
+            Status = r.GetString(10),
+            Notes = r.IsDBNull(11) ? "" : r.GetString(11),
+            CreatedAt = r.GetDateTime(12)
+        };
+    }
+}

+ 254 - 0
RestaurantApp.csproj

@@ -0,0 +1,254 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{633C41ED-C421-43BA-99D7-5D3713445758}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <RootNamespace>RestaurantApp</RootNamespace>
+    <AssemblyName>RestaurantApp</AssemblyName>
+    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.1.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
+    </Reference>
+    <Reference Include="Npgsql, Version=4.1.3.0, Culture=neutral, PublicKeyToken=5d8b90d52f46fda7, processorArchitecture=MSIL">
+      <HintPath>..\packages\Npgsql.4.1.3\lib\net461\Npgsql.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Core" />
+    <Reference Include="System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Numerics" />
+    <Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.5.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.6.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Text.Encodings.Web, Version=4.0.4.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Text.Encodings.Web.4.6.0\lib\netstandard2.0\System.Text.Encodings.Web.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Text.Json, Version=4.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Text.Json.4.6.0\lib\net461\System.Text.Json.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.3\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll</HintPath>
+    </Reference>
+    <Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Deployment" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Client.cs" />
+    <Compile Include="ClientEditForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="ClientEditForm.Designer.cs">
+      <DependentUpon>ClientEditForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="ClientForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="ClientForm.Designer.cs">
+      <DependentUpon>ClientForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="ClientRepository.cs" />
+    <Compile Include="Database.cs" />
+    <Compile Include="Dish.cs" />
+    <Compile Include="DishEditForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="DishEditForm.Designer.cs">
+      <DependentUpon>DishEditForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="DishForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="DishForm.Designer.cs">
+      <DependentUpon>DishForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="DishRepository.cs" />
+    <Compile Include="Employee.cs" />
+    <Compile Include="EmployeeEditForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="EmployeeEditForm.Designer.cs">
+      <DependentUpon>EmployeeEditForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="EmployeeForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="EmployeeForm.Designer.cs">
+      <DependentUpon>EmployeeForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="EmployeeRepository.cs" />
+    <Compile Include="LoginForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="LoginForm.Designer.cs">
+      <DependentUpon>LoginForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="MainForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="MainForm.Designer.cs">
+      <DependentUpon>MainForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Order.cs" />
+    <Compile Include="OrderEditForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="OrderEditForm.Designer.cs">
+      <DependentUpon>OrderEditForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="OrderForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="OrderForm.Designer.cs">
+      <DependentUpon>OrderForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="OrderItem.cs" />
+    <Compile Include="OrderItemEditForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="OrderItemEditForm.Designer.cs">
+      <DependentUpon>OrderItemEditForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="OrderRepository.cs" />
+    <Compile Include="PaymentForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="PaymentForm.Designer.cs">
+      <DependentUpon>PaymentForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="ReportForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="ReportForm.Designer.cs">
+      <DependentUpon>ReportForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="ReportRepository.cs" />
+    <Compile Include="Reservation.cs" />
+    <Compile Include="ReservationEditForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="ReservationEditForm.Designer.cs">
+      <DependentUpon>ReservationEditForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="ReservationForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="ReservationForm.Designer.cs">
+      <DependentUpon>ReservationForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="ReservationRepository.cs" />
+    <EmbeddedResource Include="ClientEditForm.resx">
+      <DependentUpon>ClientEditForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="ClientForm.resx">
+      <DependentUpon>ClientForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="DishEditForm.resx">
+      <DependentUpon>DishEditForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="DishForm.resx">
+      <DependentUpon>DishForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="EmployeeEditForm.resx">
+      <DependentUpon>EmployeeEditForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="EmployeeForm.resx">
+      <DependentUpon>EmployeeForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="LoginForm.resx">
+      <DependentUpon>LoginForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="MainForm.resx">
+      <DependentUpon>MainForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="OrderEditForm.resx">
+      <DependentUpon>OrderEditForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="OrderForm.resx">
+      <DependentUpon>OrderForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="OrderItemEditForm.resx">
+      <DependentUpon>OrderItemEditForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="PaymentForm.resx">
+      <DependentUpon>PaymentForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
+    <EmbeddedResource Include="ReportForm.resx">
+      <DependentUpon>ReportForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="ReservationEditForm.resx">
+      <DependentUpon>ReservationEditForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="ReservationForm.resx">
+      <DependentUpon>ReservationForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <None Include="packages.config" />
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>

+ 118 - 0
RestaurantAppSetup.iss

@@ -0,0 +1,118 @@
+; Build the project before compiling this script.
+; Preferred layout:
+;   bin\Release\RestaurantApp.exe
+; Optional .NET Framework 4.7.2 redistributable:
+;   redist\NDP472-KB4054530-x86-x64-AllOS-ENU.exe
+
+#define AppName "RestaurantApp"
+#define AppVersion "1.0.0"
+#define AppPublisher "RestaurantApp"
+#define AppExeName "RestaurantApp.exe"
+#define DotNetRedist "redist\\NDP472-KB4054530-x86-x64-AllOS-ENU.exe"
+
+#ifexist "bin\Release\RestaurantApp.exe"
+  #define BuildDir "bin\\Release"
+#else
+  #define BuildDir "bin\\Debug"
+#endif
+
+#ifexist DotNetRedist
+  #define HasDotNetRedist
+#endif
+
+[Setup]
+AppId={{A7A8807E-9A78-44D1-B879-B0C1EF78C6C1}
+AppName={#AppName}
+AppVersion={#AppVersion}
+AppPublisher={#AppPublisher}
+DefaultDirName={autopf}\{#AppName}
+DefaultGroupName={#AppName}
+DisableProgramGroupPage=yes
+PrivilegesRequired=admin
+OutputDir=installer-output
+OutputBaseFilename=RestaurantAppSetup
+Compression=lzma
+SolidCompression=yes
+WizardStyle=modern
+UninstallDisplayIcon={app}\{#AppExeName}
+
+[Languages]
+Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl"
+
+[Tasks]
+Name: "desktopicon"; Description: "Create a desktop shortcut"; GroupDescription: "Additional shortcuts:"
+
+[Files]
+Source: "{#BuildDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.pdb,*.xml,*.vshost.*"
+#ifdef HasDotNetRedist
+Source: "{#DotNetRedist}"; DestDir: "{tmp}"; Flags: deleteafterinstall
+#endif
+
+[Icons]
+Name: "{autoprograms}\{#AppName}"; Filename: "{app}\{#AppExeName}"
+Name: "{autodesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Tasks: desktopicon
+
+[Run]
+#ifdef HasDotNetRedist
+Filename: "{tmp}\NDP472-KB4054530-x86-x64-AllOS-ENU.exe"; Parameters: "/passive /norestart"; StatusMsg: "Installing .NET Framework 4.7.2..."; Flags: waituntilterminated; Check: NeedsDotNet472
+#endif
+Filename: "{app}\{#AppExeName}"; Description: "Launch {#AppName}"; Flags: nowait postinstall skipifsilent; Check: IsDotNet472Installed
+
+[Code]
+function GetDotNetRelease(): Cardinal;
+var
+  Release: Cardinal;
+begin
+  Result := 0;
+
+  if RegQueryDWordValue(HKLM, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', Release) then
+  begin
+    Result := Release;
+    exit;
+  end;
+
+  if IsWin64 and RegQueryDWordValue(HKLM64, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', Release) then
+  begin
+    Result := Release;
+    exit;
+  end;
+
+  if RegQueryDWordValue(HKLM32, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', Release) then
+  begin
+    Result := Release;
+  end;
+end;
+
+function IsDotNet472Installed(): Boolean;
+begin
+  Result := GetDotNetRelease() >= 461808;
+end;
+
+function NeedsDotNet472(): Boolean;
+begin
+  Result := not IsDotNet472Installed();
+end;
+
+function InitializeSetup(): Boolean;
+begin
+  Result := True;
+
+  if IsDotNet472Installed() then
+    exit;
+
+#ifdef HasDotNetRedist
+  MsgBox(
+    '.NET Framework 4.7.2 was not found. The installer will install it before the first launch.',
+    mbInformation,
+    MB_OK
+  );
+#else
+  MsgBox(
+    '.NET Framework 4.7.2 or newer is required.' + #13#10 + #13#10 +
+    'Place the offline installer in redist\\NDP472-KB4054530-x86-x64-AllOS-ENU.exe and rebuild setup, or install .NET manually before running setup.',
+    mbCriticalError,
+    MB_OK
+  );
+  Result := False;
+#endif
+end;

BIN
installer-output/RestaurantAppSetup.exe


+ 13 - 0
packages.config

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Microsoft.Bcl.AsyncInterfaces" version="1.0.0" targetFramework="net472" />
+  <package id="Npgsql" version="4.1.3" targetFramework="net472" />
+  <package id="System.Buffers" version="4.5.0" targetFramework="net472" />
+  <package id="System.Memory" version="4.5.3" targetFramework="net472" />
+  <package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net472" />
+  <package id="System.Runtime.CompilerServices.Unsafe" version="4.6.0" targetFramework="net472" />
+  <package id="System.Text.Encodings.Web" version="4.6.0" targetFramework="net472" />
+  <package id="System.Text.Json" version="4.6.0" targetFramework="net472" />
+  <package id="System.Threading.Tasks.Extensions" version="4.5.3" targetFramework="net472" />
+  <package id="System.ValueTuple" version="4.5.0" targetFramework="net472" />
+</packages>