cs_edit_product.md 24 KB

Добавление/редактирование продукции

Необходимо добавить возможность редактирования данных существующей продукции, а также добавление новой продукции в новом окне - форме для добавления/редактирования продукции.

Переходы на данное окно должны быть реализованы из главной формы списка: для редактирования - при нажатии на конкретный элемент, для добавления - при нажатии кнопки “Добавить продукцию”.

На форме должны быть предусмотрены следующие поля: артикул, наименование, тип продукта (выпадающий список), изображение, количество человек для производства, номер производственного цеха, минимальная стоимость для агента и подробное описание (с возможностью многострочного ввода).

Также необходимо реализовать вывод списка материалов, используемых при производстве продукции, с указанием количества. В список можно добавлять новые позиции и удалять существующие. При добавлении материалы должны выбираться из выпадающего списка с возможностью поиска по наименованию.

При открытии формы для редактирования все поля выбранного объекта должны быть подгружены в соответствующие поля из базы данных, а таблица заполнена актуальными значениями.

Стоимость продукции может включать сотые части, а также не может быть отрицательной. Система должна проверять существование продукта с введенным артикулом и не давать использовать один артикул для нескольких продуктов.

Пользователь может добавить/заменить изображение у продукции.

Для того чтобы администратор случайно не изменял несколько продуктов, предусмотрите невозможность открытия более одного окна редактирования.

В окне редактирования продукта должна присутствовать кнопка “Удалить”, которая удаляет продукт из базы данных. При этом должны соблюдаться следующие условия. Если у продукта есть информация о материалах, используемых при его производстве, или история изменения цен, то эта информация должна быть удалена вместе с продуктом. Но если у продукта есть информация о его продажах агентами, то удаление продукта из базы данных должно быть запрещено. После удаления продукта система должна сразу вернуть пользователя обратно к списку продукции.

После редактирования/добавления/удаления продукции данные в окне списка продукции должны быть обновлены.

Подсветка элементов по условию. Дополнительные выборки.Массовая смена цены продукции. Содержание Создание, изменение продукции
Критерий Баллы
Реализован переход на окно добавления 0.1
Реализован переход на окно редактирования выбранного объекта 0.2
Присутствуют все поля для заполнения 0.5
При редактировании продукции в поля для ввода загружены данные из БД 0.3
Выбор типа продукта реализован в виде выпадающего списка со значениями из БД 0.3
Для ввода описания продукции предусмотрено многострочное поле для ввода 0.2
Реализован список используемых материалов для текущего продукта 0.3
В списке присутствует название материала и используемое количество 0.2
При редактировании продукции список материалов заполнен значениями из БД 0.2
В список можно добавлять новые позиции 0.3
Из списка можно удалять существующие позиции 0.2
При добавлении материалы выбираются из выпадающего списка со значениями из БД 0.3
В списке материалов реализована возможность поиска по наименованию 0.2
Список используемых материалов сохраняется в БД при добавлении 0.5
Список используемых материалов сохраняется в БД при редактировании 0.5
Стоимость продукции не может быть отрицательной 0.1
Стоимость продукции записывается только с точностью до сотых 0.2
Реализована проверка артикула на уникальность 0.3
Есть возможность выбрать изображение 0.2
Изображение продукции подгружается из БД при редактировании 0.2
Есть возможность заменить изображение 0.1
Данные при добавлении сохраняются в БД 0.5
Данные при редактировании изменяются в БД 0.5
Открывается только одно окно редактирования 0.1
Реализовано удаление выбранного продукта, у которого не заполнен список используемых материалов 0.2
Реализовано удаление продукта вместе с информацией об используемых материалах 0.5
Запрещено удаление продукта, по которому были выполнены продажи агентом 0.3
После удаления реализован автоматический переход обратно в список 0.1
После закрытия окна данные в таблице обновляются 0.3
Итого 7.9

Создание окна редактирования продукции

Для добавления и редактирования мы будем использовать одно и то же окно. Название окна будем вычислять по наличию ID у продукции (у новой записи это поле = 0)

  1. Создайте новое окно: EditWindow

  2. В классе окна EditWindow добавьте свойство CurrentProduct, в котором будет храниться добавляемый/редактируемый экземпляр продукции:

    public Product CurrentProduct { get; set; }
    

    И геттер для названия окна:

    public string WindowName {
        get {
            return CurrentProduct.ID == 0 ? "Новый продукт" : "Редактирование продукта";
        }
    }
    
  3. В конструктор окна добавьте параметр типа Product и присвойте его ранее объявленному свойству:

    public EditWindow(Product EditProduct)
    {
        InitializeComponent();
        DataContext = this;
        CurrentProduct = EditProduct;
    }
    
  4. В разметке окна вместо фиксированного названия вставьте привязку к свойству WindowName

    <Window
        ...
        Title="{Binding WindowName}">
    
  5. В окне создайте сетку из трёх колонок: в первой у нас будет изображение, во второй редактируемые поля продукта, а в третей список материалов

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="auto"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="auto"/>
    </Grid.ColumnDefinitions>
    
  6. Во вторую колонку добавьте StackPanel с границами (чтобы визуальные компоненты не прилипали к границам окна) и в этом списке разместите редактируемые элементы

    На форме должны быть предусмотрены следующие поля: артикул, наименование, тип продукта (выпадающий список), изображение, количество человек для производства, номер производственного цеха, минимальная стоимость для агента и подробное описание

    <StackPanel 
        Margin="5">
    
        <Label Content="Артикул"/>
        <TextBox Text="{Binding CurrentProduct.ArticleNumber}"/>
    
        <Label Content="Наименование продукта"/>
        <TextBox Text="{Binding CurrentService.Title}"/>
    
        ...
    
    </StackPanel>
    

    Обычные поля наклепайте по шаблону сами, а я подробнее остановлюсь на полях: тип продукта, изображение и описание:

    • Выбор типа продукта из списка

      В классе окна объявляем свойство ProductTypes - список типов продукции

      public IEnumerable<ProductType> ProductTypes { get; set; }
      

      И в конструкторе получаем его из поставщика данных

      ProductTypes = Globals.DataProvider.GetProductTypes();
      

      В выпадающий список мы должны передать собственно список выбираемых объектов (ItemsSource) и текущий объект (SelectedItem) из этого списка. Но у нас в модели Product нет объекта ProductType, есть отдельные поля ProductTypeID и ProductTypeTitle. Изменим модель, вместо этих полей сделаем поле CurrentProductType, значение которого будем получать из списка типов по ID

      В поставщик данных добавим переменную, в которой будет храниться список типов продукции:

      private List<ProductType> ProductTypes = null;
      

      Исправим метод GetProductTypes, чтобы он считывал список только в первый раз (поиск объектов происходит по хешу и нам важно, чтобы этот список был всегда один и тот же)

      public IEnumerable<ProductType> GetProductTypes()
      {
          if (ProductTypes == null)
          {
              ProductTypes = new List<ProductType>();
              ...
      

      В модели Product убираем свойства ProductTypeTitle, ProductTypeID и добавляем CurrentProductType (после этого пересоберите проект и исправьте возникшие ошибки)

      // public string ProductTypeTitle { get; set; }
      // public int ProductTypeID { get; set; }
      public ProductType CurrentProductType { get; set; }
      

      И в классе MySQLDataProvider переделайте получение типа продукта:

      // NewProduct.ProductTypeID = Reader.GetInt32("ProductTypeID");
      // NewProduct.ProductTypeTitle = Reader["ProductTypeTitle"].ToString();
      NewProduct.CurrentProductType = GetProductType(Reader.GetInt32("ProductTypeID"));
      

      Реализация метода GetProductType:

      private ProductType GetProductType(int Id)
      {
          // тут заполнится список типов продукции, если он ещё пустой
          GetProductTypes();
          return ProductTypes.Find(pt => pt.ID == Id);
      }
      

      Теперь в верстке окна редактирования продукции мы можем использовать выпадающий список

      <ComboBox 
          ItemsSource="{Binding ProductTypes}"
          SelectedItem="{Binding CurrentProduct.CurrentProductType}"/>
      
    • смена изображения продукции

      Вывод изображения производится как и в главном окне

      <Image
          Width="200" 
          Height="200"
          Source="{Binding CurrentProduct.ImagePreview,TargetNullValue={StaticResource defaultImage}}" />
      

      А для смены изображения используем стандартный диалог Windows, повесив его на кнопку Сменить картинку (кнопку добавьте сами в StackPanel)

      Обработчик кнопки:

      private void ChangeImage_Click(object sender, RoutedEventArgs e)
      {
          OpenFileDialog GetImageDialog = new OpenFileDialog();
          // задаем фильтр для выбираемых файлов
          // до символа "|" идет произвольный текст, а после него шаблоны файлов разделенные точкой с запятой
          GetImageDialog.Filter = "Файлы изображений: (*.png, *.jpg)|*.png;*.jpg";
          // чтобы не искать по всему диску задаем начальный каталог
          GetImageDialog.InitialDirectory = Environment.CurrentDirectory;
          if (GetImageDialog.ShowDialog() == true)
          {
              // перед присвоением пути к картинке обрезаем начало строки, т.к. диалог возвращает полный путь
              CurrentProduct.Image = GetImageDialog.FileName.Substring(Environment.CurrentDirectory.Length);
              // обратите внимание, это другое окно и другой Invalidate, который реализуйте сами
              Invalidate();
          }
      }
      
    • Многострочное описание

      Тут просто - разрешаем переносы и задаем высоту элемента

      <Label Content="Описание продукта"/>
      <TextBox 
          AcceptsReturn="True"
          Height="2cm"
          Text="{Binding CurrentProduct.Description}"/>
      
  7. Сохранение введенных данных

    В разметку добавьте кнопку Сохранить и напишите обработчик

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        // вся работа с БД должна быть завернута в исключения
        try
        {
            // сюда добавлять проверки
    
            // метод SaveProduct реализуем ниже
            Globals.DataProvider.SaveProduct(CurrentProduct);
            DialogResult = true;
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }
    

    В интерфейсе IDataProvider объявляем метод SaveProduct:

    void SaveProduct(Product ChangedProduct);
    

    И реализуем его в классе поставщика данных

    public void SaveProduct(Product ChangedProduct)
    {
        Connection.Open();
        try
        {
            if (ChangedProduct.ID == 0)
            {
                // новый продукт - добавляем запись
                string Query = @"INSERT INTO Product
                (Title,
                ProductTypeID,
                ArticleNumber,
                Description,
                Image,
                ProductionPersonCount,
                ProductionWorkshopNumber,
                MinCostForAgent)
                VALUES
                (@Title,
                @ProductTypeID,
                @ArticleNumber,
                @Description,
                @Image,
                @ProductionPersonCount,
                @ProductionWorkshopNumber,
                @MinCostForAgent)";
    
                MySqlCommand Command = new MySqlCommand(Query, Connection);
                Command.Parameters.AddWithValue("@Title", ChangedProduct.Title);
                Command.Parameters.AddWithValue("@ProductTypeID", ChangedProduct.CurrentProductType.ID);
                Command.Parameters.AddWithValue("@ArticleNumber", ChangedProduct.ArticleNumber);
                Command.Parameters.AddWithValue("@Description", ChangedProduct.Description);
                Command.Parameters.AddWithValue("@Image", ChangedProduct.Image);
                Command.Parameters.AddWithValue("@ProductionPersonCount", ChangedProduct.ProductionPersonCount);
                Command.Parameters.AddWithValue("@ProductionWorkshopNumber", ChangedProduct.ProductionWorkshopNumber);
                Command.Parameters.AddWithValue("@MinCostForAgent", ChangedProduct.MinCostForAgent);
                Command.ExecuteNonQuery();
            }
            else
            {
                // существующий продукт - изменяем запись
    
                string Query = @"UPDATE Product
                SET
                Title = @Title,
                ProductTypeID = @ProductTypeID,
                ArticleNumber = @ArticleNumber,
                Description = @Description,
                Image = @Image,
                ProductionPersonCount = @ProductionPersonCount,
                ProductionWorkshopNumber = @ProductionWorkshopNumber,
                MinCostForAgent = @MinCostForAgent
                WHERE ID = @ID";
    
                MySqlCommand Command = new MySqlCommand(Query, Connection);
                Command.Parameters.AddWithValue("@Title", ChangedProduct.Title);
                Command.Parameters.AddWithValue("@ProductTypeID", ChangedProduct.CurrentProductType.ID);
                Command.Parameters.AddWithValue("@ArticleNumber", ChangedProduct.ArticleNumber);
                Command.Parameters.AddWithValue("@Description", ChangedProduct.Description);
                Command.Parameters.AddWithValue("@Image", ChangedProduct.Image);
                Command.Parameters.AddWithValue("@ProductionPersonCount", ChangedProduct.ProductionPersonCount);
                Command.Parameters.AddWithValue("@ProductionWorkshopNumber", ChangedProduct.ProductionWorkshopNumber);
                Command.Parameters.AddWithValue("@MinCostForAgent", ChangedProduct.MinCostForAgent);
                Command.Parameters.AddWithValue("@ID", ChangedProduct.ID);
                Command.ExecuteNonQuery();
            }
        }
        finally
        {
            Connection.Close();
        }
    }
    
  8. Открытие окна редактирования для существующей и новой продукции

    • для редактирования существующей продукции в списке продукции реализуем обработчик двойного клика

      private void ProductListView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
      {
          // в создаваемое окно передаем выбранный продукт
          var NewEditWindow = new EditWindow(ProductListView.SelectedItem as Product);
          if ((bool)NewEditWindow.ShowDialog())
          {
              // при успешном сохранении продукта перечитываем список продукции
              ProductList = Globals.DataProvider.GetProducts();
          }
      }
      
    • для создания нового продукта в разметке главного окна создайте кнопку "Добавить продукцию" (либо в верхней панели, либо в левой) и в её обработчике создайте новый экземпляр продукта

      var NewEditWindow = new EditWindow(new Product());
      ...
      
  9. Проверки перед сохранением продукта

    Все проверки вставляем в обработчик кнопки "Сохранить" окна редактирования, т.к. это относится к бизнес-логике, до вызова метода сохранения продукта

    • Стоимость продукции не может быть отрицательной

      if (ChangedProduct.MinCostForAgent < 0)
          throw new Exception("Цена продукта не может быть отрицательной");
      
    • Стоимость продукции записывается только с точностью до сотых

    • Реализована проверка артикула на уникальность

      Тут по идее надо делать запрос к базе, но у нас есть метод получения списка продукции и мы можем искать в нём используя LINQ-запросы