cs_edit_product2.md 25 KB

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

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

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

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

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

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

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

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

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

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

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

Подсветка элементов по условию. Дополнительные выборки.Массовая смена цены продукции. Содержание Основы языка Kotlin
Критерий Баллы
Реализован переход на окно добавления 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. Создайте новое окно: EditProductWindow (в папке Windows - не забываем про логическую структуру проекта)

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

    public Product currentProduct { get; set; };
    
  3. В разметке окна вставьте аттрибут Name

    <Window
        ...
        Name="root">
    
  4. В конструктор окна добавьте параметр типа Product, присвойте его ранее объявленному свойству. Задайте заголовок окна:

    public EditWindow(Product EditProduct)
    {
        currentProduct = editProduct;
        InitializeComponent();
        root.Title = currentProduct.Id == 0 ? "Новый продукт" : "Редактирование продукта";
    }
    
  5. В разметке окна редактирования продукции создайте сетку из трёх колонок: в первой у нас будет изображение, во второй редактируемые поля продукта, а в третей список материалов

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

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

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

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

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

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

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

      И в конструкторе получаем его из БД:

      using (var context = new ksmirnovContext())
      {
          productTypeList = context.ProductTypes.ToList();
      }        
      

      В вёрстке окна редактирования продукции мы можем использовать выпадающий список, атрибут SelectedItem которого позволяет отобразить текущее значение редактируемого элемента

      <ComboBox 
          ItemsSource="{Binding #root.productTypeList}"
          SelectedItem="{Binding #root.currentProduct.ProductType}"/>
      

      Из документации: *Класс ComboBox выполняет поиск указанного объекта с помощью IndexOf метода. Этот метод, в свою очередь, использует метод Equals для определения равенства (объектов)*.

      А метод Equals сравнивает объекты по уникальному идентификатору (встроенное свойство у базового класса в C#, не путать с полем id таблицы БД), т.е. свойство ProductType у экземпляра продукции НЕ РАВНО экземпляру элемента списка productTypeList

      Чтобы исправить эту неприятность нужно переопределить метод Equals у класса ProductType:

      public override bool Equals(object obj)
      {
          return (obj != null) && 
              (obj is ProductType) && 
              (this.Id == (obj as ProductType).Id);
      }
      

      Здесь мы проверяем определён ли вообще объект (у нового продукта его ещё нет), нужного ли он типа и совпадет ли его Id с Id текущего типа продукции

    • смена изображения продукции

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

      <Image
          Name="CurrentProductImage"
          Width="200" 
          Height="200"
          Source="{Binding #root.currentProduct.ImageBitmap}" />
      

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

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

      private void ChangeImage_Click(object sender, RoutedEventArgs e)
      {
          var getImageDialog = new OpenFileDialog();
      
          // задаем фильтр для выбираемых файлов
          getImageDialog.Filters.Add(
              new FileDialogFilter() {
                  Name = "Файлы изображений", 
                  Extensions = {"png", "jpg", "jpeg"}});
      
          // чтобы не искать по всему диску задаем начальный каталог
          // В ТЕКУЩЕЙ ВЕРСИИ АВАЛОНИИ ТАКОГО СВОЙСТВА НЕТ
          // getImageDialog.InitialDirectory = Environment.CurrentDirectory;
      
          // открываем СИНХРОННО диалог
          // в ответ получим массив выбранных файлов
          var files = await getImageDialog.ShowAsync(this);
          if (files != null)
          {
              // перед присвоением пути к картинке обрезаем начало строки, т.к. диалог возвращает полный путь
              currentProduct.Image = files[0].Substring(Environment.CurrentDirectory.Length);
      
              // тут нужно либо реализовать интерфейс InotifyPropertyChanged
              // и обновить информацию о картинке
      
              // либо тупо поменять свойство нужного контрола
              CurrentProductImage.Source = currentProduct.ImageBitmap;
          }
      }
      
    • Многострочное описание

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

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

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

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        using (var context = new esmirnovContext())
        {
            // вся работа с БД должна быть завернута в исключения
            try
            {
                Product product = null;
                if (currentProduct.Id != 0)
                    product = context.Products.Find(currentProduct.Id);
                else
                    product = new Product();
    
                if (product != null)
                {
                    // сюда добавлять проверки
    
                    product.Title = currentProduct.Title;
                    product.ArticleNumber = currentProduct.ArticleNumber;
    
                    ...
    
                    product.ProductTypeId = currentProduct.ProductType.Id;
    
                    if (product.Id==0)
                        context.Products.Add(product);
                    else
                        context.Products.Update(product);
    
                    if (context.SaveChanges() > 0)
                    {
                        Close(true);
                    }
                        
                }
            }
            catch (Exception ex)
            {
                // if(ex.InnerException != null)
                //     MessageBox.Show(ex.InnerException.Message);
                // else
                //     MessageBox.Show(ex.Message);            
            }
        }    
    }
    

Открытие окна редактирования для существующей и новой продукции

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

    private void ProductListView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
    {
        var currentProduct = (sender as ListBox).SelectedItem as Product;
        // в создаваемое окно передаем выбранный продукт
    
        var newEditWindow = new EditProductWindow(product);
        var res = await newEditWindow.ShowDialog<bool>(this);
        if (res)
        {
            // эту функцию реализуйте сами - тут перечитывание списка продукции из базы
            getProductList();   
        }
    }
    
  • для создания нового продукта в разметке главного окна создайте кнопку "Добавить продукцию" (в левой или верхней панели) и в её обработчике создайте новый экземпляр продукта, а дальше то же, что и в предыдущем пункте

    var newEditWindow = new EditProductWindow(new Product());
    ...
    

Проверки перед сохранением продукта

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

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

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

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

    Тут надо делать запрос к базе c методом Where

    var dublicateArticle = context.Products
        .Where(p => p.ArticleNumber == currentProduct.ArticleNumber)
        .FirstOrDefault();
    
    if (dublicateArticle != null)
        throw new Exception("Такой артикул уже есть в базе");
    

Удаление продукции

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

Добавьте кнопку Удалить в среднюю колоку (рядом с кнопкой сохранить). Атрибут IsVisible привяжите к Id продукта (т.е. у нового продукта кнопки удалить быть не должно). Можно реализовать через Binding, но можно задать в конструкторе

DeleteButton.IsVisible = currentProduct.Id > 0;

И реализуйте обработчик клика по этой кнопке

private void DeleteProductButton_Click(object sender, RoutedEventArgs e)
{
    using (var context = new esmirnovContext())
    {
        try
        {
            // тут вставляем проверки, требуемые по ТЗ

            // + исключение, если есть продажи
            var saleCount = context.ProductSales
                .Count(ps => ps.ProductId == currentProduct.Id);

            if (saleCount > 0)
                throw new Exception("Нельзя удалять продукт с продажами");

            // - удаление списка материалов продукта (НЕ ПРОВЕРЯЛ)
            context.ProductMaterials.RemoveRange(
                context.ProductMaterials.Where(pm => pm.ProductId == currentProduct.Id)
            );

            // - удаление истории изменения цен (если есть)

            var product = context.Products.Find(currentProduct.Id);
            context.Products.Remove(product);
            if (context.SaveChanges() > 0)
            {
                Close(true);
            }
        }
        catch (Exception ex)
        {
            // if(ex.InnerException != null)
            //    MessageBox.Show(ex.InnerException.Message);
            // else
            //    MessageBox.Show(ex.Message);            
        }
    }
}

Метод RemoveRange не рекомендуется использовать в больших базах, в инетах пишут, что он вытягивает всю таблицу в ОЗУ. Лучше использовать либо одиночное удаление в цикле, либо "чистый" SQL запрос:

context.Database.ExecuteSqlRaw($"DELETE FROM ProductMaterial WHERE ProductId={currentProduct.Id}");
// также можно удалить и сам продукт
context.Database.ExecuteSqlRaw($"DELETE FROM Product WHERE Id={currentProduct.Id}");

Но в случае использования "чистых" SQL-запросов нужно учитывать, что EntityFramework не будет знать сколько записей затронуто и нужно убрать проверку количества измененных записей в SaveChanges (собственно и сам этот метод не нужно вызывать, если всё сделано "чистыми" SQL-запросами)

Подсветка элементов по условию. Дополнительные выборки.Массовая смена цены продукции. Содержание Основы языка Kotlin